diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 9fb7050a..865b7e75 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -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'; diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 4d181230..a26995b1 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -1200,8 +1200,8 @@ class _AppointmentDetailsPageState extends State { 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); diff --git a/lib/widgets/dropdown/country_dropdown_widget.dart b/lib/widgets/dropdown/country_dropdown_widget.dart index c1ef39f1..44869e58 100644 --- a/lib/widgets/dropdown/country_dropdown_widget.dart +++ b/lib/widgets/dropdown/country_dropdown_widget.dart @@ -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 countryList; @@ -35,7 +36,9 @@ class CustomCountryDropdown extends StatefulWidget { class CustomCountryDropdownState extends State { 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 { @override void dispose() { + _showDropdownTimer?.cancel(); + _refocusTimer?.cancel(); + _removeOverlayEntry(); textFocusNode.dispose(); super.dispose(); } @@ -74,9 +80,18 @@ class CustomCountryDropdownState extends State { }, 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 { 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 { 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 { 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, @@ -136,21 +166,31 @@ class CustomCountryDropdownState extends State { Text( selectedCountry != null ? appState.getLanguageCode() == "ar" - ? selectedCountry!.nameArabic - : selectedCountry!.displayName + ? 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), () { - _showDropdown(); + _showDropdownTimer = Timer(const Duration(milliseconds: 300), () { + _showDropdownTimer = null; + if (mounted) { + _showDropdown(); + } }); } else { _showDropdown(); @@ -158,8 +198,18 @@ class CustomCountryDropdownState extends State { } void _showDropdown() { + if (!mounted || _isDropdownOpen || _overlayEntry != null) return; + AppState appState = getIt.get(); - 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 { leftPosition = offset.dx; } - _overlayEntry = OverlayEntry( + final overlayEntry = OverlayEntry( builder: (context) => Stack( children: [ Positioned.fill( @@ -185,32 +235,41 @@ class CustomCountryDropdownState extends State { 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(); - }, - child: Container( - 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), - 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)), - ], - ), - )), - ) + onTap: () => _selectCountry(country), + child: Container( + 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), + 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)), + ], + ), + )), + ) .toList(), ), ), @@ -220,7 +279,8 @@ class CustomCountryDropdownState extends State { ), ); - Overlay.of(context)?.insert(_overlayEntry); + overlay.insert(overlayEntry); + _overlayEntry = overlayEntry; setState(() { _isDropdownOpen = true; }); @@ -291,18 +351,61 @@ class CustomCountryDropdownState extends State { // }); // } - void _closeDropdown() { - _overlayEntry.remove(); + 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() { + _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); } }); } } -} +} \ No newline at end of file