updates and fixes

pull/281/head
Sultan khan 14 hours ago
parent d9b6e9fed8
commit 246d414fe1

@ -1810,5 +1810,13 @@
"watchUsOnYoutube": "شاهدنا على إنستغرام", "watchUsOnYoutube": "شاهدنا على إنستغرام",
"connectOnLinkedin": "تواصل معنا على لينكد إن", "connectOnLinkedin": "تواصل معنا على لينكد إن",
"riskFactorPageTitle": "اعراض تشعر بها", "riskFactorPageTitle": "اعراض تشعر بها",
"symptomsCheckSuggestions": "الاحتمالات" "symptomsCheckSuggestions": "الاحتمالات",
"bloodDonationService": "التبرع \n بالدم",
"bookAppointmentService": "حجز\nموعد",
"pinchToZoom": "اضغط للتكبير",
"gotIt": "فهمت",
"zoomIntoDetails": "قم بالتكبير لرؤية التفاصيل",
"pinchWithTwoFingersToZoom": "اضغط بإصبعين للتكبير",
"dragToMoveAround": "اسحب للتحرك",
"startExploring": "ابدأ الاستكشاف"
} }

@ -1800,7 +1800,15 @@
"watchUsOnYoutube": "Watch us on Instagram", "watchUsOnYoutube": "Watch us on Instagram",
"connectOnLinkedin": "Connect on LinkedIn", "connectOnLinkedin": "Connect on LinkedIn",
"riskFactorPageTitle": "What do you feel?", "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"
} }

@ -80,4 +80,5 @@ class CacheConst {
static const String zoomRoomID = 'zoom-room-id'; static const String zoomRoomID = 'zoom-room-id';
static const String callTypeID = 'call-type-id'; static const String callTypeID = 'call-type-id';
static String isAppOpenedFromCall = "is_app_opened_from_call"; static String isAppOpenedFromCall = "is_app_opened_from_call";
static const String organSelectorTutorialShown = 'organ_selector_tutorial_shown';
} }

@ -131,9 +131,13 @@ class _NewReferralPageState extends State<NewReferralPage> {
hmgServicesVM.createEReferral( hmgServicesVM.createEReferral(
requestModel: createReferralRequestModel, requestModel: createReferralRequestModel,
onSuccess: (GenericApiModel response) { onSuccess: (GenericApiModel response) async{
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
showSuccessBottomSheet(int.parse(response.data), hmgServicesVM); showSuccessBottomSheet(int.parse(response.data), hmgServicesVM);
await Future.delayed(Duration(milliseconds: 2000)).then((value) {
if (mounted) Navigator.pop(context);
});
}, },
onError: (errorMessage) { onError: (errorMessage) {
// Handle error (e.g., show error message) // Handle error (e.g., show error message)

@ -88,7 +88,7 @@ class _ServicesPageState extends State<ServicesPage> {
// await getIt.get<AuthenticationViewModel>().onLoginPressed(); // await getIt.get<AuthenticationViewModel>().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<BookAppointmentsViewModel>().onTabChanged(0); getIt.get<BookAppointmentsViewModel>().onTabChanged(0);
Navigator.of(getIt<NavigationService>().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage())); Navigator.of(getIt<NavigationService>().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage()));
}), }),
@ -169,7 +169,7 @@ class _ServicesPageState extends State<ServicesPage> {
}), }),
HmgServicesComponentModel( HmgServicesComponentModel(
3, 3,
LocaleKeys.bloodDonation.tr(context: getIt.get<NavigationService>().navigatorKey.currentContext!), LocaleKeys.bloodDonationService.tr(context: getIt.get<NavigationService>().navigatorKey.currentContext!),
"".needTranslation, "".needTranslation,
AppAssets.blood_donation_icon, AppAssets.blood_donation_icon,
bgColor: AppColors.bloodDonationCardColor, bgColor: AppColors.bloodDonationCardColor,

@ -57,6 +57,7 @@ class ServiceGridViewItem extends StatelessWidget {
isBold: true, isBold: true,
color: AppColors.textColor, color: AppColors.textColor,
maxLine: 2, maxLine: 2,
), ),
], ],
)); ));

@ -178,13 +178,15 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
], ],
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
Row( // Check if doctor is active - if not, show only View Details button
children: [ (widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true)
widget.myAppointmentsViewModel.isMyAppointmentsLoading ? // Doctor is active - check rebooking logic
? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) &&
: Expanded( widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false)
flex: 7, ? // Show only the button without arrow when rebooking not allowed
child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) 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) ? getArrivedAppointmentButton(context).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading)
: CustomButton( : CustomButton(
text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction),
@ -202,51 +204,101 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction),
iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
iconSize: 14.h, iconSize: 14.h,
).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading)
), : // Normal flow - show button with arrow
SizedBox(width: 8.w), Row(
((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || children: [
(widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || widget.myAppointmentsViewModel.isMyAppointmentsLoading
!Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) && ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r)
AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) : Expanded(
? SizedBox.shrink() flex: 7,
: Expanded( child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
flex: 2, ? getArrivedAppointmentButton(context).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading)
child: Container( : CustomButton(
height: 40.h, text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction),
width: 40.w, onPressed: () {
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context);
color: AppColors.textColor, },
borderRadius: 10.r, backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15),
), borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01),
child: Padding( textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
padding: EdgeInsets.all(10.w), fontSize: 14.f,
child: Transform.flip( fontWeight: FontWeight.w600,
flipX: appState.isArabic(), borderRadius: 12.r,
child: Utils.buildSvgWithAssets( padding: EdgeInsets.symmetric(horizontal: 10.w),
iconColor: AppColors.whiteColor, height: 40.h,
icon: AppAssets.forward_arrow_icon_small, icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction),
width: 40.w, iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
height: 40.h, iconSize: 14.h,
fit: BoxFit.contain, ).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),
), ),
), )
), .then((_) {
).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading).onPress(() { widget.myAppointmentsViewModel.initAppointmentsViewModel();
Navigator.of(context) widget.myAppointmentsViewModel.getPatientAppointments(true, false);
.push( });
CustomPageRoute( },
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), backgroundColor: AppColors.secondaryLightRedColor,
), borderColor: AppColors.secondaryLightRedColor,
) textColor: AppColors.primaryRedColor,
.then((val) { fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
// widget.myAppointmentsViewModel.initAppointmentsViewModel(); fontWeight: FontWeight.w600,
// widget.myAppointmentsViewModel.getPatientAppointments(true, false); borderRadius: 12.r,
}); padding: EdgeInsets.symmetric(horizontal: 10.w),
}), height: 40.h,
), ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
],
),
], ],
).paddingAll(16.w), ).paddingAll(16.w),
), ),
@ -255,6 +307,34 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
} }
Widget getArrivedAppointmentButton(BuildContext context) { 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) || if ((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ||
(widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) ||
!Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) && !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) &&

@ -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_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.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/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/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.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/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/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/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/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -28,12 +31,36 @@ class OrganSelectorPage extends StatefulWidget {
class _OrganSelectorPageState extends State<OrganSelectorPage> { class _OrganSelectorPageState extends State<OrganSelectorPage> {
late final AppState _appState; late final AppState _appState;
late final DialogService dialogService; late final DialogService dialogService;
late final CacheService cacheService;
bool _showTutorial = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_appState = getIt.get<AppState>(); _appState = getIt.get<AppState>();
dialogService = getIt<DialogService>(); dialogService = getIt<DialogService>();
cacheService = getIt<CacheService>();
_checkAndShowTutorial();
}
Future<void> _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 { void _onNextPressed(SymptomsCheckerViewModel viewModel) async {
@ -65,30 +92,39 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Stack(
backgroundColor: AppColors.bgScaffoldColor, children: [
appBar: _buildAppBar(), Scaffold(
body: Consumer<SymptomsCheckerViewModel>( backgroundColor: AppColors.bgScaffoldColor,
builder: (context, viewModel, _) { appBar: _buildAppBar(),
return Column( body: Consumer<SymptomsCheckerViewModel>(
crossAxisAlignment: CrossAxisAlignment.start, builder: (context, viewModel, _) {
children: [ return Column(
_buildTitle(), crossAxisAlignment: CrossAxisAlignment.start,
SizedBox(height: 8.h), children: [
Expanded( _buildTitle(),
child: Stack( SizedBox(height: 8.h),
children: [ Expanded(
_buildBodyViewer(viewModel), child: Stack(
_buildViewToggleButtons(viewModel), children: [
_buildViewZoomButtons(viewModel), _buildBodyViewer(viewModel),
_buildBottomSheet(viewModel), _buildViewToggleButtons(viewModel),
], // _buildViewZoomButtons(viewModel),
), _buildBottomSheet(viewModel),
), ],
], ),
); ),
}, ],
), );
},
),
),
// Tutorial overlay
if (_showTutorial)
PinchZoomTutorialOverlay(
onComplete: _onTutorialComplete,
),
],
); );
} }

@ -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<PinchZoomTutorialOverlay> createState() => _PinchZoomTutorialOverlayState();
}
class _PinchZoomTutorialOverlayState extends State<PinchZoomTutorialOverlay> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _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<double>(
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(() {});
},
);
}
}
Loading…
Cancel
Save