From 71030c0fe9fb579ace6957665822c48f62bda6e8 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 8 Apr 2026 00:27:43 +0300 Subject: [PATCH] Updates & fixes --- .../book_appointments_view_model.dart | 10 ++- ...nt_appointment_history_response_model.dart | 3 + .../appointments/my_doctors_page.dart | 2 +- .../widgets/appointment_doctor_card.dart | 4 +- .../book_appointment_page.dart | 4 +- .../book_appointment/doctor_profile_page.dart | 8 +- .../immediate_livecare_payment_details.dart | 2 +- .../search_doctor_by_name.dart | 2 +- .../book_appointment/select_doctor_page.dart | 5 +- .../call_ambulance/call_ambulance_page.dart | 4 +- .../medical_file/medical_file_page.dart | 2 +- lib/widgets/map/gms_map.dart | 81 +++++++++++++++++-- lib/widgets/map/map_utility_screen.dart | 2 +- 13 files changed, 106 insertions(+), 23 deletions(-) diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index e43a49d5..6f2092f9 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -400,9 +400,13 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + refreshDoctorsList() { + setIsDoctorsListLoading(true); + getDoctorsList(isNearest: isNearestAppointmentSelected); + } + setIsNearestAppointmentSelected(bool isNearestAppointmentSelected) { this.isNearestAppointmentSelected = isNearestAppointmentSelected; - if (isNearestAppointmentSelected) { for (var group in doctorsListGrouped) { group.sort((a, b) { @@ -646,7 +650,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { } //TODO: Make the API dynamic with parameters for ProjectID, isNearest, languageID, doctorId, doctorName - Future getDoctorsList({int projectID = 0, bool isNearest = true, int doctorId = 0, String doctorName = "", Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future getDoctorsList({int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", Function(dynamic)? onSuccess, Function(String)? onError}) async { doctorsList.clear(); filteredDoctorList.clear(); doctorsListGrouped.clear(); @@ -674,7 +678,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { clearSearchFilters(); getFiltersFromDoctorList(); _groupDoctorsList(); - setIsNearestAppointmentSelected(true); + setIsNearestAppointmentSelected(isNearest); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart b/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart index c0da3c47..cea2e64b 100644 --- a/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart +++ b/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart @@ -51,6 +51,7 @@ class PatientAppointmentHistoryResponseModel { bool? isExecludeDoctor; dynamic isFollowup; bool? isLiveCareAppointment; + bool? isLiveCareClinic; bool? isInOutPatient; bool? isMedicalReportRequested; bool? isOnlineCheckedIN; @@ -128,6 +129,7 @@ class PatientAppointmentHistoryResponseModel { this.isExecludeDoctor, this.isFollowup, this.isLiveCareAppointment, + this.isLiveCareClinic, this.isMedicalReportRequested, this.isOnlineCheckedIN, this.latitude, @@ -205,6 +207,7 @@ class PatientAppointmentHistoryResponseModel { isExecludeDoctor = json['IsExecludeDoctor']; isFollowup = json['IsFollowup']; isLiveCareAppointment = json['IsLiveCareAppointment']; + isLiveCareClinic = json['IsLiveCareClinic']; isInOutPatient = json['IsInOutPatient']; isMedicalReportRequested = json['IsMedicalReportRequested']; isOnlineCheckedIN = json['IsOnlineCheckedIN']; diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart index 1a98964b..93733770 100644 --- a/lib/presentation/appointments/my_doctors_page.dart +++ b/lib/presentation/appointments/my_doctors_page.dart @@ -318,7 +318,7 @@ class _MyDoctorsPageState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage(isDoctorAllowedToBook: !doctor?.isLiveCareClinic), ), ); }, onError: (err) { diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 99f40b67..5de06455 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -161,7 +161,9 @@ class AppointmentDoctorCard extends StatelessWidget { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage( + isDoctorAllowedToBook: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false), + ), ), ); }, onError: (err) { diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 3d82387b..bbc88214 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -217,7 +217,7 @@ class _BookAppointmentPageState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage(isDoctorAllowedToBook: !(myAppointmentsVM.patientMyDoctorsList[index].isLiveCareClinic ?? false)), ), ); }, onError: (err) { @@ -406,7 +406,7 @@ class _BookAppointmentPageState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage(isDoctorAllowedToBook: true), ), ); }, onError: (err) { diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index 713ee2c0..dbb885b3 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -22,7 +22,9 @@ import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:provider/provider.dart'; class DoctorProfilePage extends StatelessWidget { - const DoctorProfilePage({super.key}); + const DoctorProfilePage({super.key, required this.isDoctorAllowedToBook}); + + final bool isDoctorAllowedToBook; @override Widget build(BuildContext context) { @@ -187,7 +189,7 @@ class DoctorProfilePage extends StatelessWidget { ), ), ), - Container( + isDoctorAllowedToBook ? Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.h, @@ -265,7 +267,7 @@ class DoctorProfilePage extends StatelessWidget { iconColor: Colors.white, iconSize: 20.h, ).paddingSymmetrical(24.h, 24.h), - ), + ) : SizedBox.shrink(), ], ), ); diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart index 29ac4a5d..5f44448a 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -214,7 +214,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), - Utils.getPaymentAmountWithSymbol(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.amount!.toText16(isBold: true, isEnglishOnly: true), AppColors.blackColor, 13, + Utils.getPaymentAmountWithSymbol((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.amount ?? "").toText16(isBold: true, isEnglishOnly: true), AppColors.blackColor, 13, isSaudiCurrency: immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال"), ], diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index e2ab6808..3222fbd8 100644 --- a/lib/presentation/book_appointment/search_doctor_by_name.dart +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -218,7 +218,7 @@ class _SearchDoctorByNameState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage(isDoctorAllowedToBook: true), ), ); }, diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 6c3427e7..98cc550b 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -239,6 +239,9 @@ class _SelectDoctorPageState extends State { value: bookAppointmentsVM.isNearestAppointmentSelected, onChanged: (newValue) async { bookAppointmentsVM.setIsNearestAppointmentSelected(newValue); + if(newValue) { + bookAppointmentsVM.refreshDoctorsList(); + } }, ), ], @@ -346,7 +349,7 @@ class _SelectDoctorPageState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage(isDoctorAllowedToBook: true,), ), ); }, onError: (err) { diff --git a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart index a1b1839f..55958048 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -67,7 +69,7 @@ class CallAmbulancePage extends StatelessWidget { myLocationEnabled: true, inputController: context.read().gmsController, showCenterMarker: true, - bottomPaddingHeight: MediaQuery.of(context).size.height * 0.42, + bottomPaddingHeight: 400.h, ) else HMSMap( diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index e089c057..d04f2ac8 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -1019,7 +1019,7 @@ class _MedicalFilePageState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage(isDoctorAllowedToBook: !(myAppointmentsVM.patientMyDoctorsList[index].isLiveCareClinic ?? false)), ), ); }, onError: (err) { diff --git a/lib/widgets/map/gms_map.dart b/lib/widgets/map/gms_map.dart index 24f180cd..1d06fb10 100644 --- a/lib/widgets/map/gms_map.dart +++ b/lib/widgets/map/gms_map.dart @@ -54,10 +54,16 @@ // } import 'dart:async'; +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -72,7 +78,7 @@ class GMSMap extends StatefulWidget { final bool myLocationEnabled; final bool showCenterMarker; final Completer? inputController; - final num bottomPaddingHeight; + final double bottomPaddingHeight; const GMSMap({ super.key, @@ -93,6 +99,7 @@ class GMSMap extends StatefulWidget { class _GMSMapState extends State { late Completer _controller; + GoogleMapController? _activeController; late MapType _selectedMapType; bool _showMapTypeSelector = false; num bottomPaddingHeight = 0; @@ -139,6 +146,37 @@ class _GMSMapState extends State { setState(() => _showMapTypeSelector = !_showMapTypeSelector); } + Future _animateToMyLocation() async { + final appState = getIt.get(); + final locationUtils = getIt.get(); + + void _moveToLocation() { + if (appState.userLat != 0.0 && appState.userLong != 0.0 && _activeController != null) { + _activeController!.animateCamera( + CameraUpdate.newCameraPosition( + CameraPosition( + target: LatLng(appState.userLat, appState.userLong), + zoom: 16, + ), + ), + ); + } + } + + if (appState.userLat != 0.0 && appState.userLong != 0.0) { + _moveToLocation(); + } else { + locationUtils.getLocation( + isShowConfirmDialog: true, + onSuccess: (latLng) { + _moveToLocation(); + }, + onFailure: () {}, + onLocationDeniedForever: () {}, + ); + } + } + @override Widget build(BuildContext context) { return Stack( @@ -146,16 +184,21 @@ class _GMSMapState extends State { // ── Google Map ────────────────────────────────────────────────── GoogleMap( mapType: _selectedMapType, - zoomControlsEnabled: true, - myLocationEnabled: widget.myLocationEnabled, - myLocationButtonEnabled: true, - padding: EdgeInsets.only(bottom: double.parse(widget.bottomPaddingHeight.toString())), + zoomControlsEnabled: false, + // myLocationEnabled: widget.myLocationEnabled, + myLocationEnabled: true, + myLocationButtonEnabled: false, + // padding: EdgeInsets.only(bottom: double.parse(widget.bottomPaddingHeight.toString())), + // padding: EdgeInsets.only(top: Platform.isAndroid ? double.parse(widget.bottomPaddingHeight.toString()) : 0, bottom: Platform.isIOS ? double.parse(widget.bottomPaddingHeight.toString()) : 0), compassEnabled: widget.compassEnabled, initialCameraPosition: widget.currentLocation, onCameraMove: widget.onCameraMoved, onCameraIdle: widget.onCameraIdle, onMapCreated: (GoogleMapController controller) { - _controller.complete(controller); + _activeController = controller; + if (!_controller.isCompleted) { + _controller.complete(controller); + } }, ), @@ -167,9 +210,33 @@ class _GMSMapState extends State { Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor, - ).paddingOnly(bottom: double.parse((widget.bottomPaddingHeight + 25.h).toString())), + ).paddingOnly(bottom: Platform.isAndroid ? 30.h : double.parse((widget.bottomPaddingHeight + 25.h).toString())), ), + // ── My Location button ──────────────────────────────────────────── + PositionedDirectional( + // bottom: 300.h, + // bottom: 400.h, + bottom: widget.bottomPaddingHeight, + start: 16.w, + child: GestureDetector( + onTap: _animateToMyLocation, + child: Container( + width: 42.w, + height: 42.h, + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.my_location, + size: 22.h, + color: AppColors.blackBgColor, + ), + ), + ), + ), + // ── Map-type toggle button ────────────────────────────────────── PositionedDirectional( top: 48.h, diff --git a/lib/widgets/map/map_utility_screen.dart b/lib/widgets/map/map_utility_screen.dart index 6c1d9e89..a24317e6 100644 --- a/lib/widgets/map/map_utility_screen.dart +++ b/lib/widgets/map/map_utility_screen.dart @@ -68,7 +68,7 @@ class MapUtilityScreen extends StatelessWidget { myLocationEnabled: true, inputController: context.read().gmsController, showCenterMarker: true, - bottomPaddingHeight: MediaQuery.of(context).size.height * 0.2, + bottomPaddingHeight: 300.h, ) else HMSMap(