Merge branch 'dev_aamir' into faiz_dev1
# Conflicts: # lib/features/authentication/authentication_view_model.dartpull/12/head
						commit
						488636a13f
					
				| @ -0,0 +1,315 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/gestures.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_state.dart'; | ||||
| import 'package:hmg_patient_app_new/core/dependencies.dart'; | ||||
| import 'package:hmg_patient_app_new/core/enums.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/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; | ||||
| import 'package:hmg_patient_app_new/theme/colors.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart' show CustomButton; | ||||
| import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/input_widget.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/otp/otp.dart'; | ||||
| 
 | ||||
| class RegisterNew extends StatefulWidget { | ||||
|   @override | ||||
|   _RegisterNew createState() => _RegisterNew(); | ||||
| } | ||||
| 
 | ||||
| class _RegisterNew extends State<RegisterNew> { | ||||
|   bool isTermsAccepted = true; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void dispose() { | ||||
|     super.dispose(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     AppState appState = getIt.get<AppState>(); | ||||
|     return Scaffold( | ||||
|         backgroundColor: AppColors.bgScaffoldColor, | ||||
|         appBar: CustomAppBar( | ||||
|           onBackPressed: () { | ||||
|             Navigator.of(context).pop(); | ||||
|           }, | ||||
|           onLanguageChanged: (String value) { | ||||
|             // context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US')); | ||||
|           }, | ||||
|         ), | ||||
|         body: GestureDetector( | ||||
|           onTap: () { | ||||
|             FocusScope.of(context).unfocus(); | ||||
|           }, | ||||
|           child: ScrollConfiguration( | ||||
|             behavior: ScrollConfiguration.of(context).copyWith(overscroll: false, physics: const ClampingScrollPhysics()), | ||||
|             child: NotificationListener<OverscrollIndicatorNotification>( | ||||
|               onNotification: (notification) { | ||||
|                 notification.disallowIndicator(); | ||||
|                 return true; | ||||
|               }, | ||||
|               child: SingleChildScrollView( | ||||
|                 physics: ClampingScrollPhysics(), | ||||
|                 padding: EdgeInsets.symmetric(horizontal: 24.h), | ||||
|                 child: Column( | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     Utils.showLottie(context: context, | ||||
|                         assetPath: 'assets/animations/lottie/register.json', | ||||
|                         width: 200.h, | ||||
|                         height: 200.h, | ||||
|                         fit: BoxFit.cover, | ||||
|                         repeat: true), | ||||
|                     SizedBox(height: 16.h), | ||||
|                     LocaleKeys.prepareToElevate.tr().toText32(isBold: true), | ||||
|                     SizedBox(height: 24.h), | ||||
|                     Directionality( | ||||
|                       textDirection: Directionality.of(context), | ||||
|                       child: Container( | ||||
|                         decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)), | ||||
|                         padding: EdgeInsets.symmetric(horizontal: 16.h), | ||||
|                         child: Column( | ||||
|                           children: [ | ||||
|                             CustomCountryDropdown( | ||||
|                               countryList: CountryEnum.values, | ||||
|                               onCountryChange: (CountryEnum? value) {}, | ||||
|                               isRtl: Directionality.of(context) == TextDirection.LTR, | ||||
|                             ).withVerticalPadding(8.h), | ||||
|                             Divider(height: 1.h), | ||||
|                             TextInputWidget( | ||||
|                                 labelText: LocaleKeys.nationalIdNumber.tr(), | ||||
|                                 hintText: "xxxxxxxxx", | ||||
|                                 controller: TextEditingController(), | ||||
|                                 isEnable: true, | ||||
|                                 prefix: null, | ||||
|                                 isAllowRadius: true, | ||||
|                                 isBorderAllowed: false, | ||||
|                                 isAllowLeadingIcon: true, | ||||
|                                 autoFocus: true, | ||||
|                                 padding: EdgeInsets.symmetric(vertical: 8.h), | ||||
|                                 leadingIcon: AppAssets.student_card, | ||||
|                                 onChange: (value) { | ||||
|                                   print(value); | ||||
|                                 }).withVerticalPadding(8), | ||||
|                             Divider(height: 1), | ||||
|                             TextInputWidget( | ||||
|                               labelText: LocaleKeys.dob.tr(), | ||||
|                               hintText: "11 July, 1994", | ||||
|                               controller: TextEditingController(), | ||||
|                               isEnable: true, | ||||
|                               prefix: null, | ||||
|                               isAllowRadius: true, | ||||
|                               isBorderAllowed: false, | ||||
|                               isAllowLeadingIcon: true, | ||||
|                               padding: EdgeInsets.symmetric(vertical: 8.h), | ||||
|                               leadingIcon: AppAssets.birthday_cake, | ||||
|                               onChange: (value) {}, | ||||
|                             ).withVerticalPadding(8), | ||||
|                           ], | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                     SizedBox(height: 25.h), | ||||
|                     GestureDetector( | ||||
|                       onTap: () {}, | ||||
|                       child: Row( | ||||
|                         children: [ | ||||
|                           AnimatedContainer( | ||||
|                             duration: const Duration(milliseconds: 200), | ||||
|                             height: 24.h, | ||||
|                             width: 24.h, | ||||
|                             decoration: BoxDecoration( | ||||
|                               color: isTermsAccepted ? const Color(0xFFE92227) : Colors.transparent, | ||||
|                               borderRadius: BorderRadius.circular(6), | ||||
|                               border: Border.all( | ||||
|                                 color: isTermsAccepted ? const Color(0xFFE92227) : Colors.grey, | ||||
|                                 width: 2.h, | ||||
|                               ), | ||||
|                             ), | ||||
|                             child: isTermsAccepted ? Icon(Icons.check, size: 16.fSize, color: Colors.white) : null, | ||||
|                           ), | ||||
|                           SizedBox(width: 12.h), | ||||
|                           Expanded( | ||||
|                             child: Text( | ||||
|                               LocaleKeys.iAcceptTermsConditions.tr(), | ||||
|                               style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), | ||||
|                             ), | ||||
|                           ), | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|                     SizedBox(height: 25.h), | ||||
|                     CustomButton( | ||||
|                       text: "Register", | ||||
|                       icon: AppAssets.note_edit, | ||||
|                       onPressed: () { | ||||
|                         showRegisterModel(context: context); | ||||
|                       }, | ||||
|                     ), | ||||
|                     SizedBox(height: 14), | ||||
|                     Center( | ||||
|                       child: RichText( | ||||
|                         textAlign: TextAlign.center, | ||||
|                         text: TextSpan( | ||||
|                           style: context.dynamicTextStyle( | ||||
|                             color: Colors.black, | ||||
|                             fontSize: 16.fSize, | ||||
|                             height: 26 / 16, | ||||
|                             fontWeight: FontWeight.w500, | ||||
|                           ), | ||||
|                           children: <TextSpan>[ | ||||
|                             TextSpan(text: LocaleKeys.alreadyHaveAccount.tr(), style: context.dynamicTextStyle()), | ||||
|                             TextSpan(text: "  "), | ||||
|                             TextSpan( | ||||
|                               text: LocaleKeys.loginNow.tr(), | ||||
|                               style: context.dynamicTextStyle( | ||||
|                                 color: AppColors.primaryRedColor, | ||||
|                                 fontSize: 16.fSize, | ||||
|                                 height: 26 / 16, | ||||
|                                 fontWeight: FontWeight.w500, | ||||
|                               ), | ||||
|                               recognizer: TapGestureRecognizer() | ||||
|                                 ..onTap = () { | ||||
|                                   Navigator.of(context).pop(); | ||||
|                                 }, | ||||
|                             ), | ||||
|                           ], | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                     SizedBox(height: 30), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         )); | ||||
|   } | ||||
| 
 | ||||
|   void showRegisterModel({required BuildContext context, TextEditingController? textController}) { | ||||
|     showModalBottomSheet( | ||||
|       context: context, | ||||
|       isScrollControlled: true, | ||||
|       isDismissible: false, | ||||
|       useSafeArea: true, | ||||
|       backgroundColor: Colors.transparent, | ||||
|       builder: (bottomSheetContext) => | ||||
|           Padding( | ||||
|             padding: EdgeInsets.only(bottom: MediaQuery | ||||
|                 .of(bottomSheetContext) | ||||
|                 .viewInsets | ||||
|                 .bottom), | ||||
|             child: SingleChildScrollView( | ||||
|               child: GenericBottomSheet( | ||||
|                 countryCode: "966", | ||||
|                 initialPhoneNumber: "", | ||||
|                 textController: TextEditingController(), | ||||
|                 onChange: (String? value) {}, | ||||
|                 buttons: [ | ||||
|                   Padding( | ||||
|                     padding: const EdgeInsets.only(bottom: 10), | ||||
|                     child: CustomButton( | ||||
|                       text: LocaleKeys.sendOTPSMS.tr(), | ||||
|                       onPressed: () { | ||||
|                         Navigator.of(context).push( | ||||
|                             MaterialPageRoute(builder: (BuildContext context) => OTPVerificationPage(phoneNumber: '12234567',))); | ||||
| 
 | ||||
| 
 | ||||
|                             // if (mobileNo.isEmpty) { | ||||
|                             //   context.showBottomSheet( | ||||
|                             //     child: ExceptionBottomSheet( | ||||
|                             //       message: TranslationBase.of(context).pleaseEnterMobile, | ||||
|                             //       showCancel: false, | ||||
|                             //       onOkPressed: () { | ||||
|                             //         Navigator.of(context).pop(); | ||||
|                             //       }, | ||||
|                             //     ), | ||||
|                             //   ); | ||||
|                             // } else if (!Utils.validateMobileNumber(mobileNo)) { | ||||
|                             //   context.showBottomSheet( | ||||
|                             //     child: ExceptionBottomSheet( | ||||
|                             //       message: TranslationBase.of(context).pleaseEnterValidMobile, | ||||
|                             //       showCancel: false, | ||||
|                             //       onOkPressed: () { | ||||
|                             //         Navigator.of(context).pop(); | ||||
|                             //       }, | ||||
|                             //     ), | ||||
|                             //   ); | ||||
|                             // } else { | ||||
|                             //   registerUser(1); | ||||
|                             // } | ||||
|                             }, | ||||
|                       backgroundColor: AppColors.primaryRedColor, | ||||
|                       borderColor: AppColors.primaryRedBorderColor, | ||||
|                       textColor: AppColors.whiteColor, | ||||
|                       icon: AppAssets.message, | ||||
|                     ), | ||||
|                   ), | ||||
|                   Row( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                     mainAxisAlignment: MainAxisAlignment.center, | ||||
|                     children: [ | ||||
|                       Padding( | ||||
|                         padding: EdgeInsets.symmetric(horizontal: 8.h), | ||||
|                         child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                   Padding( | ||||
|                     padding: EdgeInsets.only(bottom: 10.h, top: 10.h), | ||||
|                     child: CustomButton( | ||||
|                       text: LocaleKeys.sendOTPWHATSAPP.tr(), | ||||
|                       onPressed: () { | ||||
|                         // if (mobileNo.isEmpty) { | ||||
|                         //   context.showBottomSheet( | ||||
|                         //     child: ExceptionBottomSheet( | ||||
|                         //       message: TranslationBase.of(context).pleaseEnterMobile, | ||||
|                         //       showCancel: false, | ||||
|                         //       onOkPressed: () { | ||||
|                         //         Navigator.of(context).pop(); | ||||
|                         //       }, | ||||
|                         //     ), | ||||
|                         //   ); | ||||
|                         // } else if (!Utils.validateMobileNumber(mobileNo)) { | ||||
|                         //   context.showBottomSheet( | ||||
|                         //     child: ExceptionBottomSheet( | ||||
|                         //       message: TranslationBase.of(context).pleaseEnterValidMobile, | ||||
|                         //       showCancel: false, | ||||
|                         //       onOkPressed: () { | ||||
|                         //         Navigator.of(context).pop(); | ||||
|                         //       }, | ||||
|                         //     ), | ||||
|                         //   ); | ||||
|                         // } else { | ||||
|                         //   registerUser(4); | ||||
|                         // } | ||||
|                         // int? val = Utils.onOtpBtnPressed(OTPType.whatsapp, mobileNo, context); | ||||
|                         // registerUser(val); | ||||
|                       }, | ||||
|                       backgroundColor: AppColors.whiteColor, | ||||
|                       borderColor: AppColors.borderOnlyColor, | ||||
|                       textColor: AppColors.textColor, | ||||
|                       icon: AppAssets.whatsapp, | ||||
|                     ), | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,111 @@ | ||||
| import 'dart:io'; | ||||
| 
 | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| 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/string_extensions.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 ExceptionBottomSheet extends StatelessWidget { | ||||
|   String message; | ||||
|   bool showOKButton; | ||||
|   bool showCancel; | ||||
|   Function() onOkPressed; | ||||
|   Function()? onCancelPressed; | ||||
| 
 | ||||
|   ExceptionBottomSheet({Key? key, required this.message, this.showOKButton = true, this.showCancel = false, required this.onOkPressed, this.onCancelPressed}) : super(key: key); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SafeArea( | ||||
|       bottom: Platform.isIOS ? false : true, // Adjust for iOS to avoid bottom padding | ||||
|       child: GestureDetector( | ||||
|         onTap: () { | ||||
|           FocusScope.of(context).unfocus(); // Dismiss the keyboard when tapping outside | ||||
|         }, | ||||
|         child: Builder(builder: (context) { | ||||
|           return Directionality( | ||||
|             textDirection: Directionality.of(context), | ||||
|             child: Container( | ||||
|               padding: EdgeInsets.all(24.h), | ||||
|               decoration: BoxDecoration( | ||||
|                 color: Color(0xFFF8F8FA), | ||||
|                 borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), | ||||
|               ), | ||||
|               child: Column( | ||||
|                 mainAxisSize: MainAxisSize.min, | ||||
|                 mainAxisAlignment: MainAxisAlignment.start, | ||||
|                 crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                 children: [ | ||||
|                   Row( | ||||
|                     mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|                     children: [ | ||||
|                       LocaleKeys.notice.tr().toText28(), | ||||
|                       InkWell( | ||||
|                         onTap: () { | ||||
|                           Navigator.of(context).pop(); | ||||
|                         }, | ||||
|                         child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle), | ||||
|                       ) | ||||
|                     ], | ||||
|                   ), | ||||
|                   SizedBox(height: 10.h), | ||||
|                   (message ?? "").toText16(isBold: false, color: AppColors.textColor), | ||||
|                   SizedBox(height: 10.h), | ||||
|                   SizedBox(height: 24.h), | ||||
|                   if (showOKButton && showCancel) | ||||
|                     Row( | ||||
|                       children: [ | ||||
|                         Expanded( | ||||
|                           child: CustomButton( | ||||
|                             text: LocaleKeys.cancel.tr(), | ||||
|                             onPressed: onCancelPressed != null | ||||
|                                 ? onCancelPressed! | ||||
|                                 : () { | ||||
|                                     Navigator.of(context).pop(); | ||||
|                                   }, | ||||
|                             backgroundColor: AppColors.secondaryLightRedColor, | ||||
|                             borderColor: AppColors.secondaryLightRedColor, | ||||
|                             textColor: AppColors.primaryRedColor, | ||||
|                             icon: AppAssets.cancel, | ||||
|                             iconColor: AppColors.primaryRedColor, | ||||
|                           ), | ||||
|                         ), | ||||
|                         SizedBox(width: 5.h), | ||||
|                         Expanded( | ||||
|                           child: CustomButton( | ||||
|                             text: showCancel ? LocaleKeys.confirm.tr() : LocaleKeys.ok.tr(), | ||||
|                             onPressed: onOkPressed, | ||||
|                             backgroundColor: AppColors.bgGreenColor, | ||||
|                             borderColor: AppColors.bgGreenColor, | ||||
|                             textColor: Colors.white, | ||||
|                             icon: AppAssets.confirm, | ||||
|                           ), | ||||
|                         ), | ||||
|                       ], | ||||
|                     ), | ||||
|                   if (showOKButton && !showCancel) | ||||
|                     Padding( | ||||
|                       padding: EdgeInsets.only(bottom: 10.h), | ||||
|                       child: CustomButton( | ||||
|                         text: LocaleKeys.ok.tr(), | ||||
|                         onPressed: onOkPressed, | ||||
|                         backgroundColor: AppColors.primaryRedColor, | ||||
|                         borderColor: AppColors.primaryRedBorderColor, | ||||
|                         textColor: Colors.white, | ||||
|                         icon: AppAssets.confirm, | ||||
|                       ), | ||||
|                     ), | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|           ); | ||||
|         }), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,145 @@ | ||||
| import 'dart:io'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| import 'package:hmg_patient_app_new/core/enums.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/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; | ||||
| import 'package:hmg_patient_app_new/theme/colors.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/input_widget.dart'; | ||||
| 
 | ||||
| class GenericBottomSheet extends StatefulWidget { | ||||
|   String? countryCode; | ||||
|   String? initialPhoneNumber; | ||||
|   final List<Widget> buttons; | ||||
|   TextEditingController? textController; | ||||
|   final bool isForEmail; | ||||
|   Function(CountryEnum)? onCountryChange; | ||||
|   final bool isEnableCountryDropdown; | ||||
|   final bool isFromSavedLogin; | ||||
|   Function(String?)? onChange; | ||||
| 
 | ||||
|   // FocusNode myFocusNode; | ||||
| 
 | ||||
|   GenericBottomSheet({ | ||||
|     this.countryCode = "", | ||||
|     this.initialPhoneNumber = "", | ||||
|     required this.buttons, | ||||
|     this.textController, | ||||
|     this.isForEmail = false, | ||||
|     this.onCountryChange, | ||||
|     this.isEnableCountryDropdown = false, | ||||
|     this.isFromSavedLogin = false, | ||||
|     this.onChange, | ||||
|     // required this.myFocusNode | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   _GenericBottomSheetState createState() => _GenericBottomSheetState(); | ||||
| } | ||||
| 
 | ||||
| class _GenericBottomSheetState extends State<GenericBottomSheet> { | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
| 
 | ||||
|     if (!widget.isForEmail) { | ||||
|       widget.textController = TextEditingController(text: widget.initialPhoneNumber); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void dispose() { | ||||
|     super.dispose(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SafeArea( | ||||
|       top: false, | ||||
|       bottom: Platform.isIOS ? false : true, | ||||
|       child: GestureDetector( | ||||
|         onTap: () { | ||||
|           FocusScope.of(context).unfocus(); | ||||
|         }, | ||||
|         child: Directionality( | ||||
|           textDirection: Directionality.of(context), | ||||
|           child: Container( | ||||
|             padding: EdgeInsets.all(24.h), | ||||
|             decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.bgScaffoldColor, borderRadius: 16), | ||||
|             child: Column( | ||||
|               mainAxisSize: MainAxisSize.min, | ||||
|               mainAxisAlignment: MainAxisAlignment.start, | ||||
|               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|               children: [ | ||||
|                 // Title | ||||
|                 Row( | ||||
|                   mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     Flexible( | ||||
|                         child: widget.isFromSavedLogin | ||||
|                             ? LocaleKeys.receiveOtpToast.tr().toText24() | ||||
|                             : widget.isForEmail | ||||
|                                 ? LocaleKeys.enterEmail.tr().toText24() | ||||
|                                 : LocaleKeys.enterPhoneNumber.tr().toText24()), | ||||
|                     InkWell( | ||||
|                       onTap: () { | ||||
|                         Navigator.of(context).pop(); | ||||
|                       }, | ||||
|                       child: Padding( | ||||
|                         padding: EdgeInsets.only(top: 10.h), | ||||
|                         child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|                 SizedBox(height: 8.h), | ||||
|                 // Subtitle | ||||
|                 widget.isFromSavedLogin | ||||
|                     ? LocaleKeys.pleaseChooseOption.tr().toText16() | ||||
|                     : widget.isForEmail | ||||
|                         ? LocaleKeys.enterEmailDesc.tr().toText16() | ||||
|                         : LocaleKeys.enterPhoneDesc.tr().toText16(), | ||||
| 
 | ||||
|                 if (widget.isFromSavedLogin) | ||||
|                   ...[] | ||||
|                 else ...[ | ||||
|                   widget.textController != null | ||||
|                       ? TextInputWidget( | ||||
|                           labelText: widget.isForEmail ? LocaleKeys.email : LocaleKeys.phoneNumber, | ||||
|                           hintText: widget.isForEmail ? "demo@gmail.com" : "5xxxxxxxx", | ||||
|                           controller: widget.textController!, | ||||
|                           padding: EdgeInsets.all(8.h), | ||||
|                           keyboardType: widget.isForEmail ? TextInputType.emailAddress : TextInputType.number, | ||||
|                           onChange: (value) { | ||||
|                             widget.textController!.text = value!; | ||||
|                             if (widget.onChange != null) { | ||||
|                               widget.onChange!(value); | ||||
|                             } | ||||
|                           }, | ||||
|                           isEnable: true, | ||||
|                           // focusNode: widget.myFocusNode, | ||||
|                           isReadOnly: widget.isFromSavedLogin, | ||||
|                           prefix: widget.isForEmail ? null : widget.countryCode, | ||||
|                           isBorderAllowed: false, | ||||
|                           isAllowLeadingIcon: true, | ||||
|                           isCountryDropDown: widget.isEnableCountryDropdown, | ||||
|                           leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone, | ||||
|                         ) | ||||
|                       : SizedBox(), | ||||
|                 ], | ||||
| 
 | ||||
|                 SizedBox(height: 24.h), | ||||
|                 ...widget.buttons, | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,165 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| import 'package:hmg_patient_app_new/core/enums.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/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| 
 | ||||
| class CustomCountryDropdown extends StatefulWidget { | ||||
|   final List<CountryEnum> countryList; | ||||
|   final Function(CountryEnum)? onCountryChange; | ||||
|   final bool isRtl; | ||||
| 
 | ||||
|   const CustomCountryDropdown({ | ||||
|     Key? key, | ||||
|     required this.countryList, | ||||
|     this.onCountryChange, | ||||
|     required this.isRtl, | ||||
|   }) : super(key: key); | ||||
| 
 | ||||
|   @override | ||||
|   _CustomCountryDropdownState createState() => _CustomCountryDropdownState(); | ||||
| } | ||||
| 
 | ||||
| class _CustomCountryDropdownState extends State<CustomCountryDropdown> { | ||||
|   CountryEnum? selectedCountry; | ||||
|   late OverlayEntry _overlayEntry; | ||||
|   bool _isDropdownOpen = false; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     selectedCountry = CountryEnum.saudiArabia; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return GestureDetector( | ||||
|       onTap: () { | ||||
|         if (_isDropdownOpen) { | ||||
|           _closeDropdown(); | ||||
|         } else { | ||||
|           _openDropdown(); | ||||
|         } | ||||
|       }, | ||||
|       child: Container( | ||||
|         height: 40.h, | ||||
|         decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h), | ||||
|         child: Row( | ||||
|           children: [ | ||||
|             Utils.buildSvgWithAssets(icon: selectedCountry != null ? selectedCountry!.iconPath : AppAssets.ksa, width: 40.h, height: 40.h), | ||||
|             SizedBox(width: 8.h), | ||||
|             Utils.buildSvgWithAssets(icon: _isDropdownOpen ? AppAssets.dropdow_icon : AppAssets.dropdow_icon), | ||||
|             SizedBox(width: 4.h), | ||||
|             Text( | ||||
|               selectedCountry != null ? selectedCountry!.displayName : "Select Country", | ||||
|               style: TextStyle( | ||||
|                 fontSize: 14.fSize, | ||||
|                 height: 21 / 14, | ||||
|                 fontWeight: FontWeight.w500, | ||||
|                 letterSpacing: -0.2, | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   void _openDropdown() { | ||||
|     RenderBox renderBox = context.findRenderObject() as RenderBox; | ||||
|     Offset offset = renderBox.localToGlobal(Offset.zero); | ||||
| 
 | ||||
|     _overlayEntry = OverlayEntry( | ||||
|       builder: (context) => Stack( | ||||
|         children: [ | ||||
|           // Dismiss dropdown when tapping outside | ||||
|           Positioned.fill( | ||||
|             child: GestureDetector( | ||||
|               onTap: _closeDropdown, | ||||
|               behavior: HitTestBehavior.translucent, | ||||
|               child: Container(), | ||||
|             ), | ||||
|           ), | ||||
|           Positioned( | ||||
|             top: offset.dy + renderBox.size.height, | ||||
|             left: widget.isRtl ? offset.dx + 15.h : offset.dx - 15.h, | ||||
|             width: renderBox.size.width, | ||||
|             child: Material( | ||||
|               child: Container( | ||||
|                 decoration: RoundedRectangleBorder().toSmoothCornerDecoration( | ||||
|                   color: Colors.white, | ||||
|                   borderRadius: 12, | ||||
|                 ), | ||||
|                 // decoration: BoxDecoration( | ||||
|                 //   borderRadius: BorderRadius.circular(12), | ||||
|                 //   boxShadow: [ | ||||
|                 //     BoxShadow( | ||||
|                 //       color: Color(0xFFF8F8FA), | ||||
|                 //       blurRadius: 8.h, | ||||
|                 //       offset: Offset( | ||||
|                 //         0, | ||||
|                 //         2, | ||||
|                 //       ), | ||||
|                 //     ), | ||||
|                 //   ], | ||||
|                 // ), | ||||
|                 child: Column( | ||||
|                   children: widget.countryList | ||||
|                       .map( | ||||
|                         (country) => GestureDetector( | ||||
|                           onTap: () { | ||||
|                             setState(() { | ||||
|                               selectedCountry = country; | ||||
|                             }); | ||||
|                             widget.onCountryChange?.call(country); | ||||
|                             _closeDropdown(); | ||||
|                           }, | ||||
|                           child: Container( | ||||
|                             padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 16.h), | ||||
|                             decoration: RoundedRectangleBorder().toSmoothCornerDecoration( | ||||
|                               borderRadius: 16.h, | ||||
|                             ), | ||||
|                             child: Row( | ||||
|                               children: [ | ||||
|                                 Utils.buildSvgWithAssets( | ||||
|                                   icon: country.iconPath, | ||||
|                                   width: 38.h, | ||||
|                                   height: 38.h, | ||||
|                                 ), | ||||
|                                 SizedBox(width: 12.h), | ||||
|                                 Text(country.displayName, | ||||
|                                     style: TextStyle( | ||||
|                                       fontSize: 14.fSize, | ||||
|                                       height: 21 / 14, | ||||
|                                       fontWeight: FontWeight.w500, | ||||
|                                       letterSpacing: -0.2, | ||||
|                                     )), | ||||
|                               ], | ||||
|                             ), | ||||
|                           ), | ||||
|                         ), | ||||
|                       ) | ||||
|                       .toList(), | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
| 
 | ||||
|     Overlay.of(context)?.insert(_overlayEntry); | ||||
|     setState(() { | ||||
|       _isDropdownOpen = true; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   void _closeDropdown() { | ||||
|     _overlayEntry.remove(); | ||||
|     setState(() { | ||||
|       _isDropdownOpen = false; | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,227 @@ | ||||
| import 'dart:async'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/theme/colors.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; | ||||
| 
 | ||||
| class OTPVerificationPage extends StatefulWidget { | ||||
|   final String phoneNumber; | ||||
| 
 | ||||
|   const OTPVerificationPage({Key? key, required this.phoneNumber}) : super(key: key); | ||||
| 
 | ||||
|   @override | ||||
|   State<OTPVerificationPage> createState() => _OTPVerificationPageState(); | ||||
| } | ||||
| 
 | ||||
| class _OTPVerificationPageState extends State<OTPVerificationPage> { | ||||
|   final int _otpLength = 4; | ||||
|   late final List<TextEditingController> _controllers; | ||||
|   late final List<FocusNode> _focusNodes; | ||||
| 
 | ||||
|   Timer? _resendTimer; | ||||
|   int _resendTime = 60; | ||||
|   bool _isOtpComplete = false; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _controllers = List.generate(_otpLength, (_) => TextEditingController()); | ||||
|     _focusNodes = List.generate(_otpLength, (_) => FocusNode()); | ||||
|     _startResendTimer(); | ||||
| 
 | ||||
|     // Focus the first field once the screen is built | ||||
|     WidgetsBinding.instance.addPostFrameCallback((_) { | ||||
|       if (_focusNodes.isNotEmpty) { | ||||
|         FocusScope.of(context).requestFocus(_focusNodes[0]); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void dispose() { | ||||
|     for (final c in _controllers) c.dispose(); | ||||
|     for (final f in _focusNodes) f.dispose(); | ||||
|     _resendTimer?.cancel(); | ||||
|     super.dispose(); | ||||
|   } | ||||
| 
 | ||||
|   void _startResendTimer() { | ||||
|     _resendTimer = Timer.periodic(const Duration(seconds: 1), (timer) { | ||||
|       if (_resendTime > 0) { | ||||
|         setState(() => _resendTime--); | ||||
|       } else { | ||||
|         timer.cancel(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   void _onOtpChanged(int index, String value) { | ||||
|     if (value.length == 1 && index < _otpLength - 1) { | ||||
|       _focusNodes[index + 1].requestFocus(); | ||||
|     } else if (value.isEmpty && index > 0) { | ||||
|       _focusNodes[index - 1].requestFocus(); | ||||
|     } | ||||
|     _checkOtpCompletion(); | ||||
|   } | ||||
| 
 | ||||
|   void _checkOtpCompletion() { | ||||
|     final isComplete = _controllers.every((c) => c.text.isNotEmpty); | ||||
| 
 | ||||
|     if (isComplete != _isOtpComplete) { | ||||
|       setState(() => _isOtpComplete = isComplete); | ||||
| 
 | ||||
|       if (isComplete) { | ||||
|         _verifyOtp(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void _resendOtp() { | ||||
|     if (_resendTime == 0) { | ||||
|       setState(() => _resendTime = 60); | ||||
|       _startResendTimer(); | ||||
|       autoFillOtp("1234"); | ||||
| 
 | ||||
|       // call resend API here | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   String _getMaskedPhoneNumber() { | ||||
|     final phone = widget.phoneNumber; | ||||
|     return phone.length > 4 ? '05xxxxxx${phone.substring(phone.length - 2)}' : phone; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Scaffold( | ||||
|       appBar: CustomAppBar( | ||||
|         hideLogoAndLang: true, | ||||
|         onBackPressed: () { | ||||
|           Navigator.of(context).pop(); | ||||
|         }, | ||||
|         onLanguageChanged: (lang) {}, | ||||
|       ), | ||||
|       body: SingleChildScrollView( | ||||
|         child: Padding( | ||||
|           padding: EdgeInsets.symmetric(horizontal: 24.h), | ||||
|           child: Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             children: [ | ||||
|               SizedBox(height: 40.h), | ||||
|               Text( | ||||
|                 'OTP Verification', | ||||
|                 style: TextStyle(fontSize: 24.fSize, fontWeight: FontWeight.bold), | ||||
|               ), | ||||
|               SizedBox(height: 16.h), | ||||
|               Text( | ||||
|                 'We have sent you the OTP code on ${_getMaskedPhoneNumber()} via SMS for registration verification', | ||||
|                 style: TextStyle(fontSize: 16.fSize, color: Colors.grey), | ||||
|               ), | ||||
|               SizedBox(height: 40.h), | ||||
| 
 | ||||
|               // OTP Input Fields | ||||
|               SizedBox( | ||||
|                 height: 100, | ||||
|                 child: Row( | ||||
|                   mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||||
|                   children: List.generate(_otpLength, (index) { | ||||
|                     return ValueListenableBuilder<TextEditingValue>( | ||||
|                       valueListenable: _controllers[index], | ||||
|                       builder: (context, value, _) { | ||||
|                         final hasText = value.text.isNotEmpty; | ||||
| 
 | ||||
|                         return AnimatedContainer( | ||||
|                           duration: const Duration(milliseconds: 200), | ||||
|                           curve: Curves.easeInOut, | ||||
|                           width: 70.h, | ||||
|                           margin: EdgeInsets.symmetric(horizontal: 4.h), | ||||
|                           decoration: RoundedRectangleBorder() | ||||
|                               .toSmoothCornerDecoration(color: _isOtpComplete ? AppColors.successColor : (hasText ? AppColors.blackBgColor : AppColors.whiteColor), borderRadius: 16), | ||||
|                           child: Center( | ||||
|                             child: TextField( | ||||
|                               controller: _controllers[index], | ||||
|                               focusNode: _focusNodes[index], | ||||
|                               textAlign: TextAlign.center, | ||||
|                               keyboardType: TextInputType.number, | ||||
|                               maxLength: 1, | ||||
|                               style: TextStyle( | ||||
|                                 fontSize: 40.fSize, | ||||
|                                 fontWeight: FontWeight.bold, | ||||
|                                 color: AppColors.whiteColor, | ||||
|                               ), | ||||
|                               decoration: InputDecoration( | ||||
|                                 counterText: '', | ||||
|                                 filled: true, | ||||
|                                 fillColor: Colors.transparent, | ||||
|                                 border: OutlineInputBorder( | ||||
|                                   borderRadius: BorderRadius.circular(18), | ||||
|                                   borderSide: BorderSide.none, | ||||
|                                 ), | ||||
|                               ), | ||||
|                               onChanged: (v) => _onOtpChanged(index, v), | ||||
|                             ), | ||||
|                           ), | ||||
|                         ); | ||||
|                       }, | ||||
|                     ); | ||||
|                   }), | ||||
|                 ), | ||||
|               ), | ||||
| 
 | ||||
|               const SizedBox(height: 32), | ||||
| 
 | ||||
|               // Resend OTP | ||||
|               Row( | ||||
|                 mainAxisAlignment: MainAxisAlignment.center, | ||||
|                 children: [ | ||||
|                   const Text("Didn't receive it? "), | ||||
|                   if (_resendTime > 0) | ||||
|                     Text( | ||||
|                       'resend in (${_resendTime.toString().padLeft(2, '0')}:00). ', | ||||
|                       style: const TextStyle(color: Colors.grey), | ||||
|                     ) | ||||
|                   else | ||||
|                     GestureDetector( | ||||
|                       onTap: _resendOtp, | ||||
|                       child: const Text( | ||||
|                         'Resend', | ||||
|                         style: TextStyle( | ||||
|                           color: AppColors.primaryRedColor, | ||||
|                           fontWeight: FontWeight.bold, | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                 ], | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   void _verifyOtp() { | ||||
|     final otp = _controllers.map((c) => c.text).join(); | ||||
|     debugPrint('Verifying OTP: $otp'); | ||||
| 
 | ||||
|     ScaffoldMessenger.of(context).showSnackBar( | ||||
|       SnackBar(content: Text('Verifying OTP: $otp')), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /// Auto fill OTP into text fields | ||||
|   void autoFillOtp(String otp) { | ||||
|     if (otp.length != _otpLength) return; | ||||
| 
 | ||||
|     for (int i = 0; i < _otpLength; i++) { | ||||
|       _controllers[i].text = otp[i]; | ||||
|     } | ||||
| 
 | ||||
|     // Move focus to the last field | ||||
|     _focusNodes[_otpLength - 1].requestFocus(); | ||||
| 
 | ||||
|     // Trigger completion check and color update | ||||
|     _checkOtpCompletion(); | ||||
|   } | ||||
| } | ||||
					Loading…
					
					
				
		Reference in New Issue