diff --git a/sama_chat_client/lib/src/features/login/view/login_form.dart b/sama_chat_client/lib/src/features/login/view/login_form.dart index de5524a..6f91973 100644 --- a/sama_chat_client/lib/src/features/login/view/login_form.dart +++ b/sama_chat_client/lib/src/features/login/view/login_form.dart @@ -6,7 +6,7 @@ import 'package:go_router/go_router.dart'; import '../../../navigation/constants.dart'; import '../../../shared/ui/colors.dart'; -import '../../../shared/ui/view/text_field_form.dart'; +import '../../../shared/ui/view/text_button_forms.dart'; import '../bloc/login_bloc.dart'; import '../models/models.dart'; @@ -193,21 +193,18 @@ class _PasswordInputState extends State<_PasswordInput> { context.read().add(LoginPasswordChanged(password)), iconData: Icons.lock_outline, obscureText: isPasswordInvisible, - suffix: Padding( - padding: const EdgeInsets.only(right: 8), - child: IconButton( - onPressed: () { - setState(() { - isPasswordInvisible = !isPasswordInvisible; - }); - }, - icon: Icon( - isPasswordInvisible - ? Icons.visibility_off_outlined - : Icons.visibility_outlined, - color: dullGray, - size: 20, - ), + suffix: IconButton( + onPressed: () { + setState(() { + isPasswordInvisible = !isPasswordInvisible; + }); + }, + icon: Icon( + isPasswordInvisible + ? Icons.visibility_off_outlined + : Icons.visibility_outlined, + color: dullGray, + size: 22, ), ), hint: 'Password', @@ -265,40 +262,19 @@ class _LoginButton extends StatelessWidget { var isSignUpValid = state.isValidSignup && isSignup; return state.status.isInProgress ? const CircularProgressIndicator() - : Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(12), - ), - ), - height: 46, - width: double.infinity, - child: FilledButton( - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - isSignInValid || isSignUpValid - ? slateBlue - : whiteAluminum), - foregroundColor: WidgetStatePropertyAll( - isSignInValid || isSignUpValid ? white : gainsborough), - shape: WidgetStatePropertyAll( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(22), - ), - ), - ), - key: const Key('loginForm_continue_raisedButton'), - onPressed: isSignInValid || isSignUpValid - ? () { - context.read().add( - LoginSubmitted(isSignup, isSighupWithLogin), - ); - } - : null, - child: Text(isSignup ? 'Create account' : 'Login', - style: const TextStyle(fontSize: 16)), - ), - ); + : ButtonForm( + onPressed: isSignInValid || isSignUpValid + ? () { + context.read().add( + LoginSubmitted(isSignup, isSighupWithLogin), + ); + } + : null, + backgroundColor: WidgetStatePropertyAll( + isSignInValid || isSignUpValid ? slateBlue : whiteAluminum), + foregroundColor: WidgetStatePropertyAll( + isSignInValid || isSignUpValid ? white : gainsborough), + hint: isSignup ? 'Create account' : 'Login'); }, ); } diff --git a/sama_chat_client/lib/src/features/reset_password/view/reset_password_page.dart b/sama_chat_client/lib/src/features/reset_password/view/reset_password_page.dart index 9a862ff..a4c6392 100644 --- a/sama_chat_client/lib/src/features/reset_password/view/reset_password_page.dart +++ b/sama_chat_client/lib/src/features/reset_password/view/reset_password_page.dart @@ -54,16 +54,24 @@ class ResetPasswordPage extends StatelessWidget { context.read().add(const OnBackCurrentForm()); } }), - body: Container( - alignment: Alignment.topCenter, - padding: const EdgeInsets.symmetric(horizontal: 28), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 400), - child: SingleChildScrollView( - child: forms[state.currentForm], + body: LayoutBuilder(builder: (context, constraint) { + return Container( + alignment: Alignment.topCenter, + padding: const EdgeInsets.symmetric(horizontal: 28), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 400), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: + BoxConstraints(minHeight: constraint.maxHeight), + child: IntrinsicHeight( + child: forms[state.currentForm], + ), + ), + ), ), - ), - ), + ); + }), bottomNavigationBar: SafeArea( child: SizedBox( height: 18, diff --git a/sama_chat_client/lib/src/features/reset_password/view/send_email_form.dart b/sama_chat_client/lib/src/features/reset_password/view/send_email_form.dart index 6bc62cc..40b7a2d 100644 --- a/sama_chat_client/lib/src/features/reset_password/view/send_email_form.dart +++ b/sama_chat_client/lib/src/features/reset_password/view/send_email_form.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:formz/formz.dart'; +import '../../../shared/ui/view/text_button_forms.dart'; import '../../../shared/utils/date_utils.dart'; import '../bloc/timer_bloc/timer_bloc.dart'; import '../models/models.dart'; @@ -37,7 +38,9 @@ class SendEmailForm extends StatelessWidget { const Padding(padding: EdgeInsets.all(8)), _EmailInput(), const Padding(padding: EdgeInsets.all(8)), - _ContinueButton() + const Spacer(), + _ContinueButton(), + const Padding(padding: EdgeInsets.all(16)) ], ), ); @@ -50,46 +53,19 @@ class _EmailInput extends StatelessWidget { return BlocBuilder( buildWhen: (previous, current) => previous.email != current.email, builder: (context, state) { - return Container( - padding: const EdgeInsets.all(8), - decoration: const BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(12), - ), - color: gainsborough, - ), - child: TextField( - keyboardType: TextInputType.emailAddress, + return TextFieldForm( onChanged: (email) => context.read().add(EmailChanged(email)), - enableSuggestions: false, - autocorrect: false, - decoration: InputDecoration( - border: InputBorder.none, - label: const Row( - children: [ - Icon( - Icons.email_outlined, - size: 16, - color: dullGray, - ), - Padding(padding: EdgeInsets.all(4)), - Text( - 'Email', - style: TextStyle(color: dullGray, fontSize: 16), - ) - ], - ), - errorText: state.email.displayError != null - ? state.email.displayError == EmailValidationError.empty - ? 'Email is too short' - : state.email.displayError == - EmailValidationError.incorrect - ? 'The format of the email address is incorrect' - : null - : null), - ), - ); + iconData: Icons.email_outlined, + keyboardType: TextInputType.emailAddress, + hint: 'Email', + error: state.email.displayError != null + ? state.email.displayError == EmailValidationError.empty + ? 'Email is too short' + : state.email.displayError == EmailValidationError.incorrect + ? 'The format of the email address is incorrect' + : null + : null); }, ); } @@ -107,53 +83,37 @@ class _ContinueButton extends StatelessWidget { builder: (context, state) { return state.status.isInProgress ? const CircularProgressIndicator() - : Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(12), - ), - ), - height: 46, - width: double.infinity, - child: FilledButton( - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - state.isEmailValid ? slateBlue : whiteAluminum), - foregroundColor: WidgetStatePropertyAll( - state.isEmailValid ? white : gainsborough), - shape: WidgetStatePropertyAll( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), - ), - ), - ), - onPressed: state.isEmailValid - ? () { - hideKeyboard(); - var time = context.read().state.duration; - if (time != 0) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: ValueListenableBuilder( - valueListenable: snackBarText, - builder: (context, currentText, child) { - return Text(currentText, - textAlign: TextAlign.center); - }, - ), + : ButtonForm( + onPressed: state.isEmailValid + ? () { + hideKeyboard(); + var time = context.read().state.duration; + if (time != 0) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: ValueListenableBuilder( + valueListenable: snackBarText, + builder: (context, currentText, child) { + return Text(currentText, + textAlign: TextAlign.center); + }, ), - ); - } else { - context - .read() - .add(const EmailSubmitted()); - } + ), + ); + } else { + context + .read() + .add(const EmailSubmitted()); } - : null, - child: const Text('Continue'), - )); + } + : null, + backgroundColor: WidgetStatePropertyAll( + state.isEmailValid ? slateBlue : whiteAluminum), + foregroundColor: WidgetStatePropertyAll( + state.isEmailValid ? white : gainsborough), + hint: 'Continue'); }, )); } diff --git a/sama_chat_client/lib/src/features/reset_password/view/send_otp_form.dart b/sama_chat_client/lib/src/features/reset_password/view/send_otp_form.dart index d25f6e0..8413852 100644 --- a/sama_chat_client/lib/src/features/reset_password/view/send_otp_form.dart +++ b/sama_chat_client/lib/src/features/reset_password/view/send_otp_form.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:formz/formz.dart'; import '../../../shared/ui/colors.dart'; +import '../../../shared/ui/view/text_button_forms.dart'; import '../../../shared/utils/date_utils.dart'; import '../../../shared/utils/screen_factor.dart'; import '../bloc/reset_password_bloc.dart'; @@ -38,6 +39,7 @@ class SendOtpForm extends StatelessWidget { const Padding(padding: EdgeInsets.all(8)), const OtpInput(), const Padding(padding: EdgeInsets.all(8)), + const Spacer(), _ContinueButton(), const Padding(padding: EdgeInsets.all(8)), const Text('Didn\'t receive the email?'), @@ -67,6 +69,7 @@ class SendOtpForm extends StatelessWidget { ], ), ), + const Padding(padding: EdgeInsets.all(16)) ], ); })); @@ -80,37 +83,20 @@ class _ContinueButton extends StatelessWidget { builder: (context, state) { return state.status.isInProgress ? const CircularProgressIndicator() - : Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(12), - ), - ), - height: 46, - width: double.infinity, - child: FilledButton( - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - state.isOTPValid ? slateBlue : whiteAluminum), - foregroundColor: WidgetStatePropertyAll( - state.isOTPValid ? white : gainsborough), - shape: WidgetStatePropertyAll( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), - ), - ), - ), - onPressed: state.isOTPValid - ? () { - hideKeyboard(); - context - .read() - .add(const OtpSubmitted()); - } - : null, - child: const Text('Continue'), - ), - ); + : ButtonForm( + onPressed: state.isOTPValid + ? () { + hideKeyboard(); + context + .read() + .add(const OtpSubmitted()); + } + : null, + backgroundColor: WidgetStatePropertyAll( + state.isOTPValid ? slateBlue : whiteAluminum), + foregroundColor: WidgetStatePropertyAll( + state.isOTPValid ? white : gainsborough), + hint: 'Continue'); }, ); } @@ -160,43 +146,50 @@ class OtpInputState extends State { } Widget digitField(int index) { - return SizedBox( - width: 45, - height: 45, - child: TextField( - style: const TextStyle(fontSize: 22), - controller: _controllers[index], - focusNode: _focusNodes[index], - keyboardType: TextInputType.number, - textAlign: TextAlign.center, - autofocus: true, - onChanged: (value) { - if (value.characters.length == OtpInput.otpSize) { - for (final (index, controller) in _controllers.indexed) { - controller.text = value.characters.elementAt(index); - if (index < 5) { - FocusScope.of(context).requestFocus(_focusNodes[index + 1]); + return Material( + elevation: 8.0, + borderRadius: BorderRadius.circular(5.0), + shadowColor: black.withValues(alpha: 0.6), + child: SizedBox( + width: 45, + height: 45, + child: TextField( + style: const TextStyle(fontSize: 22), + controller: _controllers[index], + focusNode: _focusNodes[index], + keyboardType: TextInputType.number, + textAlign: TextAlign.center, + autofocus: true, + onChanged: (value) { + if (value.characters.length == OtpInput.otpSize) { + for (final (index, controller) in _controllers.indexed) { + controller.text = value.characters.elementAt(index); + if (index < 5) { + FocusScope.of(context).requestFocus(_focusNodes[index + 1]); + } + context + .read() + .add(OtpChanged(index, value.characters.elementAt(index))); } - context - .read() - .add(OtpChanged(index, value.characters.elementAt(index))); + return; } - return; - } - if (value.isNotEmpty && index < 5) { - FocusScope.of(context).requestFocus(_focusNodes[index + 1]); - } else if (value.isEmpty && index > 0) { - FocusScope.of(context).requestFocus(_focusNodes[index - 1]); - } - context.read().add(OtpChanged(index, value)); - }, - decoration: const InputDecoration( - counterText: '', - border: OutlineInputBorder(), - contentPadding: EdgeInsets.only( - bottom: 10, - )), + if (value.isNotEmpty && index < 5) { + FocusScope.of(context).requestFocus(_focusNodes[index + 1]); + } else if (value.isEmpty && index > 0) { + FocusScope.of(context).requestFocus(_focusNodes[index - 1]); + } + context.read().add(OtpChanged(index, value)); + }, + decoration: const InputDecoration( + counterText: '', + border: OutlineInputBorder( + borderSide: BorderSide.none, + ), + contentPadding: EdgeInsets.only( + bottom: 10, + )), + ), ), ); } diff --git a/sama_chat_client/lib/src/features/reset_password/view/send_reset_password_form.dart b/sama_chat_client/lib/src/features/reset_password/view/send_reset_password_form.dart index 96967ca..9d5b39e 100644 --- a/sama_chat_client/lib/src/features/reset_password/view/send_reset_password_form.dart +++ b/sama_chat_client/lib/src/features/reset_password/view/send_reset_password_form.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:formz/formz.dart'; -import '../bloc/timer_bloc/timer_bloc.dart'; +import '../../../shared/ui/view/text_button_forms.dart'; import '../models/models.dart'; import '../../../shared/ui/colors.dart'; @@ -32,20 +32,19 @@ class SendResetPasswordForm extends StatelessWidget { Navigator.popUntil(context, (route) => route.isFirst); } }, - child: Align( - alignment: const Alignment(0, -1 / 3), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Set new password'), - const Padding(padding: EdgeInsets.all(8)), - _PasswordInput(), - const Padding(padding: EdgeInsets.all(8)), - _ConfirmPasswordInput(), - const Padding(padding: EdgeInsets.all(8)), - _ContinueButton() - ], - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Set new password'), + const Padding(padding: EdgeInsets.all(8)), + _PasswordInput(), + const Padding(padding: EdgeInsets.all(8)), + _ConfirmPasswordInput(), + const Padding(padding: EdgeInsets.all(8)), + const Spacer(), + _ContinueButton(), + const Padding(padding: EdgeInsets.all(16)) + ], ), ); } @@ -66,67 +65,38 @@ class _PasswordInputState extends State<_PasswordInput> { return BlocBuilder( buildWhen: (previous, current) => previous.password != current.password, builder: (context, state) { - return Container( - padding: const EdgeInsets.all(8), - decoration: const BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(12), - ), - color: gainsborough, - ), - child: TextField( + return TextFieldForm( keyboardType: TextInputType.visiblePassword, onChanged: (psw) => context.read().add(PasswordChanged(psw)), + iconData: Icons.lock_outline, obscureText: isPasswordInvisible, - obscuringCharacter: '*', - enableSuggestions: false, - autocorrect: false, - decoration: InputDecoration( - border: InputBorder.none, - label: const Row( - children: [ - Icon( - Icons.lock_outline, - size: 16, - color: dullGray, - ), - Text( - 'Password', - style: TextStyle(color: dullGray, fontSize: 16), - ) - ], - ), - errorText: state.password.displayError != null - ? state.password.displayError == PasswordValidationError.short - ? 'Password is too short' - : state.password.displayError == - PasswordValidationError.long - ? 'Password is too long' - : state.password.displayError == - PasswordValidationError.unavailableSymbols - ? 'Password contains not allowed symbols' - : null - : null, - suffixIcon: Padding( - padding: const EdgeInsets.only(right: 8), - child: IconButton( - onPressed: () { - setState(() { - isPasswordInvisible = !isPasswordInvisible; - }); - }, - icon: Icon( - isPasswordInvisible - ? Icons.visibility_off_outlined - : Icons.visibility_outlined, - color: dullGray, - ), - ), + suffix: IconButton( + onPressed: () { + setState(() { + isPasswordInvisible = !isPasswordInvisible; + }); + }, + icon: Icon( + isPasswordInvisible + ? Icons.visibility_off_outlined + : Icons.visibility_outlined, + size: 22, + color: dullGray, ), ), - ), - ); + hint: 'Password', + error: state.password.displayError != null + ? state.password.displayError == PasswordValidationError.short + ? 'Password is too short' + : state.password.displayError == + PasswordValidationError.long + ? 'Password is too long' + : state.password.displayError == + PasswordValidationError.unavailableSymbols + ? 'Password contains not allowed symbols' + : null + : null); }, ); } @@ -148,63 +118,34 @@ class _ConfirmPasswordInputState extends State<_ConfirmPasswordInput> { buildWhen: (previous, current) => previous.confirmPassword != current.confirmPassword, builder: (context, state) { - return Container( - padding: const EdgeInsets.all(8), - decoration: const BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(12), - ), - color: gainsborough, - ), - child: TextField( - keyboardType: TextInputType.visiblePassword, - onChanged: (psw) => context - .read() - .add(ConfirmPasswordChanged(psw)), - obscureText: isPasswordInvisible, - obscuringCharacter: '*', - enableSuggestions: false, - autocorrect: false, - decoration: InputDecoration( - border: InputBorder.none, - label: const Row( - children: [ - Icon( - Icons.lock_outline, - size: 16, - color: dullGray, - ), - Text( - 'Confirm password', - style: TextStyle(color: dullGray, fontSize: 16), - ) - ], - ), - errorText: state.confirmPassword.displayError != null - ? state.confirmPassword.displayError == - ConfirmPasswordValidationError.empty - ? 'Password is too short' - : state.confirmPassword.displayError == - ConfirmPasswordValidationError.notMatching - ? 'Passwords do NOT match' - : null - : null, - suffixIcon: Padding( - padding: const EdgeInsets.only(right: 8), - child: IconButton( - onPressed: () { - setState(() { - isPasswordInvisible = !isPasswordInvisible; - }); - }, - icon: Icon( - isPasswordInvisible - ? Icons.visibility_off_outlined - : Icons.visibility_outlined, - color: dullGray, - ), - ), - ), + return TextFieldForm( + keyboardType: TextInputType.visiblePassword, + onChanged: (psw) => context + .read() + .add(ConfirmPasswordChanged(psw)), + iconData: Icons.lock_outline, + hint: 'Confirm password', + error: state.confirmPassword.displayError != null + ? state.confirmPassword.displayError == + ConfirmPasswordValidationError.empty + ? 'Password is too short' + : state.confirmPassword.displayError == + ConfirmPasswordValidationError.notMatching + ? 'Passwords do NOT match' + : null + : null, + suffix: IconButton( + onPressed: () { + setState(() { + isPasswordInvisible = !isPasswordInvisible; + }); + }, + icon: Icon( + isPasswordInvisible + ? Icons.visibility_off_outlined + : Icons.visibility_outlined, + size: 22, + color: dullGray, ), ), ); @@ -220,37 +161,20 @@ class _ContinueButton extends StatelessWidget { builder: (context, state) { return state.status.isInProgress ? const CircularProgressIndicator() - : Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(12), - ), - ), - height: 46, - width: double.infinity, - child: FilledButton( - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - state.isPasswordValid ? slateBlue : whiteAluminum), - foregroundColor: WidgetStatePropertyAll( - state.isPasswordValid ? white : gainsborough), - shape: WidgetStatePropertyAll( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), - ), - ), - ), - onPressed: state.isPasswordValid - ? () { - hideKeyboard(); - context - .read() - .add(const PasswordSubmitted()); - } - : null, - child: const Text('Continue'), - ), - ); + : ButtonForm( + onPressed: state.isPasswordValid + ? () { + hideKeyboard(); + context + .read() + .add(const PasswordSubmitted()); + } + : null, + backgroundColor: WidgetStatePropertyAll( + state.isPasswordValid ? slateBlue : whiteAluminum), + foregroundColor: WidgetStatePropertyAll( + state.isPasswordValid ? white : gainsborough), + hint: 'Continue'); }, ); } diff --git a/sama_chat_client/lib/src/shared/ui/view/text_button_forms.dart b/sama_chat_client/lib/src/shared/ui/view/text_button_forms.dart new file mode 100644 index 0000000..47822a4 --- /dev/null +++ b/sama_chat_client/lib/src/shared/ui/view/text_button_forms.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; + +import '../colors.dart'; + +class TextFieldForm extends StatelessWidget { + final ValueChanged onChanged; + final IconData iconData; + final String hint; + final TextInputType? keyboardType; + final String? error; + final Widget? suffix; + final bool? obscureText; + + const TextFieldForm( + {super.key, + required this.onChanged, + required this.iconData, + required this.hint, + this.keyboardType, + this.error, + this.suffix, + this.obscureText}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(4), + height: 55, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(28)), + color: gainsborough, + ), + child: TextField( + keyboardType: keyboardType, + style: const TextStyle(fontSize: 17), + autocorrect: false, + enableSuggestions: false, + obscuringCharacter: '*', + obscureText: obscureText ?? false, + onChanged: (username) => onChanged(username), + decoration: InputDecoration( + border: InputBorder.none, + floatingLabelBehavior: error != null + ? FloatingLabelBehavior.never + : FloatingLabelBehavior.auto, + contentPadding: const EdgeInsets.only(left: 12.0), + isDense: true, + label: Row( + spacing: 10, + children: [ + Icon( + iconData, + size: 26, + color: dullGray, + ), + Text( + hint, + style: const TextStyle(color: dullGray, fontSize: 16), + ) + ], + ), + errorText: error, + suffixIcon: Padding( + padding: const EdgeInsets.only(right: 8), + child: suffix ?? const SizedBox(), + ), + ), + )); + } +} + +class ButtonForm extends StatelessWidget { + final VoidCallback? onPressed; + final WidgetStateProperty? backgroundColor; + final WidgetStateProperty? foregroundColor; + final String hint; + + const ButtonForm( + {super.key, + required this.onPressed, + required this.backgroundColor, + required this.foregroundColor, + required this.hint}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + height: 46, + width: double.infinity, + child: FilledButton( + style: ButtonStyle( + backgroundColor: backgroundColor, + foregroundColor: foregroundColor, + shape: WidgetStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(22), + ), + ), + ), + onPressed: onPressed, + child: Text(hint), + ), + ); + } +}