haroon_dev #359

Merged
Haroon6138 merged 7 commits from haroon_dev into master 6 days ago

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

@ -86,7 +86,7 @@
<key>NSMicrophoneUsageDescription</key>
<string>This app requires microphone access to enable virtual consultation between patient &amp; doctor</string>
<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>
<string>This app requires photo library access to select image as document &amp; upload it.</string>
<key>NSPhotoLibraryAddUsageDescription</key>

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

@ -5,7 +5,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts {
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
@ -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 CANCEL_PHARMA_LIVECARE_REQUEST = 'https://vcallapi.hmg.com/api/PharmaLiveCare/SendPaymentStatus';
var INSERT_FREE_SLOTS_LOGS = 'Services/Doctors.svc/Rest/InsertDoctorFreeSlotsLogs';
var GET_NATIONALITY = 'Services/Lists.svc/REST/GetNationality';

@ -40,14 +40,23 @@ class RequestUtils {
if (zipCode == "0") {
request.patientMobileNumberOthers = phoneNumber;
} 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.zipCode = zipCode; // or countryCode if defined elsewhere
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.isHijri = calenderType.toInt;
request.patientID = patientId;
@ -56,7 +65,7 @@ class RequestUtils {
request.isDentalAllowedBackend = false;
} else {
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.searchType = 2;
} else {
@ -91,8 +100,17 @@ class RequestUtils {
request.patientMobileNumberOthers = phoneNumber;
request.mobileNo = phoneNumber;
} else {
request.patientMobileNumber = int.parse(phoneNumber);
request.mobileNo = '0$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;
request.mobileNo = '0$numericPhone';
} else {
// If parsing fails, use as string in Others field
request.patientMobileNumberOthers = phoneNumber;
request.mobileNo = phoneNumber;
}
}
request.deviceToken = deviceToken;
request.projectOutSA = patientOutSA;
@ -115,7 +133,8 @@ class RequestUtils {
log("nationIdText: ${nationIdText}");
} else {
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.searchType = 2;
//TODO: Issue HEre is Not Login
@ -155,8 +174,17 @@ class RequestUtils {
request.patientMobileNumberOthers = mobileNumber;
request.mobileNo = mobileNumber;
} else {
request.patientMobileNumber = int.parse(mobileNumber);
request.mobileNo = '0$mobileNumber';
// Remove any non-numeric characters before parsing
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;
@ -314,7 +342,9 @@ class RequestUtils {
request.sharedPatientId = 0;
request.sharedPatientIdentificationId = nationalIDorFile;
} 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.searchType = loginType;

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

@ -56,6 +56,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
bool isAppointmentDataToBeLoaded = true;
bool isMyDoctorsDataToBeLoaded = true;
bool isArrivedAppointmentDataLoaded = false;
bool isFullArrivedAppointmentsLoaded = false;
bool isEyeMeasurementsAppointmentsLoading = false;
@ -77,7 +78,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
List<PatientAppointmentHistoryResponseModel> patientUpcomingAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientArrivedAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientAllArrivedAppointmentsHistoryList = [];
// List<PatientAppointmentHistoryResponseModel> patientAllArrivedAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientMyDoctorsList = [];
@ -173,11 +174,12 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
patientArrivedAppointmentsHistoryList.clear();
patientAllArrivedAppointmentsHistoryList.clear();
// patientAllArrivedAppointmentsHistoryList.clear();
patientEyeMeasurementsAppointmentsHistoryList.clear();
isMyAppointmentsLoading = true;
isTimeLineAppointmentsLoading = true;
patientMyDoctorsList.clear();
isFullArrivedAppointmentsLoaded = false;
}
isTamaraDetailsLoading = true;
isAppointmentPatientShareLoading = true;
@ -276,13 +278,19 @@ class MyAppointmentsViewModel extends ChangeNotifier {
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;
patientAppointmentsByClinic.clear();
patientAppointmentsByHospital.clear();
patientAppointmentsViewList.clear();
patientAllArrivedAppointmentsHistoryList.clear();
// patientAllArrivedAppointmentsHistoryList.clear();
filteredAppointmentList.clear();
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
@ -321,8 +329,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
isMyAppointmentsLoading = false;
isAppointmentDataToBeLoaded = false;
if (!isForTimeLine) {
patientAllArrivedAppointmentsHistoryList = apiResponse.data!;
// patientAllArrivedAppointmentsHistoryList = apiResponse.data!;
isArrivedAppointmentDataLoaded = true;
isFullArrivedAppointmentsLoaded = true;
}
notifyListeners();
if (onSuccess != null) {
@ -354,6 +363,69 @@ class MyAppointmentsViewModel extends ChangeNotifier {
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) {
availableFilters.clear();
if (filteredAppointmentList.isEmpty == true) return;
@ -362,9 +434,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
availableFilters.add(AppointmentListingFilters.LIVECARE);
}
if (filteredAppointmentList.any((element) => element.isLiveCareAppointment == false)) {
availableFilters.add(AppointmentListingFilters.WALKIN);
}
// if (filteredAppointmentList.any((element) => element.isLiveCareAppointment == false)) {
// availableFilters.add(AppointmentListingFilters.WALKIN);
// }
if (filteredAppointmentList.any((element) => AppointmentType.isArrived(element) == true)) {
availableFilters.add(AppointmentListingFilters.ARRIVED);
@ -801,15 +873,15 @@ class MyAppointmentsViewModel extends ChangeNotifier {
selectedFilter = [];
// if(previouslySelectedTab == selectedTabIndex ) return;
switch (index) {
// case 0:
// filteredAppointmentList.clear();
// filteredAppointmentList.addAll(patientAppointmentsHistoryList);
// break;
case 0:
filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientAppointmentsHistoryList);
break;
case 1:
filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientUpcomingAppointmentsHistoryList);
break;
case 2:
case 1:
filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientArrivedAppointmentsHistoryList);
break;
@ -835,11 +907,12 @@ class MyAppointmentsViewModel extends ChangeNotifier {
this.end = end;
isDateFilterSelected = true;
List<PatientAppointmentHistoryResponseModel> sourceList = [];
// if (selectedTabIndex == 0) {
// sourceList = patientAppointmentsHistoryList;
// } else
if (selectedTabIndex == 0) {
sourceList = patientAppointmentsHistoryList;
} else if (selectedTabIndex == 1) {
sourceList = patientUpcomingAppointmentsHistoryList;
} else if (selectedTabIndex == 2) {
} else if (selectedTabIndex == 1) {
sourceList = patientArrivedAppointmentsHistoryList;
}
// if (isDateFilterSelected) sourceList = filteredAppointmentList;

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

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

@ -69,14 +69,20 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
widget.patientAppointmentHistoryResponseModel.clinicID,
widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onSuccess: (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! &&
myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) {
if (mounted) {
setState(() {
isShowTamara = true;
});
}
}
}
});
}, onError: (err) {
Navigator.of(context).pop();

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

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

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

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

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

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

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

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

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

@ -257,8 +257,7 @@ void showCommonBottomSheetWithoutHeight(
duration: Duration(milliseconds: 500),
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,
isScrollControlled: true,
showDragHandle: false,

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'dart:ui' as ui;
class CustomCountryDropdown extends StatefulWidget {
final List<CountryEnum> countryList;
@ -35,7 +36,9 @@ class CustomCountryDropdown extends StatefulWidget {
class CustomCountryDropdownState extends State<CustomCountryDropdown> {
CountryEnum? selectedCountry;
late OverlayEntry _overlayEntry;
OverlayEntry? _overlayEntry;
Timer? _showDropdownTimer;
Timer? _refocusTimer;
bool _isDropdownOpen = false;
FocusNode textFocusNode = FocusNode();
@ -55,6 +58,9 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
@override
void dispose() {
_showDropdownTimer?.cancel();
_refocusTimer?.cancel();
_removeOverlayEntry();
textFocusNode.dispose();
super.dispose();
}
@ -74,9 +80,18 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
},
child: Row(
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),
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: [
Text(
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(
mainAxisAlignment: MainAxisAlignment.start,
@ -102,9 +121,15 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
if (selectedCountry != CountryEnum.others)
Text(
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)
SizedBox(
height: 20.h,
@ -113,14 +138,19 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
alignment: Alignment.centerLeft,
child: TextField(
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(
hintText: selectedCountry == CountryEnum.others ? "001*******" : "",
hintText: selectedCountry == CountryEnum.others
? "001*******"
: "",
isDense: true,
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
keyboardType: TextInputType.phone,
onChanged: widget.onPhoneNumberChanged,
@ -139,18 +169,28 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
? selectedCountry!.nameArabic
: selectedCountry!.displayName
: 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() {
if (!mounted || _isDropdownOpen || _overlayEntry != null) return;
_showDropdownTimer?.cancel();
if (textFocusNode.hasFocus) {
textFocusNode.unfocus();
// Wait for keyboard to close before calculating position
Future.delayed(Duration(milliseconds: 300), () {
_showDropdownTimer = Timer(const Duration(milliseconds: 300), () {
_showDropdownTimer = null;
if (mounted) {
_showDropdown();
}
});
} else {
_showDropdown();
@ -158,8 +198,18 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
}
void _showDropdown() {
if (!mounted || _isDropdownOpen || _overlayEntry != null) return;
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);
bool isRtl = appState.getLanguageCode() == "ar";
double leftPosition;
@ -169,7 +219,7 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
leftPosition = offset.dx;
}
_overlayEntry = OverlayEntry(
final overlayEntry = OverlayEntry(
builder: (context) => Stack(
children: [
Positioned.fill(
@ -185,28 +235,37 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
width: !widget.isFromBottomSheet ? renderBox.size.width : 60.h,
child: Material(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 12),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: Colors.white, borderRadius: 12),
child: Column(
children: widget.countryList
.map(
(country) => GestureDetector(
onTap: () {
setState(() {
selectedCountry = country;
});
widget.onCountryChange?.call(country);
_closeDropdown();
},
onTap: () => _selectCountry(country),
child: Container(
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 16.h),
padding: EdgeInsets.symmetric(
vertical: 8.h, horizontal: 8.h),
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(borderRadius: 16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(icon: country.iconPath, width: 38.h, height: 38.h, applyThemeColor: false),
if (!widget.isFromBottomSheet) SizedBox(width: 12.h),
Utils.buildSvgWithAssets(
icon: country.iconPath,
width: 38.h,
height: 38.h,
applyThemeColor: false),
if (!widget.isFromBottomSheet)
SizedBox(width: 12.h),
if (!widget.isFromBottomSheet)
Text(appState.getLanguageCode() == "ar" ? country.nameArabic : country.displayName,
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)),
],
),
)),
@ -220,7 +279,8 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
),
);
Overlay.of(context)?.insert(_overlayEntry);
overlay.insert(overlayEntry);
_overlayEntry = overlayEntry;
setState(() {
_isDropdownOpen = true;
});
@ -291,14 +351,57 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
// });
// }
void _selectCountry(CountryEnum country) {
if (!mounted) {
_removeOverlayEntry();
return;
}
_removeOverlayEntry();
setState(() {
selectedCountry = country;
_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() {
_overlayEntry.remove();
_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) {
Future.delayed(Duration(milliseconds: 100), () {
_refocusTimer = Timer(const Duration(milliseconds: 100), () {
_refocusTimer = null;
if (mounted && textFocusNode.canRequestFocus) {
FocusScope.of(context).requestFocus(textFocusNode);
}

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

Loading…
Cancel
Save