@ -7,7 +7,6 @@ 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/core/utils/validation_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/authentication/authentication_view_model.dart ' ;
@ -34,19 +33,35 @@ class _RegisterNew extends State<RegisterNew> {
super . initState ( ) ;
_nationalIdFocusNode = FocusNode ( ) ;
_dobFocusNode = FocusNode ( ) ;
/ / Clear errors when entering register screen
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
final authVm = context . read < AuthenticationViewModel > ( ) ;
authVm . clearNationalIdError ( ) ;
authVm . clearDobError ( ) ;
authVm . clearPhoneNumberError ( ) ;
} ) ;
}
@ override
void dispose ( ) {
_nationalIdFocusNode . dispose ( ) ;
_dobFocusNode . dispose ( ) ;
/ / Clear errors when leaving register screen
/ / Use post frame callback to avoid calling notifyListeners during dispose
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
final authVm = context . read < AuthenticationViewModel > ( ) ;
authVm . clearNationalIdError ( ) ;
authVm . clearDobError ( ) ;
authVm . clearPhoneNumberError ( ) ;
} ) ;
super . dispose ( ) ;
}
@ override
Widget build ( BuildContext context ) {
AuthenticationViewModel authVm = context . read < AuthenticationViewModel > ( ) ;
return Scaffold (
backgroundColor: AppColors . bgScaffoldColor ,
appBar: CustomAppBar (
@ -57,199 +72,216 @@ class _RegisterNew extends State<RegisterNew> {
context . setLocale ( value = = ' en ' ? Locale ( ' en ' , ' US ' ) : Locale ( ' ar ' , ' SA ' ) ) ;
} ,
) ,
body: GestureDetector (
onTap: ( ) {
/ / Dismiss keyboard and unfocus all input fields
_nationalIdFocusNode . unfocus ( ) ;
_dobFocusNode . unfocus ( ) ;
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 ;
body: Consumer < AuthenticationViewModel > (
builder: ( context , authVm , child ) {
return GestureDetector (
onTap: ( ) {
/ / Dismiss keyboard and unfocus all input fields
_nationalIdFocusNode . unfocus ( ) ;
_dobFocusNode . unfocus ( ) ;
FocusScope . of ( context ) . unfocus ( ) ;
} ,
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. w , height: 200. h , fit: BoxFit . cover , repeat: true ) ,
SizedBox ( height: 16. h ) ,
LocaleKeys . prepareToElevate . tr ( context: context ) . toText32 ( isBold: true ) ,
SizedBox ( height: 24. h ) ,
Directionality (
textDirection: Directionality . of ( context ) ,
child: Container (
decoration: BoxDecoration ( color: AppColors . whiteColor , borderRadius: BorderRadius . circular ( 24 ) ) ,
padding: EdgeInsets . symmetric ( horizontal: 16. h ) ,
child: Column (
children: [
CustomCountryDropdown (
countryList: CountryEnum . values . where ( ( c ) = > c ! = CountryEnum . others ) . toList ( ) ,
onCountryChange: authVm . onCountryChange ,
/ / isRtl: Directionality . of ( context ) = = TextDirection . LTR ,
) . withVerticalPadding ( 8. h ) ,
Divider ( height: 1. h ) ,
TextInputWidget (
labelText: LocaleKeys . nationalIdNumber . tr ( context: context ) ,
hintText: " xxxxxxxxx " ,
controller: authVm . nationalIdController ,
focusNode: _nationalIdFocusNode ,
keyboardType: TextInputType . number ,
isEnable: true ,
prefix: null ,
isAllowRadius: true ,
isBorderAllowed: false ,
isAllowLeadingIcon: true ,
autoFocus: true ,
padding: EdgeInsets . symmetric ( vertical: 8. h ) ,
leadingIcon: AppAssets . student_card ,
fontFamily: " Poppins " ,
) . withVerticalPadding ( 8 ) ,
Divider ( height: 1 ) ,
TextInputWidget (
labelText: LocaleKeys . dob . tr ( context: context ) ,
hintText: " 11 July, 1994 " ,
controller: authVm . dobController ,
focusNode: _dobFocusNode ,
isEnable: true ,
prefix: null ,
isAllowRadius: true ,
isBorderAllowed: false ,
isAllowLeadingIcon: true ,
padding: EdgeInsets . symmetric ( vertical: 8. h ) ,
leadingIcon: AppAssets . birthday_cake ,
selectionType: SelectionTypeEnum . calendar ,
onCalendarTypeChanged: authVm . onCalenderTypeChange ,
onChange: authVm . onDobChange ,
fontFamily: " Poppins " ,
) . withVerticalPadding ( 8 ) ,
] ,
) ,
) ,
) ,
SizedBox ( height: 25. h ) ,
GestureDetector (
onTap: authVm . onTermAccepted ,
child: Row (
children: [
Selector < AuthenticationViewModel , bool > (
selector: ( _ , viewModel ) = > viewModel . isTermsAccepted ,
shouldRebuild: ( previous , next ) = > previous ! = next ,
builder: ( context , isTermsAccepted , child ) {
return AnimatedContainer (
duration: const Duration ( milliseconds: 200 ) ,
height: 24. h ,
width: 24. h ,
decoration: BoxDecoration (
color: isTermsAccepted ? AppColors . primaryRedColor : Colors . transparent ,
borderRadius: BorderRadius . circular ( 6 ) ,
border: Border . all ( color: isTermsAccepted ? AppColors . primaryRedBorderColor : AppColors . greyColor , width: 2. h ) ,
) ,
child: isTermsAccepted ? Icon ( Icons . check , size: 16. f , color: Colors . white ) : null ,
) ;
} ,
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. w , height: 200. h , fit: BoxFit . cover , repeat: true ) ,
SizedBox ( height: 16. h ) ,
LocaleKeys . prepareToElevate . tr ( context: context ) . toText32 ( isBold: true ) ,
SizedBox ( height: 24. h ) ,
Directionality (
textDirection: Directionality . of ( context ) ,
child: Container (
decoration: BoxDecoration (
color: AppColors . whiteColor ,
borderRadius: BorderRadius . circular ( 24 ) ,
border: Border . all (
color: authVm . hasRegistrationFormError ? AppColors . primaryRedBorderColor : Colors . transparent ,
width: 1 ,
) ,
) ,
padding: EdgeInsets . symmetric ( horizontal: 16. h ) ,
child: Column (
children: [
CustomCountryDropdown (
countryList: CountryEnum . values . where ( ( c ) = > c ! = CountryEnum . others ) . toList ( ) ,
onCountryChange: authVm . onCountryChange ,
/ / isRtl: Directionality . of ( context ) = = TextDirection . LTR ,
) . withVerticalPadding ( 8. h ) ,
Divider ( height: 1. h ) ,
TextInputWidget (
labelText: LocaleKeys . nationalIdNumber . tr ( context: context ) ,
hintText: " xxxxxxxxx " ,
controller: authVm . nationalIdController ,
focusNode: _nationalIdFocusNode ,
keyboardType: TextInputType . number ,
isEnable: true ,
prefix: null ,
isAllowRadius: true ,
isBorderAllowed: false ,
isAllowLeadingIcon: true ,
autoFocus: true ,
padding: EdgeInsets . symmetric ( vertical: 8. h ) ,
leadingIcon: AppAssets . student_card ,
fontFamily: " Poppins " ,
hasError: false , / / Don ' t show individual field border
errorMessage: authVm . nationalIdError , / / Show error message if exists
onChange: ( value ) {
/ / Clear error when user starts typing
authVm . clearNationalIdError ( ) ;
} ,
) . withVerticalPadding ( 8 ) ,
Divider ( height: 1 ) ,
TextInputWidget (
labelText: LocaleKeys . dob . tr ( context: context ) ,
hintText: " 11 July, 1994 " ,
controller: authVm . dobController ,
focusNode: _dobFocusNode ,
isEnable: true ,
prefix: null ,
isAllowRadius: true ,
isBorderAllowed: false ,
isAllowLeadingIcon: true ,
padding: EdgeInsets . symmetric ( vertical: 8. h ) ,
leadingIcon: AppAssets . birthday_cake ,
selectionType: SelectionTypeEnum . calendar ,
onCalendarTypeChanged: authVm . onCalenderTypeChange ,
onChange: ( value ) {
authVm . onDobChange ( value ) ;
/ / Clear error when user selects a date
authVm . clearDobError ( ) ;
} ,
fontFamily: " Poppins " ,
hasError: false , / / Don ' t show individual field border
errorMessage: authVm . dobError , / / Show error message if exists
) . withVerticalPadding ( 8 ) ,
] ,
) ,
) ,
SizedBox ( width: 12. h ) ,
Row (
) ,
SizedBox ( height: 25. h ) ,
GestureDetector (
onTap: authVm . onTermAccepted ,
child: Row (
children: [
Text (
LocaleKeys . iAcceptThe . tr ( context: context ) ,
style: context . dynamicTextStyle ( fontSize: 14. f , fontWeight: FontWeight . w600 , color: Color ( 0xFF2E3039 ) ) ,
) ,
GestureDetector (
onTap: ( ) {
/ / Navigate to terms and conditions page
Utils . openWebView (
url: ' https://hmg.com/en/Pages/Terms.aspx ' ,
Selector < AuthenticationViewModel , bool > (
selector: ( _ , viewModel ) = > viewModel . isTermsAccepted ,
shouldRebuild: ( previous , next ) = > previous ! = next ,
builder: ( context , isTermsAccepted , child ) {
return AnimatedContainer (
duration: const Duration ( milliseconds: 200 ) ,
height: 24. h ,
width: 24. h ,
decoration: BoxDecoration (
color: isTermsAccepted ? AppColors . primaryRedColor : Colors . transparent ,
borderRadius: BorderRadius . circular ( 6 ) ,
border: Border . all ( color: isTermsAccepted ? AppColors . primaryRedBorderColor : AppColors . greyColor , width: 2. h ) ,
) ,
child: isTermsAccepted ? Icon ( Icons . check , size: 16. f , color: Colors . white ) : null ,
) ;
} ,
child: Text (
" ${ LocaleKeys . termsConditoins . tr ( context: context ) } " ,
style: context . dynamicTextStyle (
fontSize: 14. f ,
fontWeight: FontWeight . w600 ,
color: AppColors . primaryRedColor ,
decoration: TextDecoration . underline ,
decorationColor: AppColors . primaryRedBorderColor ,
) ,
SizedBox ( width: 12. h ) ,
Row (
children: [
Text (
LocaleKeys . iAcceptThe . tr ( context: context ) ,
style: context . dynamicTextStyle ( fontSize: 14. f , fontWeight: FontWeight . w600 , color: Color ( 0xFF2E3039 ) ) ,
) ,
) ,
GestureDetector (
onTap: ( ) {
/ / Navigate to terms and conditions page
Utils . openWebView (
url: ' https://hmg.com/en/Pages/Terms.aspx ' ,
) ;
} ,
child: Text (
" ${ LocaleKeys . termsConditoins . tr ( context: context ) } " ,
style: context . dynamicTextStyle (
fontSize: 14. f ,
fontWeight: FontWeight . w600 ,
color: AppColors . primaryRedColor ,
decoration: TextDecoration . underline ,
decorationColor: AppColors . primaryRedBorderColor ,
) ,
) ,
) ,
] ,
) ,
/ / Expanded (
/ / child: Text (
/ / LocaleKeys . iAcceptTermsConditions . tr ( context: context ) . split ( " the " ) . first ,
/ / style: context . dynamicTextStyle ( fontSize: 14. fSize , isBold: true , color: Color ( 0xFF2E3039 ) ) ,
/ / ) ,
/ / ) ,
] ,
) ,
/ / Expanded (
/ / child: Text (
/ / LocaleKeys . iAcceptTermsConditions . tr ( context: context ) . split ( " the " ) . first ,
/ / style: context . dynamicTextStyle ( fontSize: 14. fSize , isBold: true , color: Color ( 0xFF2E3039 ) ) ,
/ / ) ,
/ / ) ,
] ,
) ,
) ,
SizedBox ( height: 25. h ) ,
CustomButton (
text: LocaleKeys . registernow . tr ( context: context ) ,
icon: AppAssets . note_edit ,
onPressed: ( ) {
/ / Dismiss keyboard before proceeding
_nationalIdFocusNode . unfocus ( ) ;
_dobFocusNode . unfocus ( ) ;
FocusScope . of ( context ) . unfocus ( ) ;
) ,
SizedBox ( height: 25. h ) ,
CustomButton (
text: LocaleKeys . registernow . tr ( context: context ) ,
icon: AppAssets . note_edit ,
onPressed: ( ) {
/ / Dismiss keyboard before proceeding
_nationalIdFocusNode . unfocus ( ) ;
_dobFocusNode . unfocus ( ) ;
FocusScope . of ( context ) . unfocus ( ) ;
if ( ValidationUtils . isValidatedId (
nationalId: authVm . nationalIdController . text ,
selectedCountry: authVm . selectedCountrySignup ,
isTermsAccepted: authVm . isTermsAccepted ,
dob: authVm . dobController . text ,
onOkPress: ( ) {
Navigator . of ( context ) . pop ( ) ;
} ) ) {
showRegisterModel ( context: context , authVM: authVm ) ;
}
} ,
) ,
SizedBox ( height: 14 ) ,
Center (
child: RichText (
textAlign: TextAlign . center ,
text: TextSpan (
style: context . dynamicTextStyle (
color: Colors . black ,
fontSize: 16. f ,
height: 26 / 16 ,
fontWeight: FontWeight . w600 ,
) ,
children: < TextSpan > [
TextSpan ( text: LocaleKeys . alreadyHaveAccount . tr ( context: context ) , style: context . dynamicTextStyle ( ) ) ,
TextSpan ( text: " " ) ,
TextSpan (
text: LocaleKeys . loginNow . tr ( context: context ) ,
/ / Use ViewModel validation method
if ( authVm . validateRegistrationForm ( ) ) {
showRegisterModel ( context: context , authVM: authVm ) ;
}
} ,
) ,
SizedBox ( height: 14 ) ,
Center (
child: RichText (
textAlign: TextAlign . center ,
text: TextSpan (
style: context . dynamicTextStyle (
color: AppColors. primaryRedColor ,
color: Colors . black ,
fontSize: 16. f ,
height: 26 / 16 ,
fontWeight: FontWeight . w600 ,
) ,
recognizer: TapGestureRecognizer ( )
. . onTap = ( ) {
Navigator . of ( context ) . pop ( ) ;
} ,
children: < TextSpan > [
TextSpan ( text: LocaleKeys . alreadyHaveAccount . tr ( context: context ) , style: context . dynamicTextStyle ( ) ) ,
TextSpan ( text: " " ) ,
TextSpan (
text: LocaleKeys . loginNow . tr ( context: context ) ,
style: context . dynamicTextStyle (
color: AppColors . primaryRedColor ,
fontSize: 16. f ,
height: 26 / 16 ,
fontWeight: FontWeight . w600 ,
) ,
recognizer: TapGestureRecognizer ( )
. . onTap = ( ) {
Navigator . of ( context ) . pop ( ) ;
} ,
) ,
] ,
) ,
] ,
) ,
) ,
) ,
SizedBox ( height: 30. h ) ,
] ,
) ,
SizedBox ( height: 30. h ) ,
] ,
) ,
) ,
) ,
) ,
) ,
) ;
} ,
) ) ;
}
@ -264,73 +296,75 @@ class _RegisterNew extends State<RegisterNew> {
builder: ( bottomSheetContext ) = > Padding (
padding: EdgeInsets . only ( bottom: MediaQuery . of ( bottomSheetContext ) . viewInsets . bottom ) ,
child: SingleChildScrollView (
child: GenericBottomSheet (
countryCode: authVM . selectedCountrySignup . countryCode ,
initialPhoneNumber: authVM . phoneNumberController . text ,
textController: authVM . phoneNumberController ,
isEnableCountryDropdown: false ,
onCountryChange: authVM . onCountryChange ,
onChange: authVM . onPhoneNumberChange ,
autoFocus: true ,
buttons: [
Padding (
padding: const EdgeInsets . only ( bottom: 10 ) ,
child: CustomButton (
text: LocaleKeys . sendOTPSMS . tr ( context: context ) ,
onPressed: ( ) async {
/ / Dismiss keyboard before validation
FocusScope . of ( context ) . unfocus ( ) ;
child: Consumer < AuthenticationViewModel > (
builder: ( context , authVm , child ) {
return GenericBottomSheet (
countryCode: authVm . selectedCountrySignup . countryCode ,
initialPhoneNumber: authVm . phoneNumberController . text ,
textController: authVm . phoneNumberController ,
isEnableCountryDropdown: false ,
onCountryChange: authVm . onCountryChange ,
onChange: ( value ) {
authVm . onPhoneNumberChange ( value ) ;
/ / Clear error when user starts typing
authVm . clearPhoneNumberError ( ) ;
} ,
autoFocus: true ,
phoneNumberError: authVm . phoneNumberError ,
buttons: [
Padding (
padding: const EdgeInsets . only ( bottom: 10 ) ,
child: CustomButton (
text: LocaleKeys . sendOTPSMS . tr ( context: context ) ,
onPressed: ( ) async {
/ / Dismiss keyboard before validation
FocusScope . of ( context ) . unfocus ( ) ;
if ( ValidationUtils . isValidatePhone (
phoneNumber: authVM . phoneNumberController . text ,
onOkPress: ( ) {
Navigator . of ( context ) . pop ( ) ;
/ / Use ViewModel validation method
if ( authVm . validatePhoneNumber ( ) ) {
appState . setSelectDeviceByImeiRespModelElement ( null ) ;
await authVm . onRegistrationStart ( otpTypeEnum: OTPTypeEnum . sms ) ;
}
} ,
) ) {
appState . setSelectDeviceByImeiRespModelElement ( null ) ;
await authVM . onRegistrationStart ( otpTypeEnum: OTPTypeEnum . sms ) ;
}
} ,
backgroundColor: AppColors . primaryRedColor ,
borderColor: AppColors . primaryRedBorderColor ,
textColor: AppColors . whiteColor ,
icon: AppAssets . message ,
) ,
) ,
Row (
crossAxisAlignment: CrossAxisAlignment . center ,
mainAxisAlignment: MainAxisAlignment . center ,
children: [
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 ( context: context ) . toText16 ( color: AppColors . textColor ) ,
) ,
] ,
) ,
Padding (
padding: EdgeInsets . symmetric ( horizontal: 8. h ) ,
child: LocaleKeys . oR . tr ( context: context ) . toText16 ( color: AppColors . textColor ) ,
padding: EdgeInsets . only ( bottom: 10. h , top: 10. h ) ,
child: CustomButton (
text: LocaleKeys . sendOTPWHATSAPP . tr ( context: context ) ,
onPressed: ( ) async {
FocusScope . of ( context ) . unfocus ( ) ;
/ / Use ViewModel validation method
if ( authVm . validatePhoneNumber ( ) ) {
appState . setSelectDeviceByImeiRespModelElement ( null ) ;
await authVm . onRegistrationStart ( otpTypeEnum: OTPTypeEnum . whatsapp ) ;
}
} ,
backgroundColor: AppColors . whiteColor ,
borderColor: AppColors . borderOnlyColor ,
textColor: AppColors . textColor ,
icon: AppAssets . whatsapp ,
iconColor: null ,
) ,
) ,
] ,
) ,
Padding (
padding: EdgeInsets . only ( bottom: 10. h , top: 10. h ) ,
child: CustomButton (
text: LocaleKeys . sendOTPWHATSAPP . tr ( context: context ) ,
onPressed: ( ) async {
FocusScope . of ( context ) . unfocus ( ) ;
if ( ValidationUtils . isValidatePhone (
phoneNumber: authVM . phoneNumberController . text ,
onOkPress: ( ) {
Navigator . of ( context ) . pop ( ) ;
} ,
) ) {
appState . setSelectDeviceByImeiRespModelElement ( null ) ;
await authVM . onRegistrationStart ( otpTypeEnum: OTPTypeEnum . whatsapp ) ;
}
} ,
backgroundColor: AppColors . whiteColor ,
borderColor: AppColors . borderOnlyColor ,
textColor: AppColors . textColor ,
icon: AppAssets . whatsapp ,
iconColor: null ,
) ,
) ,
] ,
) ;
} ,
) ,
) ,
) ,