From 246d414fe16a700749eebee4db45b688ab73b27d Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Sat, 18 Apr 2026 13:31:10 +0300 Subject: [PATCH] updates and fixes --- assets/langs/ar-SA.json | 10 +- assets/langs/en-US.json | 10 +- lib/core/cache_consts.dart | 1 + .../e_referral/new_e_referral.dart | 6 +- .../hmg_services/services_page.dart | 4 +- .../hmg_services/services_view.dart | 1 + .../medical_file_appointment_card.dart | 182 ++++++++---- .../organ_selector_screen.dart | 84 ++++-- .../widgets/pinch_zoom_tutorial_overlay.dart | 261 ++++++++++++++++++ 9 files changed, 479 insertions(+), 80 deletions(-) create mode 100644 lib/presentation/symptoms_checker/widgets/pinch_zoom_tutorial_overlay.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 302c188e..b9e8d8fc 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1810,5 +1810,13 @@ "watchUsOnYoutube": "شاهدنا على إنستغرام", "connectOnLinkedin": "تواصل معنا على لينكد إن", "riskFactorPageTitle": "اعراض تشعر بها", - "symptomsCheckSuggestions": "الاحتمالات" + "symptomsCheckSuggestions": "الاحتمالات", + "bloodDonationService": "التبرع \n بالدم", + "bookAppointmentService": "حجز\nموعد", + "pinchToZoom": "اضغط للتكبير", + "gotIt": "فهمت", + "zoomIntoDetails": "قم بالتكبير لرؤية التفاصيل", + "pinchWithTwoFingersToZoom": "اضغط بإصبعين للتكبير", + "dragToMoveAround": "اسحب للتحرك", + "startExploring": "ابدأ الاستكشاف" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 3c9facad..e16f310e 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1800,7 +1800,15 @@ "watchUsOnYoutube": "Watch us on Instagram", "connectOnLinkedin": "Connect on LinkedIn", "riskFactorPageTitle": "What do you feel?", - "symptomsCheckSuggestions": "Suggestions" + "symptomsCheckSuggestions": "Suggestions", + "bloodDonationService": "Blood Donation", + "bookAppointmentService": "Book\nAppointment", + "pinchToZoom": "Pinch to zoom", + "gotIt": "Got it", + "zoomIntoDetails": "Zoom into details", + "pinchWithTwoFingersToZoom": "Pinch with two fingers to zoom", + "dragToMoveAround": "Drag to move around", + "startExploring": "Start exploring" } diff --git a/lib/core/cache_consts.dart b/lib/core/cache_consts.dart index c829adbf..118fed3b 100644 --- a/lib/core/cache_consts.dart +++ b/lib/core/cache_consts.dart @@ -80,4 +80,5 @@ class CacheConst { static const String zoomRoomID = 'zoom-room-id'; static const String callTypeID = 'call-type-id'; static String isAppOpenedFromCall = "is_app_opened_from_call"; + static const String organSelectorTutorialShown = 'organ_selector_tutorial_shown'; } diff --git a/lib/presentation/e_referral/new_e_referral.dart b/lib/presentation/e_referral/new_e_referral.dart index 53c91680..1d34b38c 100644 --- a/lib/presentation/e_referral/new_e_referral.dart +++ b/lib/presentation/e_referral/new_e_referral.dart @@ -131,9 +131,13 @@ class _NewReferralPageState extends State { hmgServicesVM.createEReferral( requestModel: createReferralRequestModel, - onSuccess: (GenericApiModel response) { + onSuccess: (GenericApiModel response) async{ LoaderBottomSheet.hideLoader(); showSuccessBottomSheet(int.parse(response.data), hmgServicesVM); + await Future.delayed(Duration(milliseconds: 2000)).then((value) { + if (mounted) Navigator.pop(context); + }); + }, onError: (errorMessage) { // Handle error (e.g., show error message) diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 9ad0fa31..be1ea43e 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -88,7 +88,7 @@ class _ServicesPageState extends State { // await getIt.get().onLoginPressed(); // } }), - HmgServicesComponentModel(11, LocaleKeys.bookAppointment.tr(), "", AppAssets.appointment_calendar_icon, bgColor: AppColors.bookAppointment, true, route: null, onTap: () { + HmgServicesComponentModel(11, LocaleKeys.bookAppointmentService.tr(), "", AppAssets.appointment_calendar_icon, bgColor: AppColors.bookAppointment, true, route: null, onTap: () { getIt.get().onTabChanged(0); Navigator.of(getIt().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage())); }), @@ -169,7 +169,7 @@ class _ServicesPageState extends State { }), HmgServicesComponentModel( 3, - LocaleKeys.bloodDonation.tr(context: getIt.get().navigatorKey.currentContext!), + LocaleKeys.bloodDonationService.tr(context: getIt.get().navigatorKey.currentContext!), "".needTranslation, AppAssets.blood_donation_icon, bgColor: AppColors.bloodDonationCardColor, diff --git a/lib/presentation/hmg_services/services_view.dart b/lib/presentation/hmg_services/services_view.dart index 7192ce23..906c23f6 100644 --- a/lib/presentation/hmg_services/services_view.dart +++ b/lib/presentation/hmg_services/services_view.dart @@ -57,6 +57,7 @@ class ServiceGridViewItem extends StatelessWidget { isBold: true, color: AppColors.textColor, maxLine: 2, + ), ], )); diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart index a67a6765..393dc3f6 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -178,13 +178,15 @@ class _MedicalFileAppointmentCardState extends State ], ), SizedBox(height: 12.h), - Row( - children: [ - widget.myAppointmentsViewModel.isMyAppointmentsLoading - ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) - : Expanded( - flex: 7, - child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + // Check if doctor is active - if not, show only View Details button + (widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) + ? // Doctor is active - check rebooking logic + (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) && + widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) + ? // Show only the button without arrow when rebooking not allowed + widget.myAppointmentsViewModel.isMyAppointmentsLoading + ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) + : AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? getArrivedAppointmentButton(context).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) : CustomButton( text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), @@ -202,51 +204,101 @@ class _MedicalFileAppointmentCardState extends State icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), iconSize: 14.h, - ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), - ), - SizedBox(width: 8.w), - ((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || - (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || - !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) && - AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) - ? SizedBox.shrink() - : Expanded( - flex: 2, - child: Container( - height: 40.h, - width: 40.w, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.textColor, - borderRadius: 10.r, - ), - child: Padding( - padding: EdgeInsets.all(10.w), - child: Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - iconColor: AppColors.whiteColor, - icon: AppAssets.forward_arrow_icon_small, - width: 40.w, - height: 40.h, - fit: BoxFit.contain, + ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) + : // Normal flow - show button with arrow + Row( + children: [ + widget.myAppointmentsViewModel.isMyAppointmentsLoading + ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) + : Expanded( + flex: 7, + child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? getArrivedAppointmentButton(context).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) + : CustomButton( + text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), + onPressed: () { + handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context); + }, + backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), + textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + iconSize: 14.h, + ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), + ), + SizedBox(width: 8.w), + ((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || + (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) && + AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) + ? SizedBox.shrink() + : Expanded( + flex: 2, + child: Container( + height: 40.h, + width: 40.w, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 10.r, + ), + child: Padding( + padding: EdgeInsets.all(10.w), + child: Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + iconColor: AppColors.whiteColor, + icon: AppAssets.forward_arrow_icon_small, + width: 40.w, + height: 40.h, + fit: BoxFit.contain, + ), + ), + ), + ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading).onPress(() { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ) + .then((val) { + // widget.myAppointmentsViewModel.initAppointmentsViewModel(); + // widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }), + ), + ], + ) + : // Doctor is not active - show only View Details button + CustomButton( + text: LocaleKeys.viewDetails.tr(context: context), + onPressed: () { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), - ), - ), - ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading).onPress(() { - Navigator.of(context) - .push( - CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - ), - ) - .then((val) { - // widget.myAppointmentsViewModel.initAppointmentsViewModel(); - // widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }), - ), - ], - ), + ) + .then((_) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), ], ).paddingAll(16.w), ), @@ -255,6 +307,34 @@ class _MedicalFileAppointmentCardState extends State } Widget getArrivedAppointmentButton(BuildContext context) { + // Check if rebooking is not allowed - show View Details button + if (widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) { + return CustomButton( + text: LocaleKeys.viewDetails.tr(context: context), + onPressed: () { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ) + .then((_) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + ); + } + + // Check if clinic allows rebooking or if doctor is excluded if ((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) && diff --git a/lib/presentation/symptoms_checker/organ_selector_screen.dart b/lib/presentation/symptoms_checker/organ_selector_screen.dart index ea85b3b5..93e826df 100644 --- a/lib/presentation/symptoms_checker/organ_selector_screen.dart +++ b/lib/presentation/symptoms_checker/organ_selector_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -11,6 +12,8 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/interactive_body_widget.dart'; +import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/pinch_zoom_tutorial_overlay.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -28,12 +31,36 @@ class OrganSelectorPage extends StatefulWidget { class _OrganSelectorPageState extends State { late final AppState _appState; late final DialogService dialogService; + late final CacheService cacheService; + bool _showTutorial = false; @override void initState() { super.initState(); _appState = getIt.get(); dialogService = getIt(); + cacheService = getIt(); + _checkAndShowTutorial(); + } + + Future _checkAndShowTutorial() async { + final hasSeenTutorial = cacheService.getBool(key: CacheConst.organSelectorTutorialShown) ?? false; + if (!hasSeenTutorial) { + // Show tutorial after a short delay to ensure the screen is fully built + await Future.delayed(const Duration(milliseconds: 500)); + if (mounted) { + setState(() { + _showTutorial = true; + }); + } + } + } + + void _onTutorialComplete() async { + await cacheService.saveBool(key: CacheConst.organSelectorTutorialShown, value: true); + setState(() { + _showTutorial = false; + }); } void _onNextPressed(SymptomsCheckerViewModel viewModel) async { @@ -65,30 +92,39 @@ class _OrganSelectorPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - appBar: _buildAppBar(), - body: Consumer( - builder: (context, viewModel, _) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildTitle(), - SizedBox(height: 8.h), - Expanded( - child: Stack( - children: [ - _buildBodyViewer(viewModel), - _buildViewToggleButtons(viewModel), - _buildViewZoomButtons(viewModel), - _buildBottomSheet(viewModel), - ], - ), - ), - ], - ); - }, - ), + return Stack( + children: [ + Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: _buildAppBar(), + body: Consumer( + builder: (context, viewModel, _) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTitle(), + SizedBox(height: 8.h), + Expanded( + child: Stack( + children: [ + _buildBodyViewer(viewModel), + _buildViewToggleButtons(viewModel), + // _buildViewZoomButtons(viewModel), + _buildBottomSheet(viewModel), + ], + ), + ), + ], + ); + }, + ), + ), + // Tutorial overlay + if (_showTutorial) + PinchZoomTutorialOverlay( + onComplete: _onTutorialComplete, + ), + ], ); } diff --git a/lib/presentation/symptoms_checker/widgets/pinch_zoom_tutorial_overlay.dart b/lib/presentation/symptoms_checker/widgets/pinch_zoom_tutorial_overlay.dart new file mode 100644 index 00000000..307445a4 --- /dev/null +++ b/lib/presentation/symptoms_checker/widgets/pinch_zoom_tutorial_overlay.dart @@ -0,0 +1,261 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; + +class PinchZoomTutorialOverlay extends StatefulWidget { + final VoidCallback onComplete; + + const PinchZoomTutorialOverlay({ + Key? key, + required this.onComplete, + }) : super(key: key); + + @override + State createState() => _PinchZoomTutorialOverlayState(); +} + +class _PinchZoomTutorialOverlayState extends State with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _fadeAnimation; + int _currentStep = 0; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _fadeAnimation = CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ); + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + void _nextStep() { + if (_currentStep < 1) { + setState(() { + _currentStep++; + }); + } else { + _animationController.reverse().then((_) { + widget.onComplete(); + }); + } + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _fadeAnimation, + child: Material( + color: Colors.black.withValues(alpha: 0.85), + child: SafeArea( + child: Stack( + children: [ + // Tap to dismiss overlay + Positioned.fill( + child: GestureDetector( + onTap: () {}, + child: Container(color: Colors.transparent), + ), + ), + // Tutorial content + Center( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: _currentStep == 0 ? _buildFirstStep() : _buildSecondStep(), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildFirstStep() { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Pinch gesture illustration + Container( + width: 200.w, + height: 200.h, + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(20.r), + ), + child: Center( + child: _buildPinchGestureAnimation(), + ), + ), + SizedBox(height: 32.h), + // Title + Text( + LocaleKeys.pinchToZoom.tr(context: context), + style: TextStyle( + color: Colors.white, + fontSize: 24.f, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 32.h), + // Button + CustomButton( + text: LocaleKeys.gotIt.tr(context: context), + onPressed: _nextStep, + backgroundColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + height: 50.h, + ), + ], + ); + } + + Widget _buildSecondStep() { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Illustration container with rounded corners + Container( + padding: EdgeInsets.all(24.w), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.95), + borderRadius: BorderRadius.circular(20.r), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + LocaleKeys.zoomIntoDetails.tr(context: context), + style: TextStyle( + color: AppColors.textColor, + fontSize: 20.f, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 24.h), + // Pinch instruction + _buildInstructionRow( + icon: Icons.pinch_outlined, + text: LocaleKeys.pinchWithTwoFingersToZoom.tr(context: context), + ), + SizedBox(height: 16.h), + // Drag instruction + _buildInstructionRow( + icon: Icons.pan_tool_outlined, + text: LocaleKeys.dragToMoveAround.tr(context: context), + ), + ], + ), + ), + SizedBox(height: 32.h), + // Button + CustomButton( + text: LocaleKeys.startExploring.tr(context: context), + onPressed: _nextStep, + backgroundColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + height: 50.h, + ), + ], + ); + } + + Widget _buildInstructionRow({required IconData icon, required String text}) { + return Row( + children: [ + Container( + width: 48.w, + height: 48.h, + decoration: BoxDecoration( + color: AppColors.primaryRedColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12.r), + ), + child: Icon( + icon, + color: AppColors.primaryRedColor, + size: 24.w, + ), + ), + SizedBox(width: 16.w), + Expanded( + child: Text( + text, + style: TextStyle( + color: AppColors.textColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ); + } + + Widget _buildPinchGestureAnimation() { + return TweenAnimationBuilder( + tween: Tween(begin: 1.0, end: 0.7), + duration: const Duration(milliseconds: 1500), + curve: Curves.easeInOut, + builder: (context, value, child) { + return Transform.scale( + scale: value, + child: Stack( + alignment: Alignment.center, + children: [ + // Left hand + Positioned( + left: 40.w * (1 - value), + child: Transform.rotate( + angle: -0.3, + child: Icon( + Icons.pan_tool, + size: 50.w, + color: Colors.white.withValues(alpha: 0.9), + ), + ), + ), + // Right hand + Positioned( + right: 40.w * (1 - value), + child: Transform.rotate( + angle: 0.3, + child: Icon( + Icons.pan_tool, + size: 50.w, + color: Colors.white.withValues(alpha: 0.9), + ), + ), + ), + ], + ), + ); + }, + onEnd: () { + // Restart animation + setState(() {}); + }, + ); + } +} +