From c7c0a0ac093910f7a56db3dda9a7a268d7f6a566 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Thu, 11 Dec 2025 00:00:52 +0300 Subject: [PATCH] Added user_info module IAP --- assets/images/png/female_icon.png | Bin 0 -> 31413 bytes assets/images/png/male_icon.png | Bin 0 -> 20457 bytes assets/images/svg/calendar-grey.svg | 8 + assets/images/svg/gender.svg | 4 + assets/images/svg/ruler.svg | 3 + assets/images/svg/tick.svg | 3 + assets/images/svg/weight-scale.svg | 3 + lib/core/app_assets.dart | 13 +- .../symptoms_checker_view_model.dart | 103 +++++++ lib/presentation/authentication/login.dart | 1 - .../book_appointment_page.dart | 284 ++++++++++-------- .../emergency_services_page.dart | 6 +- lib/presentation/home/landing_page.dart | 6 +- lib/presentation/home/navigation_screen.dart | 12 +- .../organ_selector_screen.dart | 2 +- .../symptoms_checker/risk_factors_screen.dart | 2 +- .../symptoms_checker/suggestions_screen.dart | 2 +- .../symptoms_selector_screen.dart | 2 +- .../symptoms_checker/triage_screen.dart | 2 +- .../symptoms_checker/user_info_selection.dart | 187 ++++++++++++ .../pages/age_selection_page.dart | 36 +++ .../pages/gender_selection_page.dart | 75 +++++ .../pages/height_selection_page.dart | 175 +++++++++++ .../pages/weight_selection_page.dart | 236 +++++++++++++++ .../user_info_flow_manager.dart | 209 +++++++++++++ .../widgets/user_info_progress_bar.dart | 36 +++ .../widgets/user_info_selection_scaffold.dart | 67 +++++ .../widgets/user_info_sticky_bottom_card.dart | 68 +++++ lib/routes/app_routes.dart | 24 +- lib/widgets/appbar/collapsing_list_view.dart | 8 +- lib/widgets/input_widget.dart | 14 +- 31 files changed, 1427 insertions(+), 164 deletions(-) create mode 100644 assets/images/png/female_icon.png create mode 100644 assets/images/png/male_icon.png create mode 100644 assets/images/svg/calendar-grey.svg create mode 100644 assets/images/svg/gender.svg create mode 100644 assets/images/svg/ruler.svg create mode 100644 assets/images/svg/tick.svg create mode 100644 assets/images/svg/weight-scale.svg create mode 100644 lib/presentation/symptoms_checker/user_info_selection.dart create mode 100644 lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart create mode 100644 lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart create mode 100644 lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart create mode 100644 lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart create mode 100644 lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart create mode 100644 lib/presentation/symptoms_checker/user_info_selection/widgets/user_info_progress_bar.dart create mode 100644 lib/presentation/symptoms_checker/user_info_selection/widgets/user_info_selection_scaffold.dart create mode 100644 lib/presentation/symptoms_checker/user_info_selection/widgets/user_info_sticky_bottom_card.dart diff --git a/assets/images/png/female_icon.png b/assets/images/png/female_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b41a542bbc7de9c09a3705557c0d47b33ac25301 GIT binary patch literal 31413 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 F`X5SD6)*q* literal 0 HcmV?d00001 diff --git a/assets/images/png/male_icon.png b/assets/images/png/male_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fa518fbd972a7d12c240479e9320f62538533c72 GIT binary patch literal 20457 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 kAq@;Xi + + + + + + + diff --git a/assets/images/svg/gender.svg b/assets/images/svg/gender.svg new file mode 100644 index 0000000..6819ba6 --- /dev/null +++ b/assets/images/svg/gender.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/ruler.svg b/assets/images/svg/ruler.svg new file mode 100644 index 0000000..a2e8c11 --- /dev/null +++ b/assets/images/svg/ruler.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/tick.svg b/assets/images/svg/tick.svg new file mode 100644 index 0000000..b6210c0 --- /dev/null +++ b/assets/images/svg/tick.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/weight-scale.svg b/assets/images/svg/weight-scale.svg new file mode 100644 index 0000000..c3329ff --- /dev/null +++ b/assets/images/svg/weight-scale.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index b369d62..2f806b6 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -195,8 +195,13 @@ class AppAssets { static const String heart = '$svgBasePath/heart.svg'; static const String alertSquare = '$svgBasePath/alert-square.svg'; static const String arrowRight = '$svgBasePath/arrow-right.svg'; + static const String tickIcon = '$svgBasePath/tick.svg'; // Symptoms Checker + static const String calendarGrey = '$svgBasePath/calendar-grey.svg'; + static const String weightScale = '$svgBasePath/weight-scale.svg'; + static const String rulerIcon = '$svgBasePath/ruler.svg'; + static const String genderIcon = '$svgBasePath/gender.svg'; static const String bodyIcon = '$svgBasePath/body_icon.svg'; static const String rotateIcon = '$svgBasePath/rotate_icon.svg'; static const String refreshIcon = '$svgBasePath/refresh.svg'; @@ -218,6 +223,8 @@ class AppAssets { static const String dummyUser = '$pngBasePath/dummy_user.png'; static const String comprehensiveCheckupEn = '$pngBasePath/cc_en.png'; static const String comprehensiveCheckupAr = '$pngBasePath/cc_er.png'; + static const String maleIcon = '$pngBasePath/male_icon.png'; + static const String femaleIcon = '$pngBasePath/female_icon.png'; static const String fullBodyFront = '$pngBasePath/full_body_front.png'; static const String fullBodyBack = '$pngBasePath/full_body_back.png'; @@ -236,9 +243,9 @@ class AppAnimations { static const String splashLaunching = '$lottieBasePath/splash_launching.json'; static const String noData = '$lottieBasePath/Nodata.json'; static const String ripple = '$lottieBasePath/Ripple.json'; - static const String pending_loading_animation = '$lottieBasePath/pending_loading_animation.json'; + static const String pendingLoadingAnimation = '$lottieBasePath/pending_loading_animation.json'; static const String ambulance = '$lottieBasePath/ambulance.json'; - static const String ambulance_alert = '$lottieBasePath/ambulance_alert.json'; - static const String rrt_ambulance = '$lottieBasePath/rrt_ambulance.json'; + static const String ambulanceAlert = '$lottieBasePath/ambulance_alert.json'; + static const String rrtAmbulance = '$lottieBasePath/rrt_ambulance.json'; } diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index a467d01..34a1754 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -36,10 +36,28 @@ class SymptomsCheckerViewModel extends ChangeNotifier { // Selected symptoms tracking (organId -> Set of symptom IDs) final Map> _selectedSymptomsByOrgan = {}; + // User Info Flow State + int _userInfoCurrentPage = 0; + String? _selectedGender; + int? _selectedAge; + double? _selectedHeight; + bool _isHeightCm = true; + double? _selectedWeight; + bool _isWeightKg = true; + // Getters bool isPossibleConditionsLoading = false; + // User Info Getters + int get userInfoCurrentPage => _userInfoCurrentPage; + String? get selectedGender => _selectedGender; + int? get selectedAge => _selectedAge; + double? get selectedHeight => _selectedHeight; + bool get isHeightCm => _isHeightCm; + double? get selectedWeight => _selectedWeight; + bool get isWeightKg => _isWeightKg; + BodyView get currentView => _currentView; Set get selectedOrganIds => _selectedOrganIds; @@ -247,6 +265,91 @@ class SymptomsCheckerViewModel extends ChangeNotifier { _isBottomSheetExpanded = false; _tooltipTimer?.cancel(); _tooltipOrganId = null; + // Reset user info flow + _userInfoCurrentPage = 0; + _selectedGender = null; + _selectedAge = null; + _selectedHeight = null; + _isHeightCm = true; + _selectedWeight = null; + _isWeightKg = true; + notifyListeners(); + } + + // User Info Flow Methods + + /// Set current page in user info flow + void setUserInfoPage(int page) { + _userInfoCurrentPage = page; + notifyListeners(); + } + + /// Navigate to next page in user info flow + void nextUserInfoPage() { + if (_userInfoCurrentPage < 3) { + _userInfoCurrentPage++; + notifyListeners(); + } + } + + /// Navigate to previous page in user info flow + void previousUserInfoPage() { + if (_userInfoCurrentPage > 0) { + _userInfoCurrentPage--; + notifyListeners(); + } + } + + /// Set selected gender + void setGender(String gender) { + _selectedGender = gender; + notifyListeners(); + } + + /// Set selected age + void setAge(int age) { + _selectedAge = age; + notifyListeners(); + } + + /// Set selected height + void setHeight(double height, bool isCm) { + _selectedHeight = height; + _isHeightCm = isCm; + notifyListeners(); + } + + /// Set selected weight + void setWeight(double weight, bool isKg) { + _selectedWeight = weight; + _isWeightKg = isKg; + notifyListeners(); + } + + /// Check if user info page is last + bool get isUserInfoLastPage => _userInfoCurrentPage == 3; + + /// Validate and submit user info + Map getUserInfoData() { + return { + 'gender': _selectedGender, + 'age': _selectedAge, + 'height': _selectedHeight, + 'heightUnit': _isHeightCm ? 'cm' : 'ft', + 'weight': _selectedWeight, + 'weightUnit': _isWeightKg ? 'kg' : 'lbs', + }; + } + + /// Reset user info flow + void resetUserInfo() { + _userInfoCurrentPage = 0; + _selectedGender = null; + _selectedAge = null; + _selectedHeight = null; + _isHeightCm = true; + _selectedWeight = null; + _isWeightKg = true; notifyListeners(); } diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index 28430a8..c14e957 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -1,7 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.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'; diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 0ad58cc..0e60a28 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -4,11 +4,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.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/dependencies.dart'; -import 'package:hmg_patient_app_new/core/utils/size_config.dart'; -import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.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/features/authentication/authentication_view_model.dart'; @@ -25,7 +25,6 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immed import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; -import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -71,145 +70,137 @@ class _BookAppointmentPageState extends State { regionalViewModel = Provider.of(context, listen: true); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: LocaleKeys.bookAppo.tr(context: context), - isLeading: true, - leadingCallback: () { - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); - }, - child: SingleChildScrollView( - child: Consumer(builder: (context, bookAppointmentsVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), - tabs: [ - CustomTabBarModel(null, "General".needTranslation), - CustomTabBarModel(null, "LiveCare".needTranslation), - ], - onTabChange: (index) { - bookAppointmentsVM.onTabChanged(index); - }, - ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 24.h), - getSelectedTabData(bookAppointmentsVM.selectedTabIndex), - SizedBox(height: 24.h), - "Recent Visits".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0.h), - SizedBox(height: 16.h), - Consumer(builder: (context, myAppointmentsVM, child) { - return myAppointmentsVM.isPatientMyDoctorsLoading - ? Column( - crossAxisAlignment: CrossAxisAlignment.center, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.bookAppo.tr(context: context), + isLeading: true, + child: SingleChildScrollView( + child: Consumer(builder: (context, bookAppointmentsVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.network( - "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", - width: 64.w, - height: 64.h, - fit: BoxFit.cover, - ).circle(100).toShimmer2(isShow: true, radius: 50.r), - SizedBox(height: 8.h), - ("Dr. John Smith Smith Smith") - .toString() - .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) - .toShimmer2(isShow: true), - ], - ) - : myAppointmentsVM.patientMyDoctorsList.isEmpty - ? Container( - width: SizeConfig.screenWidth, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.r, - hasShadow: false, - ), - child: Utils.getNoDataWidget( - context, - noDataText: "You don't have any completed visits yet".needTranslation, - isSmallWidget: true, - width: 62.w, - height: 62.h, - ), - ).paddingSymmetrical(24.w, 0.h) - : SizedBox( - height: 110.h, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: myAppointmentsVM.patientMyDoctorsList.length, - shrinkWrap: true, - padding: EdgeInsets.only(left: 24.w, right: 24.w), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SizedBox( - // width: 80.w, - child: Column( + SizedBox(height: 16.h), + CustomTabBar( + activeTextColor: Color(0xffED1C2B), + activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, "General".needTranslation), + CustomTabBarModel(null, "LiveCare".needTranslation), + ], + onTabChange: (index) { + bookAppointmentsVM.onTabChanged(index); + }, + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), + getSelectedTabData(bookAppointmentsVM.selectedTabIndex), + SizedBox(height: 24.h), + if (appState.isAuthenticated) ...[ + Consumer(builder: (context, myAppointmentsVM, child) { + return myAppointmentsVM.isPatientMyDoctorsLoading + ? Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.network( - myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 64.w, height: 64.h, fit: BoxFit.cover, - ).circle(100).toShimmer2(isShow: false, radius: 50.r), + ).circle(100).toShimmer2(isShow: true, radius: 50.r), SizedBox(height: 8.h), - SizedBox( - width: 80.w, - child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) - .toString() - .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) - .toShimmer2(isShow: false), - ), + ("Dr. John Smith Smith Smith") + .toString() + .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) + .toShimmer2(isShow: true), ], - ), - ).onPress(() async { - bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel( - clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID, - projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID, - doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID, - )); - LoaderBottomSheet.showLoader(); - await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) { - LoaderBottomSheet.hideLoader(); - Navigator.of(context).push( - CustomPageRoute( - page: DoctorProfilePage(), - ), - ); - }, onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - }), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), - ), + ) + : myAppointmentsVM.patientMyDoctorsList.isEmpty + ? SizedBox() + : Column( + children: [ + if (appState.isAuthenticated) ...[], + "Recent Visits".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0.h), + SizedBox(height: 16.h), + SizedBox( + height: 110.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: myAppointmentsVM.patientMyDoctorsList.length, + shrinkWrap: true, + padding: EdgeInsets.only(left: 24.w, right: 24.w), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SizedBox( + // width: 80.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, + width: 64.w, + height: 64.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: false, radius: 50.r), + SizedBox(height: 8.h), + SizedBox( + width: 80.w, + child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) + .toString() + .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) + .toShimmer2(isShow: false), + ), + ], + ), + ).onPress(() async { + bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel( + clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID, + projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID, + doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID, + )); + LoaderBottomSheet.showLoader(); + await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) { + LoaderBottomSheet.hideLoader(); + Navigator.of(context).push( + CustomPageRoute( + page: DoctorProfilePage(), + ), + ); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), + ), + ), + ], + ); + }), + ], + ], ); }), - ], - ); - }), - ), + ), + ), + ), + _buildSymptomsBottomCard(), + ], ), ); } @@ -460,6 +451,37 @@ class _BookAppointmentPageState extends State { return Container(); } + Widget _buildSymptomsBottomCard() { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: Row( + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Not sure? help me choose a clinic!".needTranslation.toText16(weight: FontWeight.w600, color: AppColors.textColor), + SizedBox(height: 4.h), + "Mention your symptoms and find the list of doctors accordingly".needTranslation.toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ], + ), + ), + SizedBox(width: 16.w), + CustomButton( + height: 40.h, + text: "", + onPressed: () => context.navigateWithName(AppRoutes.userInfoSelection), + icon: AppAssets.arrow_forward, + ) + ], + ).paddingAll(24.w), + ); + } + void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) { regionalViewModel.flush(); regionalViewModel.setBottomSheetType(type); diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index bce7daf..3833d32 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -97,7 +97,7 @@ class EmergencyServicesPage extends StatelessWidget { }), ], ), - Lottie.asset(AppAnimations.ambulance_alert, + Lottie.asset(AppAnimations.ambulanceAlert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), "Confirmation".needTranslation.toText28(color: AppColors.whiteColor, isBold: true), @@ -200,7 +200,7 @@ class EmergencyServicesPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Lottie.asset(AppAnimations.ambulance_alert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), + Lottie.asset(AppAnimations.ambulanceAlert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), LocaleKeys.confirm.tr().toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), @@ -313,7 +313,7 @@ class EmergencyServicesPage extends StatelessWidget { }), ], ), - Lottie.asset(AppAnimations.ambulance_alert, + Lottie.asset(AppAnimations.ambulanceAlert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), LocaleKeys.confirm.tr().toText28(color: AppColors.whiteColor, isBold: true), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 454f1e7..96ec558 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -12,7 +12,6 @@ import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/route_extensions.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/features/authentication/authentication_view_model.dart'; @@ -40,7 +39,6 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart'; -import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -494,8 +492,7 @@ class _LandingPageState extends State { mainAxisSize: MainAxisSize.min, spacing: 12.h, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h) - .onPress(() => context.navigateWithName(AppRoutes.organSelectorPage)), + Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h), Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 18.h, width: 18.h).onPress(() {}), Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() { showCommonBottomSheetWithoutHeight( @@ -581,7 +578,6 @@ class _LandingPageState extends State { ); }, ), - // height: isDone == false ? ResponsiveExtension.screenHeight * 0.5 : ResponsiveExtension.screenHeight * 0.3, isFullScreen: false, callBackFunc: () { isDone = true; diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index f8447d2..7fbcdb3 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.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/presentation/book_appointment/book_appointment_page.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart'; import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart'; +import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart'; class LandingNavigation extends StatefulWidget { @@ -18,12 +19,11 @@ class LandingNavigation extends StatefulWidget { class _LandingNavigationState extends State { int _currentIndex = 0; - late AppState appState; final PageController _pageController = PageController(); @override Widget build(BuildContext context) { - appState = getIt.get(); + AppState appState = getIt.get(); return Scaffold( body: PageView( controller: _pageController, @@ -31,7 +31,7 @@ class _LandingNavigationState extends State { children: [ const LandingPage(), appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ FeedbackPage(), - BookAppointmentPage(), + SizedBox(), const ToDoPage(), ServicesPage(), ], @@ -40,6 +40,10 @@ class _LandingNavigationState extends State { currentIndex: _currentIndex, onTap: (index) { setState(() => _currentIndex = index); + if (_currentIndex == 2) { + context.navigateWithName(AppRoutes.bookAppointmentPage); + return; + } _pageController.animateToPage(index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); }, ), diff --git a/lib/presentation/symptoms_checker/organ_selector_screen.dart b/lib/presentation/symptoms_checker/organ_selector_screen.dart index 65d9a00..d5dc32c 100644 --- a/lib/presentation/symptoms_checker/organ_selector_screen.dart +++ b/lib/presentation/symptoms_checker/organ_selector_screen.dart @@ -42,7 +42,7 @@ class _OrganSelectorPageState extends State { return; } - context.navigateWithName(AppRoutes.symptomsCheckerScreen); + context.navigateWithName(AppRoutes.symptomsSelectorScreen); } @override diff --git a/lib/presentation/symptoms_checker/risk_factors_screen.dart b/lib/presentation/symptoms_checker/risk_factors_screen.dart index 211aabc..2992593 100644 --- a/lib/presentation/symptoms_checker/risk_factors_screen.dart +++ b/lib/presentation/symptoms_checker/risk_factors_screen.dart @@ -148,7 +148,7 @@ class _RiskFactorsScreenState extends State { Expanded( child: CollapsingListView( title: "Risks".needTranslation, - onLeadingTapped: () => _buildConfirmationBottomSheet( + leadingCallback: () => _buildConfirmationBottomSheet( context: context, onConfirm: () => { context.pop(), diff --git a/lib/presentation/symptoms_checker/suggestions_screen.dart b/lib/presentation/symptoms_checker/suggestions_screen.dart index f9d922a..2832515 100644 --- a/lib/presentation/symptoms_checker/suggestions_screen.dart +++ b/lib/presentation/symptoms_checker/suggestions_screen.dart @@ -148,7 +148,7 @@ class _SuggestionsScreenState extends State { Expanded( child: CollapsingListView( title: "Suggestions".needTranslation, - onLeadingTapped: () => _buildConfirmationBottomSheet( + leadingCallback: () => _buildConfirmationBottomSheet( context: context, onConfirm: () => { context.pop(), diff --git a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart index bed232b..522c5f8 100644 --- a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart +++ b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart @@ -78,7 +78,7 @@ class _SymptomsSelectorScreenState extends State { Expanded( child: CollapsingListView( title: "Symptoms Selector".needTranslation, - onLeadingTapped: () => _buildConfirmationBottomSheet( + leadingCallback: () => _buildConfirmationBottomSheet( context: context, onConfirm: () => { context.pop(), diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index 4dbe0ca..aa0cd72 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -103,7 +103,7 @@ class _TriageScreenState extends State { // context.pop(), // }), - onLeadingTapped: () => context.pop(), + leadingCallback: () => context.pop(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart new file mode 100644 index 0000000..2fb6cc9 --- /dev/null +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -0,0 +1,187 @@ +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/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.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/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; + +class UserInfoSelectionScreen extends StatelessWidget { + const UserInfoSelectionScreen({super.key}); + + _buildEditInfoTile({ + required String leadingIcon, + required String title, + required String subTitle, + required VoidCallback onTap, + required String trailingIcon, + required BuildContext context, + Color? iconColor, + }) { + return InkWell( + onTap: onTap, + child: Row( + children: [ + Expanded( + child: Row( + children: [ + Container( + height: 40.h, + width: 40.h, + margin: EdgeInsets.only(right: 10.h), + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 12.r, color: AppColors.greyColor), + child: Utils.buildSvgWithAssets(icon: leadingIcon, iconColor: iconColor)), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText16(weight: FontWeight.w500), + subTitle.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500), + ], + ), + ], + ), + ), + Utils.buildSvgWithAssets(icon: trailingIcon, height: 24.h, width: 24.h), + ], + ), + ); + } + + Widget _getDivider() { + return Divider( + color: AppColors.dividerColor, + ).paddingSymmetrical(0, 16.h); + } + + @override + Widget build(BuildContext context) { + AppState appState = getIt.get(); + + String name = ""; + if (appState.isAuthenticated) { + name = "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!} "; + } else { + name = "Guest"; + } + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Symptoms Checker".needTranslation, + isLeading: true, + child: SingleChildScrollView( + child: Column( + children: [ + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w), + child: Column( + children: [ + "Hi $name, Is your information up to date?".needTranslation.toText18( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + SizedBox(height: 24.h), + _buildEditInfoTile( + context: context, + leadingIcon: AppAssets.genderIcon, + title: "Gender".needTranslation, + subTitle: "Male".needTranslation, + onTap: () {}, + trailingIcon: AppAssets.edit_icon, + ), + _getDivider(), + _buildEditInfoTile( + context: context, + leadingIcon: AppAssets.calendar, + title: "Age".needTranslation, + subTitle: "32 Years", + iconColor: AppColors.greyTextColor, + onTap: () {}, + trailingIcon: AppAssets.edit_icon, + ), + _getDivider(), + _buildEditInfoTile( + context: context, + leadingIcon: AppAssets.rulerIcon, + title: "Height".needTranslation, + subTitle: "17 8cm", + onTap: () {}, + trailingIcon: AppAssets.edit_icon, + ), + _getDivider(), + _buildEditInfoTile( + context: context, + leadingIcon: AppAssets.weightScale, + title: "Weight".needTranslation, + subTitle: "88 kg", + onTap: () {}, + trailingIcon: AppAssets.edit_icon, + ), + ], + ), + ), + ], + ).paddingAll(24.w), + ), + ), + ), + _buildBottomCard(context), + ], + ), + ); + } + + Widget _buildBottomCard(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 24.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "No, Edit all".needTranslation, + icon: AppAssets.edit_icon, + iconColor: AppColors.primaryRedColor, + onPressed: () => context.navigateWithName(AppRoutes.userInfoFlowManager), + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), + borderColor: Colors.transparent, + textColor: AppColors.primaryRedColor, + fontSize: 16.f, + ), + ), + SizedBox(width: 12.w), + Expanded( + child: CustomButton( + text: "Yes, It is".needTranslation, + icon: AppAssets.tickIcon, + iconColor: AppColors.whiteColor, + onPressed: () => () {}, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + ), + ), + ], + ), + ], + ).paddingSymmetrical(24.w, 0), + ), + ); + } +} diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart new file mode 100644 index 0000000..6433b93 --- /dev/null +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; + +/// Age selection page content +class AgeSelectionPage extends StatelessWidget { + final int? selectedAge; + final Function(int) onAgeSelected; + + const AgeSelectionPage({ + super.key, + required this.selectedAge, + required this.onAgeSelected, + }); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + padding: EdgeInsets.all(24.w), + child: Consumer( + builder: (BuildContext context, symptomsViewModel, Widget? child) { + return Column( + children: [ + "What is your gender?".needTranslation.toText18(weight: FontWeight.w600, color: AppColors.textColor), + SizedBox(height: 70.h), + "< Age Widget Here >".needTranslation.toText18(weight: FontWeight.w600, color: AppColors.greyTextColor), + ], + ); + }, + ), + ); + } +} diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart new file mode 100644 index 0000000..85cb6e2 --- /dev/null +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart @@ -0,0 +1,75 @@ +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/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/features/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; + +/// Gender selection page content +class GenderSelectionPage extends StatelessWidget { + final String? selectedGender; + final Function(String) onGenderSelected; + + GenderSelectionPage({ + super.key, + required this.selectedGender, + required this.onGenderSelected, + }); + + _buildGenderOption(String iconPng, String label, bool isSelected) { + return Container( + height: 160.h, + width: 160.w, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, borderRadius: 24.r, side: isSelected ? BorderSide(color: AppColors.primaryRedColor, width: 2.5) : null), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Utils.buildImgWithAssets(icon: iconPng, height: 80.h, width: 80.h, fit: BoxFit.contain), + SizedBox(height: 8.h), + label.toText16( + weight: FontWeight.w500, + ) + ], + ), + ); + } + + final genders = ["Male", "Female"]; + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + padding: EdgeInsets.all(24.w), + child: Consumer( + builder: (BuildContext context, symptomsViewModel, Widget? child) { + return Column( + children: [ + "What is your gender?".needTranslation.toText18(weight: FontWeight.w600, color: AppColors.textColor), + SizedBox(height: 70.h), + Row( + children: [ + Expanded( + child: InkWell( + onTap: () => onGenderSelected(genders[0]), + child: _buildGenderOption(AppAssets.maleIcon, "Male".needTranslation, symptomsViewModel.selectedGender == genders[0]), + ), + ), + SizedBox(width: 16.w), + Expanded( + child: InkWell( + onTap: () => onGenderSelected(genders[1]), + child: _buildGenderOption(AppAssets.femaleIcon, "Female".needTranslation, symptomsViewModel.selectedGender == genders[1]), + )) + ], + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart new file mode 100644 index 0000000..e10aca3 --- /dev/null +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart @@ -0,0 +1,175 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.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/theme/colors.dart'; + +/// Height selection page content +class HeightSelectionPage extends StatefulWidget { + final double height; + final bool isCm; + final Function(double, bool) onHeightChanged; + + const HeightSelectionPage({ + super.key, + required this.height, + required this.isCm, + required this.onHeightChanged, + }); + + @override + State createState() => _HeightSelectionPageState(); +} + +class _HeightSelectionPageState extends State { + late double heightValue; + + @override + void initState() { + super.initState(); + heightValue = widget.height; + } + + Widget _unitSelector() { + return Container( + height: 54.h, + padding: EdgeInsets.all(4.h), + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(10.r), + ), + child: LayoutBuilder( + builder: (context, constraints) { + final tabWidth = (constraints.maxWidth - 8.w) / 2; + return Stack( + children: [ + // Animated sliding indicator + AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + width: tabWidth, + height: constraints.maxHeight, + margin: EdgeInsets.only(left: widget.isCm ? 0 : tabWidth + 8.w), + decoration: BoxDecoration( + color: AppColors.bottomNAVBorder, + borderRadius: BorderRadius.circular(7.r), + ), + ), + // Tab buttons + Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + if (!widget.isCm) { + // Convert from FT to CM (1 ft = 30.48 cm) + final convertedHeight = heightValue * 30.48; + widget.onHeightChanged(convertedHeight, true); + } + }, + child: Container( + alignment: Alignment.center, + color: Colors.transparent, + child: Text( + 'CM', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 14.f, + color: widget.isCm ? AppColors.textColor : AppColors.textColor.withValues(alpha: 0.6), + ), + ), + ), + ), + ), + Expanded( + child: GestureDetector( + onTap: () { + if (widget.isCm) { + // Convert from CM to FT (1 cm = 0.0328084 ft) + final convertedHeight = heightValue / 30.48; + widget.onHeightChanged(convertedHeight, false); + } + }, + child: Container( + alignment: Alignment.center, + color: Colors.transparent, + child: Text( + 'FT', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 14.f, + color: !widget.isCm ? AppColors.textColor : AppColors.textColor.withValues(alpha: 0.6), + ), + ), + ), + ), + ), + ], + ), + ], + ); + }, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + Center( + child: Text( + 'How tall are you?'.needTranslation, + style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor), + ), + ), + SizedBox(height: 24.h), + Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: _unitSelector(), + ), + SizedBox(height: 90.h), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + heightValue.round().toString(), + style: TextStyle(fontSize: 100.f, color: AppColors.textColor, height: 1), + ), + SizedBox(width: 8.w), + Text(widget.isCm ? 'cm' : 'ft', style: TextStyle(fontWeight: FontWeight.w700, fontSize: 24.f)).paddingOnly(bottom: 10.h, left: 8.w), + ], + ), + ], + ); + } +} + +class _RulerPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paintTick = Paint()..color = const Color(0xFF222222); + final paintSmall = Paint()..color = const Color(0xFF222222).withValues(alpha: 0.6); + + final width = size.width; + final start = 120; + final end = 210; + final steps = end - start; + for (int i = 0; i <= steps; i++) { + final x = (i / steps) * width; + if (i % 10 == 0) { + canvas.drawLine(Offset(x, size.height * 0.1), Offset(x, size.height * 0.6), paintTick); + } else if (i % 5 == 0) { + canvas.drawLine(Offset(x, size.height * 0.2), Offset(x, size.height * 0.5), paintSmall); + } else { + canvas.drawLine(Offset(x, size.height * 0.35), Offset(x, size.height * 0.5), paintSmall); + } + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart new file mode 100644 index 0000000..0319f4b --- /dev/null +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart @@ -0,0 +1,236 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.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/theme/colors.dart'; + +/// Weight selection page content +class WeightSelectionPage extends StatefulWidget { + final double weight; + final bool isKg; + final Function(double, bool) onWeightChanged; + + const WeightSelectionPage({ + super.key, + required this.weight, + required this.isKg, + required this.onWeightChanged, + }); + + @override + State createState() => _WeightSelectionPageState(); +} + +class _WeightSelectionPageState extends State { + late double weightValue; + + @override + void initState() { + super.initState(); + weightValue = widget.weight; + } + + Widget _unitSelector() { + return Container( + height: 54.h, + padding: EdgeInsets.all(4.h), + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(10.r), + ), + child: LayoutBuilder( + builder: (context, constraints) { + final tabWidth = (constraints.maxWidth - 8.w) / 2; + return Stack( + children: [ + // Animated sliding indicator + AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + width: tabWidth, + height: constraints.maxHeight, + margin: EdgeInsets.only(left: widget.isKg ? 0 : tabWidth + 8.w), + decoration: BoxDecoration( + color: AppColors.bottomNAVBorder, + borderRadius: BorderRadius.circular(7.r), + ), + ), + // Tab buttons + Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + if (!widget.isKg) { + // Convert from LBS to KG (1 lb = 0.453592 kg) + final convertedWeight = weightValue / 2.20462; + widget.onWeightChanged(convertedWeight, true); + } + }, + child: Container( + alignment: Alignment.center, + color: Colors.transparent, + child: Text( + 'KG', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 14.f, + color: widget.isKg ? AppColors.textColor : AppColors.textColor.withValues(alpha: 0.6), + ), + ), + ), + ), + ), + Expanded( + child: GestureDetector( + onTap: () { + if (widget.isKg) { + // Convert from KG to LBS (1 kg = 2.20462 lbs) + final convertedWeight = weightValue * 2.20462; + widget.onWeightChanged(convertedWeight, false); + } + }, + child: Container( + alignment: Alignment.center, + color: Colors.transparent, + child: Text( + 'LBS', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 14.f, + color: !widget.isKg ? AppColors.textColor : AppColors.textColor.withValues(alpha: 0.6), + ), + ), + ), + ), + ), + ], + ), + ], + ); + }, + ), + ); + } + + Widget _weightSlider() { + return Column( + children: [ + SizedBox(height: 18.h), + SizedBox(height: 12.h), + SizedBox( + height: 80.h, + child: Row( + children: [ + Expanded(child: Container()), + Expanded( + flex: 6, + child: Stack( + alignment: Alignment.center, + children: [ + Positioned.fill( + child: CustomPaint( + painter: _WeightRulerPainter(), + ), + ), + SliderTheme( + data: SliderTheme.of(context).copyWith( + trackHeight: 6.h, + thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 0), + overlayShape: const RoundSliderOverlayShape(overlayRadius: 0), + activeTrackColor: AppColors.primaryRedColor, + inactiveTrackColor: AppColors.lightRedButtonColor, + ), + child: Slider( + min: 30, + max: 150, + value: weightValue, + onChanged: (v) { + setState(() => weightValue = v); + widget.onWeightChanged(v, widget.isKg); + }, + ), + ), + Positioned( + right: 0, + child: Container( + width: 18.w, + height: 18.h, + decoration: BoxDecoration( + color: AppColors.primaryRedColor, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(4.r), + ), + ), + ), + ], + ), + ), + Expanded(child: Container()), + ], + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + Center( + child: Text( + 'What is your weight?'.needTranslation, + style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor), + ), + ), + SizedBox(height: 20.h), + Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: _unitSelector(), + ), + SizedBox(height: 90.h), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + weightValue.round().toString(), + style: TextStyle(fontSize: 100.f, color: AppColors.textColor, height: 1), + ), + SizedBox(width: 8.w), + Text(widget.isKg ? 'kg' : 'lbs', style: TextStyle(fontWeight: FontWeight.w700, fontSize: 24.f)).paddingOnly(bottom: 10.h, left: 8.w), + ], + ), + ], + ); + } +} + +class _WeightRulerPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paintTick = Paint()..color = const Color(0xFF222222); + final paintSmall = Paint()..color = const Color(0xFF222222).withValues(alpha: 0.6); + + final width = size.width; + final start = 30; + final end = 150; + final steps = end - start; + for (int i = 0; i <= steps; i++) { + final x = (i / steps) * width; + if (i % 10 == 0) { + canvas.drawLine(Offset(x, size.height * 0.1), Offset(x, size.height * 0.6), paintTick); + } else if (i % 5 == 0) { + canvas.drawLine(Offset(x, size.height * 0.2), Offset(x, size.height * 0.5), paintSmall); + } else { + canvas.drawLine(Offset(x, size.height * 0.35), Offset(x, size.height * 0.5), paintSmall); + } + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart b/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart new file mode 100644 index 0000000..43d485a --- /dev/null +++ b/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart @@ -0,0 +1,209 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart'; +import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart'; +import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart'; +import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:provider/provider.dart'; + +/// Manages the user info selection flow with PageView +/// Only the page content changes, header and footer remain fixed +class UserInfoFlowManager extends StatefulWidget { + const UserInfoFlowManager({super.key}); + + @override + State createState() => _UserInfoFlowManagerState(); +} + +class _UserInfoFlowManagerState extends State { + final PageController _pageController = PageController(); + late SymptomsCheckerViewModel _viewModel; + + // Page titles + final List _pageTitles = [ + "Your Gender", + "Your Birth Date", + "Your Height", + "Your Weight", + ]; + + @override + void initState() { + super.initState(); + _viewModel = context.read(); + // _viewModel.resetUserInfo(); + } + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + void _onNext() { + if (_viewModel.userInfoCurrentPage < 3) { + _viewModel.nextUserInfoPage(); + _pageController.animateToPage( + _viewModel.userInfoCurrentPage, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } else { + // Submit and navigate to next screen + _submitUserInfo(); + } + } + + void _onPrevious() { + if (_viewModel.userInfoCurrentPage > 0) { + _viewModel.previousUserInfoPage(); + _pageController.animateToPage( + _viewModel.userInfoCurrentPage, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } else { + context.pop(); + } + } + + void _submitUserInfo() { + final userInfo = _viewModel.getUserInfoData(); + + // Log user info + log('User Info Submitted:'); + log('Gender: ${userInfo['gender']}'); + log('Age: ${userInfo['age']}'); + log('Height: ${userInfo['height']} ${userInfo['heightUnit']}'); + log('Weight: ${userInfo['weight']} ${userInfo['weightUnit']}'); + + // TODO: Save user info to backend/storage + + // Navigate to symptoms checker or next screen + context.pop(); + } + + Widget _buildProgressBar(int currentPage) { + return Row( + children: List.generate(4, (index) { + final isActive = index <= currentPage; + return Expanded( + child: Container( + height: 4.h, + margin: EdgeInsets.symmetric(horizontal: 6.w), + decoration: BoxDecoration( + color: isActive ? AppColors.primaryRedColor : AppColors.greyLightColor, + borderRadius: BorderRadius.circular(8.r), + ), + ), + ); + }), + ); + } + + Widget _buildStickyBottomCard(bool isLastPage) { + return Container( + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.vertical(top: Radius.circular(24.r)), + ), + padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 16.h), + child: SafeArea( + top: false, + child: Row( + children: [ + Expanded( + child: CustomButton( + text: "Previous".needTranslation, + onPressed: _onPrevious, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), + borderColor: Colors.transparent, + textColor: AppColors.primaryRedColor, + fontSize: 16.f, + ), + ), + SizedBox(width: 12.w), + Expanded( + child: CustomButton( + text: isLastPage ? "Submit".needTranslation : "Next".needTranslation, + onPressed: _onNext, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + ), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, viewModel, child) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: _pageTitles[viewModel.userInfoCurrentPage].needTranslation, + isLeading: true, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + _buildProgressBar(viewModel.userInfoCurrentPage), + SizedBox(height: 24.h), + SizedBox( + height: 600.h, + child: PageView( + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), // Disable swipe + onPageChanged: (index) { + viewModel.setUserInfoPage(index); + }, + children: [ + GenderSelectionPage( + selectedGender: viewModel.selectedGender, + onGenderSelected: viewModel.setGender, + ), + AgeSelectionPage( + selectedAge: viewModel.selectedAge, + onAgeSelected: viewModel.setAge, + ), + HeightSelectionPage( + height: viewModel.selectedHeight ?? 178, + isCm: viewModel.isHeightCm, + onHeightChanged: viewModel.setHeight, + ), + WeightSelectionPage( + weight: viewModel.selectedWeight ?? 70, + isKg: viewModel.isWeightKg, + onWeightChanged: viewModel.setWeight, + ), + ], + ), + ), + ], + ), + ), + ), + _buildStickyBottomCard(viewModel.isUserInfoLastPage), + ], + ), + ); + }, + ); + } +} diff --git a/lib/presentation/symptoms_checker/user_info_selection/widgets/user_info_progress_bar.dart b/lib/presentation/symptoms_checker/user_info_selection/widgets/user_info_progress_bar.dart new file mode 100644 index 0000000..6e6d762 --- /dev/null +++ b/lib/presentation/symptoms_checker/user_info_selection/widgets/user_info_progress_bar.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +/// Progress bar widget showing the current step in user info selection flow +/// Total steps: 4 (Gender -> Age -> Height -> Weight) +class UserInfoProgressBar extends StatelessWidget { + final int currentStep; + final int totalSteps; + + const UserInfoProgressBar({ + super.key, + required this.currentStep, + this.totalSteps = 4, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: List.generate(totalSteps, (index) { + final isActive = index < currentStep; + return Expanded( + child: Container( + height: 4.h, + margin: EdgeInsets.symmetric(horizontal: 6.w), + decoration: BoxDecoration( + color: isActive ? AppColors.primaryRedColor : AppColors.greyLightColor, + borderRadius: BorderRadius.circular(8.r), + ), + ), + ); + }), + ); + } +} + diff --git a/lib/presentation/symptoms_checker/user_info_selection/widgets/user_info_selection_scaffold.dart b/lib/presentation/symptoms_checker/user_info_selection/widgets/user_info_selection_scaffold.dart new file mode 100644 index 0000000..3390c31 --- /dev/null +++ b/lib/presentation/symptoms_checker/user_info_selection/widgets/user_info_selection_scaffold.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/widgets/user_info_progress_bar.dart'; +import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/widgets/user_info_sticky_bottom_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; + +/// Base scaffold for user info selection flow pages +/// Provides consistent layout with progress bar and sticky bottom card +class UserInfoSelectionScaffold extends StatelessWidget { + final String title; + final int currentStep; + final Widget child; + final VoidCallback? onPrevious; + final VoidCallback? onNext; + final bool showPrevious; + final String? nextButtonText; + final bool isScrollable; + + const UserInfoSelectionScaffold({ + super.key, + required this.title, + required this.currentStep, + required this.child, + this.onPrevious, + this.onNext, + this.showPrevious = true, + this.nextButtonText, + this.isScrollable = true, + }); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: title, + isLeading: true, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + UserInfoProgressBar(currentStep: currentStep), + SizedBox(height: 24.h), + isScrollable + ? SingleChildScrollView( + child: child, + ) + : child, + ], + ), + ), + ), + UserInfoStickyBottomCard( + onPrevious: onPrevious, + onNext: onNext, + showPrevious: showPrevious, + nextButtonText: nextButtonText, + ), + ], + ), + ); + } +} diff --git a/lib/presentation/symptoms_checker/user_info_selection/widgets/user_info_sticky_bottom_card.dart b/lib/presentation/symptoms_checker/user_info_selection/widgets/user_info_sticky_bottom_card.dart new file mode 100644 index 0000000..10fd934 --- /dev/null +++ b/lib/presentation/symptoms_checker/user_info_selection/widgets/user_info_sticky_bottom_card.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.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/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; + +/// Sticky bottom card with Previous/Next navigation buttons +class UserInfoStickyBottomCard extends StatelessWidget { + final VoidCallback? onPrevious; + final VoidCallback? onNext; + final bool showPrevious; + final String? nextButtonText; + + const UserInfoStickyBottomCard({ + super.key, + this.onPrevious, + this.onNext, + this.showPrevious = true, + this.nextButtonText, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 16.h), + Row( + children: [ + if (showPrevious) ...[ + Expanded( + child: CustomButton( + text: "Previous".needTranslation, + onPressed: onPrevious ?? () => context.pop(), + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), + borderColor: Colors.transparent, + textColor: AppColors.primaryRedColor, + fontSize: 16.f, + ), + ), + SizedBox(width: 12.w), + ], + Expanded( + child: CustomButton( + text: nextButtonText ?? "Next".needTranslation, + onPressed: onNext ?? () {}, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + ), + ), + ], + ), + SizedBox(height: 24.h), + ], + ).paddingSymmetrical(24.w, 0), + ); + } +} + diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index f73eba3..183c2a0 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -3,6 +3,7 @@ import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart'; import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart'; import 'package:hmg_patient_app_new/presentation/e_referral/new_e_referral.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; @@ -14,6 +15,8 @@ import 'package:hmg_patient_app_new/presentation/symptoms_checker/risk_factors_s import 'package:hmg_patient_app_new/presentation/symptoms_checker/suggestions_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/symptoms_selector_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/triage_screen.dart'; +import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection.dart'; +import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart'; import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; @@ -30,14 +33,21 @@ class AppRoutes { static const String zoomCallPage = '/zoomCallPage'; static const String bloodDonationPage = '/bloodDonationPage'; + //appointments + static const String bookAppointmentPage = '/bookAppointmentPage'; + // Symptoms Checker static const String organSelectorPage = '/organSelectorPage'; - static const String symptomsCheckerScreen = '/symptomsCheckerScreen'; + static const String symptomsSelectorScreen = '/symptomsCheckerScreen'; static const String suggestionsScreen = '/suggestionsScreen'; static const String riskFactorsScreen = '/riskFactorsScreen'; static const String possibleConditionsScreen = '/possibleConditionsScreen'; static const String triageScreen = '/triageProgressScreen'; + //UserInfoSelection + static const String userInfoSelection = '/userInfoSelection'; + static const String userInfoFlowManager = '/userInfoFlowManager'; + static Map get routes => { initialRoute: (context) => SplashPage(), loginScreen: (context) => LoginScreen(), @@ -50,12 +60,16 @@ class AppRoutes { comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(), homeHealthCarePage: (context) => HhcProceduresPage(), organSelectorPage: (context) => OrganSelectorPage(), - symptomsCheckerScreen: (context) => SymptomsSelectorScreen(), + symptomsSelectorScreen: (context) => SymptomsSelectorScreen(), riskFactorsScreen: (context) => RiskFactorsScreen(), suggestionsScreen: (context) => SuggestionsScreen(), possibleConditionsScreen: (context) => PossibleConditionsScreen(), - triageScreen: (context) => TriageScreen() - zoomCallPage: (context) => CallScreen(), - bloodDonationPage: (context) => BloodDonationPage() + triageScreen: (context) => TriageScreen(), + bloodDonationPage: (context) => BloodDonationPage(), + bookAppointmentPage: (context) => BookAppointmentPage(), + userInfoSelection: (context) => UserInfoSelectionScreen(), + userInfoFlowManager: (context) => UserInfoFlowManager(), + + // }; } diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 3e20689..ad8e743 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -26,7 +26,7 @@ class CollapsingListView extends StatelessWidget { Widget? trailing; bool isClose; bool isLeading; - VoidCallback? onLeadingTapped; + VoidCallback? leadingCallback; CollapsingListView({ super.key, @@ -42,7 +42,7 @@ class CollapsingListView extends StatelessWidget { this.requests, this.isLeading = true, this.trailing, - this.onLeadingTapped, + this.leadingCallback, }); @override @@ -69,8 +69,8 @@ class CollapsingListView extends StatelessWidget { icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 32.h, height: 32.h), padding: EdgeInsets.only(left: 12), onPressed: () { - if (onLeadingTapped != null) { - onLeadingTapped!(); + if (leadingCallback != null) { + leadingCallback!(); } else { context.pop(); } diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index c1a38ab..7992ece 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -215,8 +215,11 @@ class TextInputWidget extends StatelessWidget { language: appState.getLanguageCode()!, initialDate: DateTime.now(), fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "Poppins", - okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), - cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), + okWidget: + Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), + cancelWidget: Padding( + padding: EdgeInsets.only(right: 8.h), + child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), onCalendarTypeChanged: (bool value) { isGregorian = value; }); @@ -276,7 +279,12 @@ class TextInputWidget extends StatelessWidget { decoration: InputDecoration( isDense: true, hintText: hintText, - hintStyle: TextStyle(fontSize: 14.f, height: 21 / 16, fontWeight: FontWeight.w500, color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), letterSpacing: -0.75), + hintStyle: TextStyle( + fontSize: 14.f, + height: 21 / 16, + fontWeight: FontWeight.w500, + color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), + letterSpacing: -0.75), prefixIconConstraints: BoxConstraints(minWidth: 30.h), prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500), contentPadding: EdgeInsets.zero,