diff --git a/flutter_base/lib/mobx/form/form_store.dart b/flutter_base/lib/mobx/form/form_store.dart index 6f39e91..219b50c 100644 --- a/flutter_base/lib/mobx/form/form_store.dart +++ b/flutter_base/lib/mobx/form/form_store.dart @@ -41,7 +41,7 @@ abstract class _FormStore with Store { @action void validateEmail(String value) { - error.email = isEmail(value) ? 'Not a valid email' : null; + error.email = !isEmail(value) ? 'Not a valid email' : null; } @action @@ -65,7 +65,6 @@ abstract class _FormStore with Store { error.username = null; } - error.username = null; } Future checkValidUsername(String value) async { diff --git a/flutter_base/lib/my_form_bloc/main.dart b/flutter_base/lib/my_form_bloc/main.dart new file mode 100644 index 0000000..24a9d56 --- /dev/null +++ b/flutter_base/lib/my_form_bloc/main.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'my_form_bloc.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: BlocProvider( + create: (context) => MyFormBloc(), + child: const AppForm(), + ), + ); + } +} + +class AppForm extends StatelessWidget { + const AppForm({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final _bloc = context.read(); + return Scaffold( + appBar: AppBar( + title: const Text('Signup form'), + ), + body: Form( + child: Padding( + padding: const EdgeInsets.all(10), + child: Column( + children: [ + BlocBuilder( + bloc: _bloc, + builder: (context, state) { + return TextField( + onChanged: (value) { + _bloc.add(UsernameChanged(value)); + }, + decoration: InputDecoration( + labelText: 'Username', + hintText: 'Pick a username', + errorText: state.errorUsername, + ), + ); + }, + ), + BlocBuilder( + bloc: _bloc, + builder: (context, state) { + return AnimatedOpacity( + duration: const Duration(milliseconds: 300), + opacity: state.isUsernameCheck ? 1 : 0, + child: const LinearProgressIndicator()); + }, + ), + BlocBuilder( + bloc: _bloc, + builder: (context, state) { + return TextField( + onChanged: (value) { + _bloc.add(EmailChanged(value)); + }, + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + labelText: 'Email', + hintText: 'Enter your email address', + errorText: state.errorEmail, + ), + ); + }, + ), + BlocBuilder( + bloc: _bloc, + builder: (context, state) { + return TextField( + onChanged: (value) { + _bloc.add(PasswordChanged(value)); + }, + decoration: InputDecoration( + labelText: 'Password', + hintText: 'Set a password', + errorText: state.errorPassword + ), + ); + }, + ), + BlocBuilder( + bloc: _bloc, + builder: (context, state) { + return ElevatedButton( + onPressed: () { + _bloc.add(FormSubmitted()); + if (state.noErrors) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + const SnackBar( + content: Text('Registration Success')), + ); + } + }, + child: const Text('Sign up'), + ); + }, + ), + ], + ), + ), + ), + ); + } +} diff --git a/flutter_base/lib/my_form_bloc/my_form_bloc.dart b/flutter_base/lib/my_form_bloc/my_form_bloc.dart new file mode 100644 index 0000000..f25a059 --- /dev/null +++ b/flutter_base/lib/my_form_bloc/my_form_bloc.dart @@ -0,0 +1,174 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:validators2/validators.dart'; + +abstract class MyFormEvents {} + +class UsernameChanged extends MyFormEvents { + final String username; + + UsernameChanged(this.username); +} + +class EmailChanged extends MyFormEvents { + final String email; + + EmailChanged(this.email); +} + +class PasswordChanged extends MyFormEvents { + final String password; + + PasswordChanged(this.password); +} + +class FormSubmitted extends MyFormEvents {} + +class MyFormState { + final String username; + final String? errorUsername; + final String email; + final String? errorEmail; + final String password; + final String? errorPassword; + final bool isUsernameCheck; + + MyFormState({ + required this.username, + required this.errorUsername, + required this.email, + required this.errorEmail, + required this.password, + required this.errorPassword, + required this.isUsernameCheck, + }); + + MyFormState copyWith({ + String? username, + String? errorUsername, + String? email, + String? errorEmail, + String? password, + String? errorPassword, + bool? isUsernameCheck, + }) { + return MyFormState( + username: username ?? this.username, + errorUsername: errorUsername, + email: email ?? this.email, + errorEmail: errorEmail, + password: password ?? this.password, + errorPassword: errorPassword, + isUsernameCheck: isUsernameCheck ?? this.isUsernameCheck, + ); + } + + bool get noErrors => + username.isNotEmpty && + errorUsername == null && + email.isNotEmpty && + errorEmail == null && + password.isNotEmpty && + errorPassword == null; +} + +class MyFormBloc extends Bloc { + MyFormBloc() + : super(MyFormState( + username: '', + errorUsername: null, + email: '', + errorEmail: null, + password: '', + errorPassword: null, + isUsernameCheck: false, + )) { + on(_onUsernameChanged); + on(_onEmailChanged); + on(_onPasswordChanged); + on(_onFormSubmitted); + } + + void _onUsernameChanged( + UsernameChanged event, Emitter emit) async { + emit(state.copyWith( + username: event.username, + errorUsername: _checkUsername(event.username), + errorEmail: state.errorEmail, + errorPassword: state.errorPassword, + )); + + try { + emit(state.copyWith( + errorUsername: state.errorUsername, + errorEmail: state.errorEmail, + errorPassword: state.errorPassword, + isUsernameCheck: true, + )); + final _isValid = await _checkValidUsername(event.username); + emit(state.copyWith( + errorUsername: state.errorUsername, + errorEmail: state.errorEmail, + errorPassword: state.errorPassword, + isUsernameCheck: false, + )); + if (!_isValid) { + emit(state.copyWith( + username: event.username, + errorUsername: 'Username cannot be "admin"', + errorEmail: state.errorEmail, + errorPassword: state.errorPassword, + )); + return; + } + } catch (_) { + emit(state.copyWith( + errorUsername: null, + errorEmail: state.errorEmail, + errorPassword: state.errorPassword, + )); + } + } + + void _onEmailChanged(EmailChanged event, Emitter emit) => + emit(state.copyWith( + errorUsername: state.errorUsername, + email: event.email, + errorEmail: _checkEmail(event.email), + errorPassword: state.errorPassword, + )); + + void _onPasswordChanged(PasswordChanged event, Emitter emit) => + emit(state.copyWith( + errorUsername: state.errorUsername, + errorEmail: state.errorEmail, + password: event.password, + errorPassword: _checkPassword(event.password), + )); + + void _onFormSubmitted(FormSubmitted event, Emitter emit) { + emit(state.copyWith( + errorUsername: state.username.isEmpty + ? _checkUsername(state.username) + : state.errorUsername, + errorEmail: _checkEmail(state.email), + errorPassword: _checkPassword(state.password), + )); + } + + String? _checkUsername(String username) { + return isNull(username) ? 'Field is required' : null; + } + + Future _checkValidUsername(String value) async { + await Future.delayed(const Duration(seconds: 1)); + return value != 'admin'; + } + + String? _checkEmail(String email) { + return !isEmail(email) ? 'Not a valid email' : null; + } + + String? _checkPassword(String password) { + return isNull(password) ? 'Field is required' : null; + } +}