Merge pull request 'haroon_dev' (#359) from haroon_dev into master

Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/359
master
Haroon6138 6 days ago
commit 184b836668

@ -21,7 +21,7 @@
"mySchedule": "My Schedule", "mySchedule": "My Schedule",
"logout": "Logout", "logout": "Logout",
"respirationRate": "Respiration Rate", "respirationRate": "Respiration Rate",
"bookAppo": "New Appointment", "bookAppo": "Book Appointment",
"searchBy": "Search By:", "searchBy": "Search By:",
"clinic": "Clinic", "clinic": "Clinic",
"byClinic": "By Clinic", "byClinic": "By Clinic",

@ -86,7 +86,7 @@
<key>NSMicrophoneUsageDescription</key> <key>NSMicrophoneUsageDescription</key>
<string>This app requires microphone access to enable virtual consultation between patient &amp; doctor</string> <string>This app requires microphone access to enable virtual consultation between patient &amp; doctor</string>
<key>NSMotionUsageDescription</key> <key>NSMotionUsageDescription</key>
<string>This app requires motion detection access to function properly.</string> <string>This app requires access to motion detection to count your daily steps.</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>This app requires photo library access to select image as document &amp; upload it.</string> <string>This app requires photo library access to select image as document &amp; upload it.</string>
<key>NSPhotoLibraryAddUsageDescription</key> <key>NSPhotoLibraryAddUsageDescription</key>

@ -210,8 +210,8 @@ class ApiClientImp implements ApiClient {
body['TokenID'] = "@dm!n"; body['TokenID'] = "@dm!n";
} }
body['TokenID'] = "@dm!n"; // body['TokenID'] = "@dm!n";
// body['PatientID'] = 1307867; // body['PatientID'] = 3310954;
// body['PatientID'] = 53320; // body['PatientID'] = 53320;
// body['PatientTypeID'] = 1; // body['PatientTypeID'] = 1;
// body['PatientOutSA'] = 0; // body['PatientOutSA'] = 0;

@ -5,7 +5,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts { class ApiConsts {
static const maxSmallScreen = 660; static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT
@ -911,8 +911,6 @@ var AUTO_GENERATE_INVOICE_TAMARA = 'Services/PayFort_Serv.svc/REST/Tamara_Getinf
var GET_ONESIGNAL_VOIP_TOKEN = 'https://onesignal.com/api/v1/players'; var GET_ONESIGNAL_VOIP_TOKEN = 'https://onesignal.com/api/v1/players';
var CANCEL_PHARMA_LIVECARE_REQUEST = 'https://vcallapi.hmg.com/api/PharmaLiveCare/SendPaymentStatus';
var INSERT_FREE_SLOTS_LOGS = 'Services/Doctors.svc/Rest/InsertDoctorFreeSlotsLogs'; var INSERT_FREE_SLOTS_LOGS = 'Services/Doctors.svc/Rest/InsertDoctorFreeSlotsLogs';
var GET_NATIONALITY = 'Services/Lists.svc/REST/GetNationality'; var GET_NATIONALITY = 'Services/Lists.svc/REST/GetNationality';

@ -27,7 +27,7 @@ class RequestUtils {
}) { }) {
bool fileNo = false; bool fileNo = false;
if (nationId.isNotEmpty) { if (nationId.isNotEmpty) {
final numericRegex = RegExp(r'^[0-9]+$'); final numericRegex = RegExp(r'^[0-9]+$');
fileNo = nationId.length < 10 && numericRegex.hasMatch(nationId); fileNo = nationId.length < 10 && numericRegex.hasMatch(nationId);
//fileNo = nationId.length < 10 && nationId.isNumericOnly() ; //fileNo = nationId.length < 10 && nationId.isNumericOnly() ;
if (fileNo) { if (fileNo) {
@ -40,14 +40,23 @@ class RequestUtils {
if (zipCode == "0") { if (zipCode == "0") {
request.patientMobileNumberOthers = phoneNumber; request.patientMobileNumberOthers = phoneNumber;
} else { } else {
request.patientMobileNumber = int.parse(phoneNumber); // Remove any non-numeric characters before parsing
final numericPhone = phoneNumber.replaceAll(RegExp(r'[^0-9]'), '');
final parsedPhone = int.tryParse(numericPhone);
if (parsedPhone != null) {
request.patientMobileNumber = parsedPhone;
} else {
// If parsing fails, use as string in Others field
request.patientMobileNumberOthers = phoneNumber;
}
} }
} }
request.oTPSendType = otpTypeEnum.toInt(); // could map OTPTypeEnum if needed request.oTPSendType = otpTypeEnum.toInt(); // could map OTPTypeEnum if needed
request.zipCode = zipCode; // or countryCode if defined elsewhere request.zipCode = zipCode; // or countryCode if defined elsewhere
if (isForRegister) { if (isForRegister) {
request.patientIdentificationID = int.parse(nationId); final parsedNationId = int.tryParse(nationId.replaceAll(RegExp(r'[^0-9]'), ''));
request.patientIdentificationID = parsedNationId ?? 0;
request.searchType = 1; request.searchType = 1;
request.isHijri = calenderType.toInt; request.isHijri = calenderType.toInt;
request.patientID = patientId; request.patientID = patientId;
@ -56,7 +65,7 @@ class RequestUtils {
request.isDentalAllowedBackend = false; request.isDentalAllowedBackend = false;
} else { } else {
if (fileNo) { if (fileNo) {
request.patientID = patientId ?? int.parse(nationId); request.patientID = patientId ?? (int.tryParse(nationId.replaceAll(RegExp(r'[^0-9]'), '')) ?? 0);
request.patientIdentificationID = request.nationalID; request.patientIdentificationID = request.nationalID;
request.searchType = 2; request.searchType = 2;
} else { } else {
@ -71,15 +80,15 @@ class RequestUtils {
static dynamic getCommonRequestWelcome( static dynamic getCommonRequestWelcome(
{required String phoneNumber, {required String phoneNumber,
required OTPTypeEnum otpTypeEnum, required OTPTypeEnum otpTypeEnum,
required String? deviceToken, required String? deviceToken,
required bool patientOutSA, required bool patientOutSA,
required String? loginTokenID, required String? loginTokenID,
RegistrationDataModelPayload? registeredData, RegistrationDataModelPayload? registeredData,
int? patientId, int? patientId,
required String nationIdText, required String nationIdText,
required String countryCode, required String countryCode,
required int loginType}) { required int loginType}) {
bool fileNo = false; bool fileNo = false;
if (nationIdText.isNotEmpty) { if (nationIdText.isNotEmpty) {
final numericRegex = RegExp(r'^[0-9]+$'); final numericRegex = RegExp(r'^[0-9]+$');
@ -91,8 +100,17 @@ class RequestUtils {
request.patientMobileNumberOthers = phoneNumber; request.patientMobileNumberOthers = phoneNumber;
request.mobileNo = phoneNumber; request.mobileNo = phoneNumber;
} else { } else {
request.patientMobileNumber = int.parse(phoneNumber); // Remove any non-numeric characters before parsing
request.mobileNo = '0$phoneNumber'; final numericPhone = phoneNumber.replaceAll(RegExp(r'[^0-9]'), '');
final parsedPhone = int.tryParse(numericPhone);
if (parsedPhone != null) {
request.patientMobileNumber = parsedPhone;
request.mobileNo = '0$numericPhone';
} else {
// If parsing fails, use as string in Others field
request.patientMobileNumberOthers = phoneNumber;
request.mobileNo = phoneNumber;
}
} }
request.deviceToken = deviceToken; request.deviceToken = deviceToken;
request.projectOutSA = patientOutSA; request.projectOutSA = patientOutSA;
@ -106,8 +124,8 @@ class RequestUtils {
request.searchType = registeredData.searchType != null request.searchType = registeredData.searchType != null
? registeredData.searchType ? registeredData.searchType
: fileNo : fileNo
? 1 ? 1
: 2; : 2;
request.patientID = registeredData.patientId ?? 0; request.patientID = registeredData.patientId ?? 0;
request.patientIdentificationID = request.nationalID = (registeredData.patientIdentificationId ?? 0); request.patientIdentificationID = request.nationalID = (registeredData.patientIdentificationId ?? 0);
request.dob = registeredData.dob; request.dob = registeredData.dob;
@ -115,7 +133,8 @@ class RequestUtils {
log("nationIdText: ${nationIdText}"); log("nationIdText: ${nationIdText}");
} else { } else {
if (fileNo) { if (fileNo) {
request.patientID = patientId ?? int.parse(nationIdText); final numericNationId = nationIdText.replaceAll(RegExp(r'[^0-9]'), '');
request.patientID = patientId ?? (int.tryParse(numericNationId) ?? 0);
request.patientIdentificationID = request.nationalID = '0'; request.patientIdentificationID = request.nationalID = '0';
request.searchType = 2; request.searchType = 2;
//TODO: Issue HEre is Not Login //TODO: Issue HEre is Not Login
@ -155,8 +174,17 @@ class RequestUtils {
request.patientMobileNumberOthers = mobileNumber; request.patientMobileNumberOthers = mobileNumber;
request.mobileNo = mobileNumber; request.mobileNo = mobileNumber;
} else { } else {
request.patientMobileNumber = int.parse(mobileNumber); // Remove any non-numeric characters before parsing
request.mobileNo = '0$mobileNumber'; final numericMobile = mobileNumber.replaceAll(RegExp(r'[^0-9]'), '');
final parsedMobile = int.tryParse(numericMobile);
if (parsedMobile != null) {
request.patientMobileNumber = parsedMobile;
request.mobileNo = '0$numericMobile';
} else {
// If parsing fails, use as string in Others field
request.patientMobileNumberOthers = mobileNumber;
request.mobileNo = mobileNumber;
}
} }
} }
request.projectOutSA = patientOutSA; request.projectOutSA = patientOutSA;
@ -240,16 +268,16 @@ class RequestUtils {
"Patientobject": { "Patientobject": {
"TempValue": true, "TempValue": true,
"PatientIdentificationType": (isDubai "PatientIdentificationType": (isDubai
? appState.getUserRegistrationPayload.patientIdentificationId?.toString().substring(0, 1) ? appState.getUserRegistrationPayload.patientIdentificationId?.toString().substring(0, 1)
: appState.getNHICUserData.idNumber!.substring(0, 1)) == : appState.getNHICUserData.idNumber!.substring(0, 1)) ==
"1" "1"
? 1 ? 1
: 2, : 2,
"PatientIdentificationNo": "PatientIdentificationNo":
isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(), isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(),
"MobileNumber": appState.getUserRegistrationPayload.patientMobileNumber ?? 0, "MobileNumber": appState.getUserRegistrationPayload.patientMobileNumber ?? 0,
"PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode ||
appState.getUserRegistrationPayload.zipCode == '+966') appState.getUserRegistrationPayload.zipCode == '+966')
? 0 ? 0
: 1, : 1,
"FirstNameN": isDubai ? "..." : appState.getNHICUserData.firstNameAr, "FirstNameN": isDubai ? "..." : appState.getNHICUserData.firstNameAr,
@ -266,31 +294,31 @@ class RequestUtils {
"DateofBirthN": date, "DateofBirthN": date,
"EmailAddress": emailAddress, "EmailAddress": emailAddress,
"SourceType": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || "SourceType": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode ||
appState.getUserRegistrationPayload.zipCode == '+966') appState.getUserRegistrationPayload.zipCode == '+966')
? "1" ? "1"
: "2", : "2",
"PreferredLanguage": appState.getLanguageCode() == "ar" ? (isDubai ? "1" : 1) : (isDubai ? "2" : 2), "PreferredLanguage": appState.getLanguageCode() == "ar" ? (isDubai ? "1" : 1) : (isDubai ? "2" : 2),
"Marital": isDubai "Marital": isDubai
? (maritalStatus == MaritalStatusTypeEnum.single ? (maritalStatus == MaritalStatusTypeEnum.single
? '0' ? '0'
: maritalStatus == MaritalStatusTypeEnum.married : maritalStatus == MaritalStatusTypeEnum.married
? '1' ? '1'
: '2') : '2')
: (appState.getNHICUserData.maritalStatusCode == 'U' : (appState.getNHICUserData.maritalStatusCode == 'U'
? '0' ? '0'
: appState.getNHICUserData.maritalStatusCode == 'M' : appState.getNHICUserData.maritalStatusCode == 'M'
? '1' ? '1'
: '2'), : '2'),
}, },
"PatientIdentificationID": "PatientIdentificationID":
isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(), isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(),
"PatientMobileNumber": appState.getUserRegistrationPayload.patientMobileNumber.toString()[0] == '0' "PatientMobileNumber": appState.getUserRegistrationPayload.patientMobileNumber.toString()[0] == '0'
? appState.getUserRegistrationPayload.patientMobileNumber ? appState.getUserRegistrationPayload.patientMobileNumber
: '0${appState.getUserRegistrationPayload.patientMobileNumber}', : '0${appState.getUserRegistrationPayload.patientMobileNumber}',
"DOB": dob, "DOB": dob,
"IsHijri": appState.getUserRegistrationPayload.isHijri, "IsHijri": appState.getUserRegistrationPayload.isHijri,
"PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode ||
appState.getUserRegistrationPayload.zipCode == '+966') appState.getUserRegistrationPayload.zipCode == '+966')
? 0 ? 0
: 1, : 1,
"isDentalAllowedBackend": appState.getUserRegistrationPayload.isDentalAllowedBackend, "isDentalAllowedBackend": appState.getUserRegistrationPayload.isDentalAllowedBackend,
@ -314,7 +342,9 @@ class RequestUtils {
request.sharedPatientId = 0; request.sharedPatientId = 0;
request.sharedPatientIdentificationId = nationalIDorFile; request.sharedPatientIdentificationId = nationalIDorFile;
} else if (loginType == 2) { } else if (loginType == 2) {
request.sharedPatientId = int.parse(nationalIDorFile); // Remove any non-numeric characters before parsing
final numericId = nationalIDorFile.replaceAll(RegExp(r'[^0-9]'), '');
request.sharedPatientId = int.tryParse(numericId) ?? 0;
request.sharedPatientIdentificationId = ''; request.sharedPatientIdentificationId = '';
} }
request.searchType = loginType; request.searchType = loginType;
@ -325,4 +355,4 @@ class RequestUtils {
request.isDentalAllowedBackend = false; request.isDentalAllowedBackend = false;
return request; return request;
} }
} }

@ -676,7 +676,6 @@ class BookAppointmentsViewModel extends ChangeNotifier {
(failure) async { (failure) async {
isDoctorsListLoading = false; isDoctorsListLoading = false;
if (onError != null) onError(LocaleKeys.noDoctorFound.tr()); if (onError != null) onError(LocaleKeys.noDoctorFound.tr());
notifyListeners(); notifyListeners();
}, },
(apiResponse) { (apiResponse) {
@ -691,7 +690,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
clearSearchFilters(); clearSearchFilters();
getFiltersFromDoctorList(); getFiltersFromDoctorList();
_groupDoctorsList(); _groupDoctorsList();
setIsNearestAppointmentSelected(isNearest); // setIsNearestAppointmentSelected(isNearest);
notifyListeners(); notifyListeners();
if (onSuccess != null) { if (onSuccess != null) {
onSuccess(apiResponse); onSuccess(apiResponse);

@ -56,6 +56,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
bool isAppointmentDataToBeLoaded = true; bool isAppointmentDataToBeLoaded = true;
bool isMyDoctorsDataToBeLoaded = true; bool isMyDoctorsDataToBeLoaded = true;
bool isArrivedAppointmentDataLoaded = false; bool isArrivedAppointmentDataLoaded = false;
bool isFullArrivedAppointmentsLoaded = false;
bool isEyeMeasurementsAppointmentsLoading = false; bool isEyeMeasurementsAppointmentsLoading = false;
@ -77,7 +78,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
List<PatientAppointmentHistoryResponseModel> patientUpcomingAppointmentsHistoryList = []; List<PatientAppointmentHistoryResponseModel> patientUpcomingAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientArrivedAppointmentsHistoryList = []; List<PatientAppointmentHistoryResponseModel> patientArrivedAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientAllArrivedAppointmentsHistoryList = []; // List<PatientAppointmentHistoryResponseModel> patientAllArrivedAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientMyDoctorsList = []; List<PatientAppointmentHistoryResponseModel> patientMyDoctorsList = [];
@ -173,11 +174,12 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientAppointmentsHistoryList.clear(); patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear(); patientUpcomingAppointmentsHistoryList.clear();
patientArrivedAppointmentsHistoryList.clear(); patientArrivedAppointmentsHistoryList.clear();
patientAllArrivedAppointmentsHistoryList.clear(); // patientAllArrivedAppointmentsHistoryList.clear();
patientEyeMeasurementsAppointmentsHistoryList.clear(); patientEyeMeasurementsAppointmentsHistoryList.clear();
isMyAppointmentsLoading = true; isMyAppointmentsLoading = true;
isTimeLineAppointmentsLoading = true; isTimeLineAppointmentsLoading = true;
patientMyDoctorsList.clear(); patientMyDoctorsList.clear();
isFullArrivedAppointmentsLoaded = false;
} }
isTamaraDetailsLoading = true; isTamaraDetailsLoading = true;
isAppointmentPatientShareLoading = true; isAppointmentPatientShareLoading = true;
@ -276,13 +278,19 @@ class MyAppointmentsViewModel extends ChangeNotifier {
getPatientAppointmentQueueDetails(); getPatientAppointmentQueueDetails();
} }
} }
// Skip API call if full data is already loaded and we're not requesting timeline view
if (!isForTimeLine && isFullArrivedAppointmentsLoaded && !isAppointmentDataToBeLoaded) {
return;
}
if (!isAppointmentDataToBeLoaded) return; if (!isAppointmentDataToBeLoaded) return;
patientAppointmentsByClinic.clear(); patientAppointmentsByClinic.clear();
patientAppointmentsByHospital.clear(); patientAppointmentsByHospital.clear();
patientAppointmentsViewList.clear(); patientAppointmentsViewList.clear();
patientAllArrivedAppointmentsHistoryList.clear(); // patientAllArrivedAppointmentsHistoryList.clear();
filteredAppointmentList.clear(); filteredAppointmentList.clear();
patientAppointmentsHistoryList.clear(); patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear(); patientUpcomingAppointmentsHistoryList.clear();
@ -321,8 +329,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
isMyAppointmentsLoading = false; isMyAppointmentsLoading = false;
isAppointmentDataToBeLoaded = false; isAppointmentDataToBeLoaded = false;
if (!isForTimeLine) { if (!isForTimeLine) {
patientAllArrivedAppointmentsHistoryList = apiResponse.data!; // patientAllArrivedAppointmentsHistoryList = apiResponse.data!;
isArrivedAppointmentDataLoaded = true; isArrivedAppointmentDataLoaded = true;
isFullArrivedAppointmentsLoaded = true;
} }
notifyListeners(); notifyListeners();
if (onSuccess != null) { if (onSuccess != null) {
@ -354,6 +363,69 @@ class MyAppointmentsViewModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
changeTabToArrived() {
if (isFullArrivedAppointmentsLoaded && patientUpcomingAppointmentsHistoryList.isEmpty && patientArrivedAppointmentsHistoryList.isNotEmpty && selectedTabIndex == 0) {
selectedTabIndex = 1;
updateListWRTTab(1);
notifyListeners();
}
}
/// Loads full arrived appointments list if not already loaded
/// This is called when navigating to My Appointments page from landing page
Future<void> loadFullArrivedAppointmentsIfNeeded({Function(dynamic)? onSuccess, Function(String)? onError}) async {
// Skip if already loaded
if (isFullArrivedAppointmentsLoaded) {
// Auto-switch to Arrived tab if no upcoming appointments (even when data is already loaded)
if (patientUpcomingAppointmentsHistoryList.isEmpty && patientArrivedAppointmentsHistoryList.isNotEmpty && selectedTabIndex == 0) {
selectedTabIndex = 1;
updateListWRTTab(1);
notifyListeners();
}
return;
}
setIsAppointmentsHistoryLoading(true);
// Fetch full arrived appointments (isForTimeLine = false)
final result = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true, isForTimeLine: false);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// Handle error
} else if (apiResponse.messageStatus == 1) {
patientArrivedAppointmentsHistoryList = apiResponse.data!;
// patientAllArrivedAppointmentsHistoryList = apiResponse.data!;
isArrivedAppointmentDataLoaded = true;
isFullArrivedAppointmentsLoaded = true;
// Rebuild the combined list
patientAppointmentsHistoryList.clear();
patientAppointmentsHistoryList.addAll(patientUpcomingAppointmentsHistoryList);
patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList);
isMyAppointmentsLoading = false;
// Auto-switch to Arrived tab if no upcoming appointments
if (patientUpcomingAppointmentsHistoryList.isEmpty && patientArrivedAppointmentsHistoryList.isNotEmpty && selectedTabIndex == 0) {
selectedTabIndex = 1;
updateListWRTTab(1);
} else {
// Update filtered list based on current tab
updateListWRTTab(selectedTabIndex);
}
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
void getFiltersForSelectedAppointmentList(List<PatientAppointmentHistoryResponseModel> filteredAppointmentList) { void getFiltersForSelectedAppointmentList(List<PatientAppointmentHistoryResponseModel> filteredAppointmentList) {
availableFilters.clear(); availableFilters.clear();
if (filteredAppointmentList.isEmpty == true) return; if (filteredAppointmentList.isEmpty == true) return;
@ -362,9 +434,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
availableFilters.add(AppointmentListingFilters.LIVECARE); availableFilters.add(AppointmentListingFilters.LIVECARE);
} }
if (filteredAppointmentList.any((element) => element.isLiveCareAppointment == false)) { // if (filteredAppointmentList.any((element) => element.isLiveCareAppointment == false)) {
availableFilters.add(AppointmentListingFilters.WALKIN); // availableFilters.add(AppointmentListingFilters.WALKIN);
} // }
if (filteredAppointmentList.any((element) => AppointmentType.isArrived(element) == true)) { if (filteredAppointmentList.any((element) => AppointmentType.isArrived(element) == true)) {
availableFilters.add(AppointmentListingFilters.ARRIVED); availableFilters.add(AppointmentListingFilters.ARRIVED);
@ -801,15 +873,15 @@ class MyAppointmentsViewModel extends ChangeNotifier {
selectedFilter = []; selectedFilter = [];
// if(previouslySelectedTab == selectedTabIndex ) return; // if(previouslySelectedTab == selectedTabIndex ) return;
switch (index) { switch (index) {
// case 0:
// filteredAppointmentList.clear();
// filteredAppointmentList.addAll(patientAppointmentsHistoryList);
// break;
case 0: case 0:
filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientAppointmentsHistoryList);
break;
case 1:
filteredAppointmentList.clear(); filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientUpcomingAppointmentsHistoryList); filteredAppointmentList.addAll(patientUpcomingAppointmentsHistoryList);
break; break;
case 2: case 1:
filteredAppointmentList.clear(); filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientArrivedAppointmentsHistoryList); filteredAppointmentList.addAll(patientArrivedAppointmentsHistoryList);
break; break;
@ -835,11 +907,12 @@ class MyAppointmentsViewModel extends ChangeNotifier {
this.end = end; this.end = end;
isDateFilterSelected = true; isDateFilterSelected = true;
List<PatientAppointmentHistoryResponseModel> sourceList = []; List<PatientAppointmentHistoryResponseModel> sourceList = [];
if (selectedTabIndex == 0) { // if (selectedTabIndex == 0) {
sourceList = patientAppointmentsHistoryList; // sourceList = patientAppointmentsHistoryList;
} else if (selectedTabIndex == 1) { // } else
if (selectedTabIndex == 0) {
sourceList = patientUpcomingAppointmentsHistoryList; sourceList = patientUpcomingAppointmentsHistoryList;
} else if (selectedTabIndex == 2) { } else if (selectedTabIndex == 1) {
sourceList = patientArrivedAppointmentsHistoryList; sourceList = patientArrivedAppointmentsHistoryList;
} }
// if (isDateFilterSelected) sourceList = filteredAppointmentList; // if (isDateFilterSelected) sourceList = filteredAppointmentList;

@ -191,7 +191,9 @@ class PrescriptionsViewModel extends ChangeNotifier {
result.fold( result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure), // (failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async { (failure) async {
onError!(failure.message); if (onError != null) {
onError(failure.message);
}
}, },
(apiResponse) async { (apiResponse) async {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {

@ -1200,8 +1200,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) { onSuccess: (apiResponse) {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); // myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.getPatientAppointments(true, false); // myAppointmentsViewModel.getPatientAppointments(true, false);
showCommonBottomSheet(context, showCommonBottomSheet(context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), callBackFunc: (str) { child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), callBackFunc: (str) {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);

@ -69,12 +69,18 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
widget.patientAppointmentHistoryResponseModel.clinicID, widget.patientAppointmentHistoryResponseModel.clinicID,
widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onSuccess: (val) { widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onSuccess: (val) {
myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) { myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) {
if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null) { // if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null && myAppointmentsViewModel.patientAppointmentShareResponseModel != null) {
if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null &&
myAppointmentsViewModel.patientAppointmentShareResponseModel?.patientShareWithTax != null &&
myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel?.minLimit?.amount != null &&
myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel?.maxLimit?.amount != null) {
if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! &&
myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) {
setState(() { if (mounted) {
isShowTamara = true; setState(() {
}); isShowTamara = true;
});
}
} }
} }
}); });

@ -45,8 +45,9 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
scheduleMicrotask(() { scheduleMicrotask(() {
if (!myAppointmentsViewModel.isMyAppointmentsLoading) { if (!myAppointmentsViewModel.isMyAppointmentsLoading) {
myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false); myAppointmentsViewModel.getPatientAppointments(true, false, isForTimeLine: false);
} }
// myAppointmentsViewModel.changeTabToArrived();
}); });
super.initState(); super.initState();
} }
@ -69,9 +70,9 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
activeTextColor: Color(0xffED1C2B), activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
tabs: [ tabs: [
CustomTabBarModel(null, LocaleKeys.allAppt.tr(context: context)), // CustomTabBarModel(null, LocaleKeys.allAppt.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.upcoming.tr(context: context)), CustomTabBarModel(null, LocaleKeys.upcoming.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.completed.tr(context: context)), CustomTabBarModel(null, LocaleKeys.arrived.tr(context: context)),
], ],
onTabChange: (index) { onTabChange: (index) {
setState(() { setState(() {
@ -80,6 +81,10 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
myAppointmentsViewModel.onTabChange(index); myAppointmentsViewModel.onTabChange(index);
myAppointmentsViewModel.updateListWRTTab(index); myAppointmentsViewModel.updateListWRTTab(index);
context.read<DateRangeSelectorRangeViewModel>().flush(); context.read<DateRangeSelectorRangeViewModel>().flush();
if(index == 1) {
myAppointmentsViewModel.loadFullArrivedAppointmentsIfNeeded();
}
}, },
).paddingSymmetrical(24.h, 0.h), ).paddingSymmetrical(24.h, 0.h),
// Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) { // Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
@ -150,7 +155,7 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
? myAppointmentsVM.patientAppointmentsViewList.length ? myAppointmentsVM.patientAppointmentsViewList.length
: 1, : 1,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final isExpanded = myAppointmentsVM.selectedTabIndex == 1 ? true : expandedIndex == index; final isExpanded = myAppointmentsVM.selectedTabIndex == 0 ? true : expandedIndex == index;
return myAppointmentsVM.isMyAppointmentsLoading return myAppointmentsVM.isMyAppointmentsLoading
? Container( ? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),

@ -1021,6 +1021,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
void onClinicSelected(GetClinicsListResponseModel clinic) { void onClinicSelected(GetClinicsListResponseModel clinic) {
bookAppointmentsViewModel.setSelectedClinic(clinic); bookAppointmentsViewModel.setSelectedClinic(clinic);
bookAppointmentsViewModel.setIsDoctorsListLoading(true); bookAppointmentsViewModel.setIsDoctorsListLoading(true);
searchEditingController.text = "";
if (clinic.isLiveCareClinicAndOnline ?? false) { if (clinic.isLiveCareClinicAndOnline ?? false) {
Navigator.of(context).push( Navigator.of(context).push(
CustomPageRoute( CustomPageRoute(

@ -48,7 +48,7 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
Clarity.setCurrentScreenName('Select Doctor Page'); Clarity.setCurrentScreenName('Select Doctor Page');
_scrollController = ScrollController(); _scrollController = ScrollController();
scheduleMicrotask(() { scheduleMicrotask(() {
bookAppointmentsViewModel.setIsNearestAppointmentSelected(true); bookAppointmentsViewModel.setIsNearestAppointmentSelected(false);
if (bookAppointmentsViewModel.isLiveCareSchedule) { if (bookAppointmentsViewModel.isLiveCareSchedule) {
bookAppointmentsViewModel.getLiveCareDoctorsList(); bookAppointmentsViewModel.getLiveCareDoctorsList();
} else { } else {
@ -57,8 +57,7 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
} else if (bookAppointmentsViewModel.isGetDocForHealthCal) { } else if (bookAppointmentsViewModel.isGetDocForHealthCal) {
bookAppointmentsViewModel.getDoctorsListByHealthCal(); bookAppointmentsViewModel.getDoctorsListByHealthCal();
} else { } else {
bookAppointmentsViewModel.setIsNearestAppointmentSelected(true); bookAppointmentsViewModel.getDoctorsList(isNearest: false);
bookAppointmentsViewModel.getDoctorsList(isNearest: true);
} }
} }
}); });

@ -144,8 +144,9 @@ class DoctorCard extends StatelessWidget {
spacing: 3.h, spacing: 3.h,
runSpacing: 4.h, runSpacing: 4.h,
children: [ children: [
bookAppointmentsViewModel.isNearestAppointmentSelected // bookAppointmentsViewModel.isNearestAppointmentSelected
? doctorsListResponseModel.nearestFreeSlot != null // ?
doctorsListResponseModel.nearestFreeSlot != null
? AppCustomChipWidget( ? AppCustomChipWidget(
labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)), labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)),
// richText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)) // richText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot))
@ -155,7 +156,8 @@ class DoctorCard extends StatelessWidget {
textColor: AppColors.successColor, textColor: AppColors.successColor,
).toShimmer2(isShow: isLoading) ).toShimmer2(isShow: isLoading)
: SizedBox.shrink() : SizedBox.shrink()
: SizedBox.shrink(), // : SizedBox.shrink()
,
AppCustomChipWidget( AppCustomChipWidget(
labelText: "${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}", labelText: "${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}",
).toShimmer2(isShow: isLoading), ).toShimmer2(isShow: isLoading),

@ -176,6 +176,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
); );
}); });
}, onError: (err) { }, onError: (err) {
LoaderBottomSheet.showLoader();
showCommonBottomSheetWithoutHeight( showCommonBottomSheetWithoutHeight(
context, context,
child: Utils.getErrorWidget(loadingText: err.toString()), child: Utils.getErrorWidget(loadingText: err.toString()),

@ -165,7 +165,7 @@ class _LandingPageState extends State<LandingPage> {
immediateLiveCareViewModel.initImmediateLiveCare(); immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory(); immediateLiveCareViewModel.getPatientLiveCareHistory();
myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false); myAppointmentsViewModel.getPatientAppointments(true, false, isForTimeLine: true);
emergencyServicesViewModel.checkPatientERAdvanceBalance(); emergencyServicesViewModel.checkPatientERAdvanceBalance();
// myAppointmentsViewModel.getPatientAppointmentQueueDetails(); // myAppointmentsViewModel.getPatientAppointmentQueueDetails();
notificationsViewModel.initNotificationsViewModel(); notificationsViewModel.initNotificationsViewModel();
@ -230,7 +230,7 @@ class _LandingPageState extends State<LandingPage> {
// Refresh Appointments Data // Refresh Appointments Data
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false); myAppointmentsViewModel.getPatientAppointments(true, false, isForTimeLine: true);
// Refresh Appointments Data // Refresh Appointments Data
habibWalletVM.initHabibWalletProvider(); habibWalletVM.initHabibWalletProvider();

@ -74,7 +74,7 @@ class _PreferredLanguageWidgetState extends State<PreferredLanguageWidget> {
callBackFunc: () async { callBackFunc: () async {
Navigator.of(GetIt.instance<NavigationService>().navigatorKey.currentContext!).pop(); Navigator.of(GetIt.instance<NavigationService>().navigatorKey.currentContext!).pop();
profileSettingsViewModel.getProfileSettings(); profileSettingsViewModel.getProfileSettings();
}, isFullScreen: false, isAutoDismiss: true); }, isFullScreen: false, isAutoDismiss: true, isCloseButtonVisible: false);
}, },
onError: (error) { onError: (error) {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();

@ -134,7 +134,7 @@ class _UpdateEmailDialogState extends State<UpdateEmailDialog> {
callBackFunc: () async { callBackFunc: () async {
Navigator.of(getIt<NavigationService>().navigatorKey.currentContext!).pop(); Navigator.of(getIt<NavigationService>().navigatorKey.currentContext!).pop();
profileSettingsViewModel!.getProfileSettings(); profileSettingsViewModel!.getProfileSettings();
}, isFullScreen: false, isAutoDismiss: true); }, isFullScreen: false, isAutoDismiss: true, isCloseButtonVisible: false);
}, },
onError: (error) { onError: (error) {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();

@ -41,7 +41,7 @@ class _UpdateEmergencyContactDialogState extends State<UpdateEmergencyContactDia
// Set the text // Set the text
setState(() { setState(() {
textController!.text = viewModel.getPatientInfoForUpdate!.emergencyContactNo ?? ""; textController!.text = viewModel.getPatientInfoForUpdate != null ? viewModel.getPatientInfoForUpdate!.emergencyContactNo ?? "" : "";
}); });
}); });

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

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -9,7 +11,6 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'dart:ui' as ui;
class CustomCountryDropdown extends StatefulWidget { class CustomCountryDropdown extends StatefulWidget {
final List<CountryEnum> countryList; final List<CountryEnum> countryList;
@ -35,7 +36,9 @@ class CustomCountryDropdown extends StatefulWidget {
class CustomCountryDropdownState extends State<CustomCountryDropdown> { class CustomCountryDropdownState extends State<CustomCountryDropdown> {
CountryEnum? selectedCountry; CountryEnum? selectedCountry;
late OverlayEntry _overlayEntry; OverlayEntry? _overlayEntry;
Timer? _showDropdownTimer;
Timer? _refocusTimer;
bool _isDropdownOpen = false; bool _isDropdownOpen = false;
FocusNode textFocusNode = FocusNode(); FocusNode textFocusNode = FocusNode();
@ -55,6 +58,9 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
@override @override
void dispose() { void dispose() {
_showDropdownTimer?.cancel();
_refocusTimer?.cancel();
_removeOverlayEntry();
textFocusNode.dispose(); textFocusNode.dispose();
super.dispose(); super.dispose();
} }
@ -74,9 +80,18 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
}, },
child: Row( child: Row(
children: [ children: [
Utils.buildSvgWithAssets(icon: selectedCountry != null ? selectedCountry!.iconPath : AppAssets.ksa, width: 40.h, height: 40.h, applyThemeColor: false), Utils.buildSvgWithAssets(
icon: selectedCountry != null
? selectedCountry!.iconPath
: AppAssets.ksa,
width: 40.h,
height: 40.h,
applyThemeColor: false),
SizedBox(width: 8.h), SizedBox(width: 8.h),
Utils.buildSvgWithAssets(icon: _isDropdownOpen ? AppAssets.dropdow_icon : AppAssets.dropdow_icon), Utils.buildSvgWithAssets(
icon: _isDropdownOpen
? AppAssets.dropdow_icon
: AppAssets.dropdow_icon),
], ],
), ),
), ),
@ -94,7 +109,11 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
children: [ children: [
Text( Text(
LocaleKeys.phoneNumber.tr(), LocaleKeys.phoneNumber.tr(),
style: TextStyle(fontSize: 12.f, height: 1.5, fontWeight: FontWeight.w600, letterSpacing: -1), style: TextStyle(
fontSize: 12.f,
height: 1.5,
fontWeight: FontWeight.w600,
letterSpacing: -1),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -102,9 +121,15 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
if (selectedCountry != CountryEnum.others) if (selectedCountry != CountryEnum.others)
Text( Text(
selectedCountry!.countryCode, selectedCountry!.countryCode,
style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"), style: TextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w600,
letterSpacing: -0.4,
height: 1.5,
fontFamily: "Poppins"),
), ),
if (selectedCountry != CountryEnum.others) SizedBox(width: 4.h), if (selectedCountry != CountryEnum.others)
SizedBox(width: 4.h),
if (widget.isEnableTextField) if (widget.isEnableTextField)
SizedBox( SizedBox(
height: 20.h, height: 20.h,
@ -113,14 +138,19 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: TextField( child: TextField(
focusNode: textFocusNode, focusNode: textFocusNode,
style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"), style: TextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w600,
letterSpacing: -0.4,
height: 1.5,
fontFamily: "Poppins"),
decoration: InputDecoration( decoration: InputDecoration(
hintText: selectedCountry == CountryEnum.others ? "001*******" : "", hintText: selectedCountry == CountryEnum.others
? "001*******"
: "",
isDense: true, isDense: true,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
), ),
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
onChanged: widget.onPhoneNumberChanged, onChanged: widget.onPhoneNumberChanged,
@ -136,21 +166,31 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
Text( Text(
selectedCountry != null selectedCountry != null
? appState.getLanguageCode() == "ar" ? appState.getLanguageCode() == "ar"
? selectedCountry!.nameArabic ? selectedCountry!.nameArabic
: selectedCountry!.displayName : selectedCountry!.displayName
: LocaleKeys.selectCountry.tr(), : LocaleKeys.selectCountry.tr(),
style: TextStyle(fontSize: 14.f, height: 21 / 14, fontWeight: FontWeight.w600, letterSpacing: -0.2), style: TextStyle(
fontSize: 14.f,
height: 21 / 14,
fontWeight: FontWeight.w600,
letterSpacing: -0.2),
), ),
], ],
); );
} }
void _openDropdown() { void _openDropdown() {
if (!mounted || _isDropdownOpen || _overlayEntry != null) return;
_showDropdownTimer?.cancel();
if (textFocusNode.hasFocus) { if (textFocusNode.hasFocus) {
textFocusNode.unfocus(); textFocusNode.unfocus();
// Wait for keyboard to close before calculating position // Wait for keyboard to close before calculating position
Future.delayed(Duration(milliseconds: 300), () { _showDropdownTimer = Timer(const Duration(milliseconds: 300), () {
_showDropdown(); _showDropdownTimer = null;
if (mounted) {
_showDropdown();
}
}); });
} else { } else {
_showDropdown(); _showDropdown();
@ -158,8 +198,18 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
} }
void _showDropdown() { void _showDropdown() {
if (!mounted || _isDropdownOpen || _overlayEntry != null) return;
AppState appState = getIt.get<AppState>(); AppState appState = getIt.get<AppState>();
RenderBox renderBox = context.findRenderObject() as RenderBox; final renderObject = context.findRenderObject();
final overlay = Overlay.maybeOf(context);
if (renderObject is! RenderBox ||
!renderObject.attached ||
overlay == null) {
return;
}
final renderBox = renderObject;
Offset offset = renderBox.localToGlobal(Offset.zero); Offset offset = renderBox.localToGlobal(Offset.zero);
bool isRtl = appState.getLanguageCode() == "ar"; bool isRtl = appState.getLanguageCode() == "ar";
double leftPosition; double leftPosition;
@ -169,7 +219,7 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
leftPosition = offset.dx; leftPosition = offset.dx;
} }
_overlayEntry = OverlayEntry( final overlayEntry = OverlayEntry(
builder: (context) => Stack( builder: (context) => Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
@ -185,32 +235,41 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
width: !widget.isFromBottomSheet ? renderBox.size.width : 60.h, width: !widget.isFromBottomSheet ? renderBox.size.width : 60.h,
child: Material( child: Material(
child: Container( child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 12), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: Colors.white, borderRadius: 12),
child: Column( child: Column(
children: widget.countryList children: widget.countryList
.map( .map(
(country) => GestureDetector( (country) => GestureDetector(
onTap: () { onTap: () => _selectCountry(country),
setState(() { child: Container(
selectedCountry = country; padding: EdgeInsets.symmetric(
}); vertical: 8.h, horizontal: 8.h),
widget.onCountryChange?.call(country); decoration: RoundedRectangleBorder()
_closeDropdown(); .toSmoothCornerDecoration(borderRadius: 16.h),
}, child: Row(
child: Container( children: [
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), Utils.buildSvgWithAssets(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 16.h), icon: country.iconPath,
child: Row( width: 38.h,
children: [ height: 38.h,
Utils.buildSvgWithAssets(icon: country.iconPath, width: 38.h, height: 38.h, applyThemeColor: false), applyThemeColor: false),
if (!widget.isFromBottomSheet) SizedBox(width: 12.h), if (!widget.isFromBottomSheet)
if (!widget.isFromBottomSheet) SizedBox(width: 12.h),
Text(appState.getLanguageCode() == "ar" ? country.nameArabic : country.displayName, if (!widget.isFromBottomSheet)
style: TextStyle(fontSize: 14.f, height: 21 / 14, fontWeight: FontWeight.w600, letterSpacing: -0.2)), Text(
], appState.getLanguageCode() == "ar"
), ? country.nameArabic
)), : country.displayName,
) style: TextStyle(
fontSize: 14.f,
height: 21 / 14,
fontWeight: FontWeight.w600,
letterSpacing: -0.2)),
],
),
)),
)
.toList(), .toList(),
), ),
), ),
@ -220,7 +279,8 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
), ),
); );
Overlay.of(context)?.insert(_overlayEntry); overlay.insert(overlayEntry);
_overlayEntry = overlayEntry;
setState(() { setState(() {
_isDropdownOpen = true; _isDropdownOpen = true;
}); });
@ -291,18 +351,61 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
// }); // });
// } // }
void _closeDropdown() { void _selectCountry(CountryEnum country) {
_overlayEntry.remove(); if (!mounted) {
_removeOverlayEntry();
return;
}
_removeOverlayEntry();
setState(() { setState(() {
selectedCountry = country;
_isDropdownOpen = false; _isDropdownOpen = false;
}); });
// Notify the parent only after this widget has finished updating and
// removing its overlay. The callback may synchronously close the parent.
widget.onCountryChange?.call(country);
_scheduleTextFieldRefocus();
}
void _closeDropdown() {
_showDropdownTimer?.cancel();
_showDropdownTimer = null;
_removeOverlayEntry();
if (!mounted) {
_isDropdownOpen = false;
return;
}
if (_isDropdownOpen) {
setState(() {
_isDropdownOpen = false;
});
}
_scheduleTextFieldRefocus();
}
void _removeOverlayEntry() {
final overlayEntry = _overlayEntry;
_overlayEntry = null;
if (overlayEntry == null) return;
overlayEntry.remove();
overlayEntry.dispose();
}
void _scheduleTextFieldRefocus() {
_refocusTimer?.cancel();
if (widget.isEnableTextField && widget.isFromBottomSheet) { if (widget.isEnableTextField && widget.isFromBottomSheet) {
Future.delayed(Duration(milliseconds: 100), () { _refocusTimer = Timer(const Duration(milliseconds: 100), () {
_refocusTimer = null;
if (mounted && textFocusNode.canRequestFocus) { if (mounted && textFocusNode.canRequestFocus) {
FocusScope.of(context).requestFocus(textFocusNode); FocusScope.of(context).requestFocus(textFocusNode);
} }
}); });
} }
} }
} }

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

Loading…
Cancel
Save