Compare commits

..

No commits in common. "b21aa04557b3e076e38285af0a18494316631ed0" and "89f94cf1ff1eacb16007c1a98325b688d8ab1535" have entirely different histories.

11 changed files with 126 additions and 176 deletions

3
.gitignore vendored
View File

@ -195,5 +195,4 @@ $RECYCLE.BIN/
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
.idea/
*.iml
google-services.json
*.iml

View File

@ -31,29 +31,23 @@ class App extends StatelessWidget {
static FormData getNormalFormData() {
return const FormData([
FormInput(formFieldName, Name.pure()),
FormInput(formFieldPhone, Phone.pure()),
FormInput(formFieldPro, Boolean.pure()),
FormInput(
formFieldConfirmedPassword,
ConfirmedPassword.pure(),
metadata: FormInputMetadata(export: false),
),
FormEntry(formFieldName, Name.pure()),
FormEntry(formFieldPhone, Phone.pure()),
FormEntry(formFieldPro, Boolean.pure()),
FormEntry(formFieldConfirmedPassword, ConfirmedPassword.pure(),
export: false),
]);
}
static FormData getProFormData() {
return const FormData([
FormInput(formFieldName, Name.pure()),
FormInput(formFieldPhone, Phone.pure()),
FormInput(formFieldPro, Boolean.pure()),
FormInput(formFieldSiren, Siren.pure()),
FormInput(formFieldIban, Iban.pure()),
FormInput(
formFieldConfirmedPassword,
ConfirmedPassword.pure(),
metadata: FormInputMetadata(export: false),
),
FormEntry(formFieldName, Name.pure()),
FormEntry(formFieldPhone, Phone.pure()),
FormEntry(formFieldPro, Boolean.pure()),
FormEntry(formFieldSiren, Siren.pure()),
FormEntry(formFieldIban, Iban.pure()),
FormEntry(formFieldConfirmedPassword, ConfirmedPassword.pure(),
export: false),
]);
}
@ -109,42 +103,42 @@ class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
AuthenticationRepositoryInterface authenticationRepository =
AuthenticationRepositoryInterface _authenticationRepository =
AuthenticationRepositoryFirebase();
AuthenticationCubit authenticationCubit = AuthenticationCubit(
authenticationRepository: authenticationRepository,
AuthenticationCubit _authenticationCubit = AuthenticationCubit(
authenticationRepository: _authenticationRepository,
onAuthSuccess: onAuthSuccess,
);
SignUpCubit signUpCubit = SignUpCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
SignUpCubit _signUpCubit = SignUpCubit(
authenticationRepository: _authenticationRepository,
authenticationCubit: _authenticationCubit,
entries: getNormalFormData(),
onSignUpSuccess: onSignUpSuccess,
);
SignInCubit signInCubit = SignInCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
SignInCubit _signInCubit = SignInCubit(
authenticationRepository: _authenticationRepository,
authenticationCubit: _authenticationCubit,
);
return MultiRepositoryProvider(
providers: [
RepositoryProvider<AuthenticationRepositoryInterface>(
create: (context) => authenticationRepository,
create: (context) => _authenticationRepository,
),
],
child: MultiBlocProvider(
providers: [
BlocProvider<AuthenticationCubit>(
create: (context) => authenticationCubit..init(),
create: (context) => _authenticationCubit..init(),
),
BlocProvider<SignUpCubit>(
create: (context) => signUpCubit,
create: (context) => _signUpCubit,
),
BlocProvider<SignInCubit>(
create: (context) => signInCubit,
create: (context) => _signInCubit,
),
],
child: const AppView(),

View File

@ -37,7 +37,7 @@ class _NameInput extends StatelessWidget {
labelText: 'name',
helperText: '',
errorText:
state.data.isNotValid(formFieldName) ? 'invalid name' : null,
state.data.input(formFieldName).invalid ? 'invalid name' : null,
),
);
},
@ -58,7 +58,7 @@ class _PhoneInput extends StatelessWidget {
decoration: InputDecoration(
labelText: 'phone',
helperText: '',
errorText: state.data.isNotValid(formFieldPhone)
errorText: state.data.input(formFieldPhone).invalid
? 'invalid phone'
: null,
),
@ -81,7 +81,7 @@ class _SirenInput extends StatelessWidget {
decoration: InputDecoration(
labelText: 'siren',
helperText: '',
errorText: state.data.isNotValid(formFieldSiren)
errorText: state.data.input(formFieldSiren).invalid
? 'invalid SIREN'
: null,
),
@ -105,7 +105,7 @@ class _IbanInput extends StatelessWidget {
labelText: 'iban',
helperText: '',
errorText:
state.data.isNotValid(formFieldIban) ? 'invalid IBAN' : null,
state.data.input(formFieldIban).invalid ? 'invalid IBAN' : null,
),
);
},
@ -149,7 +149,8 @@ class _PasswordInput extends StatelessWidget {
.read<SignUpCubit>()
.state
.data
.valueOf<String, ValidationError>(formFieldConfirmedPassword),
.input(formFieldConfirmedPassword)
.value,
),
);
},
@ -184,7 +185,7 @@ class _ConfirmPasswordInput extends StatelessWidget {
decoration: InputDecoration(
labelText: 'confirm password',
helperText: '',
errorText: state.data.isNotValid(formFieldConfirmedPassword)
errorText: state.data.input(formFieldConfirmedPassword).invalid
? 'passwords do not match'
: null,
),
@ -202,7 +203,7 @@ class _CheckIsProInput extends StatelessWidget {
trailing: BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return Checkbox(
value: state.data.valueOf<bool, ValidationError>(formFieldPro),
value: state.data.input<bool>(formFieldPro).value,
onChanged: (isPro) {
final value =
isPro!; // tristate is false, so value can't be null
@ -296,7 +297,7 @@ class SignUpForm extends StatelessWidget {
const SizedBox(height: 8),
BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
if (state.data.valueOf<bool, ValidationError>(formFieldPro)) {
if (state.data.input<bool>(formFieldPro).value) {
return Column(children: [
_SirenInput(),
const SizedBox(height: 8),

View File

@ -33,7 +33,7 @@ class PasswordResetCubit extends Cubit<PasswordResetState> {
emit(
state.copyWith(
email: email,
status: FormStatus.validate([email]),
status: FormData.validate([email]),
),
);
}

View File

@ -39,7 +39,7 @@ class SignInCubit extends Cubit<SignInState> {
emit(
state.copyWith(
email: email,
status: FormStatus.validate([email, state.password]),
status: FormData.validate([email, state.password]),
),
);
}
@ -49,7 +49,7 @@ class SignInCubit extends Cubit<SignInState> {
emit(
state.copyWith(
password: password,
status: FormStatus.validate([state.email, password]),
status: FormData.validate([state.email, password]),
),
);
}

View File

@ -44,16 +44,16 @@ class SignUpCubit extends Cubit<SignUpState> {
void emailChanged(String value) {
final Email email = Email.dirty(value);
final List<FormInputValidator<dynamic, ValidationError>> toValidate = [
final List<FormInput<dynamic, ValidationError>> inputsToValidate = [
email,
state.password,
...state.data.validators<dynamic, ValidationError>(),
...state.data.inputs<dynamic>(),
];
emit(
state.copyWith(
email: email,
status: FormStatus.validate(toValidate),
status: FormData.validate(inputsToValidate),
),
);
}
@ -61,26 +61,26 @@ class SignUpCubit extends Cubit<SignUpState> {
void passwordChanged(String value) {
final Password password = Password.dirty(value);
final List<FormInputValidator<dynamic, ValidationError>> toValidate = [
final List<FormInput<dynamic, ValidationError>> inputsToValidate = [
state.email,
password,
...state.data.validators<dynamic, ValidationError>(),
...state.data.inputs<dynamic>(),
];
emit(
state.copyWith(
password: password,
status: FormStatus.validate(toValidate),
status: FormData.validate(inputsToValidate),
),
);
}
// Take from wyatt_form_bloc/wyatt_form_bloc.dart
void dataChanged<T>(String field, FormInputValidator dirtyValue) {
void dataChanged<T>(String field, FormInput dirtyValue) {
final _form = state.data.clone();
if (_form.contains(field)) {
_form.updateValidator(field, dirtyValue);
_form.update(field, dirtyValue);
} else {
throw Exception('Form field $field not found');
}
@ -88,12 +88,8 @@ class SignUpCubit extends Cubit<SignUpState> {
emit(
state.copyWith(
data: _form,
status: FormStatus.validate(
[
state.email,
state.password,
...state.data.validators<dynamic, ValidationError>(),
],
status: FormData.validate(
[state.email, state.password, ..._form.inputs<dynamic>()],
),
),
);
@ -124,12 +120,8 @@ class SignUpCubit extends Cubit<SignUpState> {
emit(
state.copyWith(
data: _form,
status: FormStatus.validate(
[
state.email,
state.password,
...state.data.validators<dynamic, ValidationError>(),
],
status: FormData.validate(
[state.email, state.password, ..._form.inputs<dynamic>()],
),
),
);

View File

@ -68,9 +68,9 @@ void main() {
SignUpCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
entries: const FormData.empty(),
entries: FormData.empty(),
).state,
const SignUpState(data: FormData.empty()),
SignUpState(data: FormData.empty()),
);
});
@ -80,11 +80,11 @@ void main() {
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
entries: const FormData.empty(),
entries: FormData.empty(),
),
act: (SignUpCubit cubit) => cubit.emailChanged(invalidEmailString),
expect: () => <SignUpState>[
const SignUpState(
SignUpState(
email: invalidEmail,
status: FormStatus.invalid,
data: FormData.empty(),
@ -97,15 +97,15 @@ void main() {
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
entries: const FormData.empty(),
entries: FormData.empty(),
),
seed: () => const SignUpState(
seed: () => SignUpState(
password: validPassword,
data: FormData.empty(),
),
act: (SignUpCubit cubit) => cubit.emailChanged(validEmailString),
expect: () => <SignUpState>[
const SignUpState(
SignUpState(
email: validEmail,
password: validPassword,
status: FormStatus.valid,
@ -121,12 +121,12 @@ void main() {
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
entries: const FormData.empty(),
entries: FormData.empty(),
),
act: (SignUpCubit cubit) =>
cubit.passwordChanged(invalidPasswordString),
expect: () => <SignUpState>[
const SignUpState(
SignUpState(
password: invalidPassword,
status: FormStatus.invalid,
data: FormData.empty(),
@ -139,15 +139,15 @@ void main() {
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
entries: const FormData.empty(),
entries: FormData.empty(),
),
seed: () => const SignUpState(
seed: () => SignUpState(
email: validEmail,
data: FormData.empty(),
),
act: (SignUpCubit cubit) => cubit.passwordChanged(validPasswordString),
expect: () => <SignUpState>[
const SignUpState(
SignUpState(
email: validEmail,
password: validPassword,
status: FormStatus.valid,
@ -163,7 +163,7 @@ void main() {
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
entries: const FormData.empty(),
entries: FormData.empty(),
),
act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(),
expect: () => const <SignUpState>[],
@ -174,9 +174,9 @@ void main() {
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
entries: const FormData.empty(),
entries: FormData.empty(),
),
seed: () => const SignUpState(
seed: () => SignUpState(
status: FormStatus.valid,
email: validEmail,
password: validPassword,
@ -199,9 +199,9 @@ void main() {
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
entries: const FormData.empty(),
entries: FormData.empty(),
),
seed: () => const SignUpState(
seed: () => SignUpState(
status: FormStatus.valid,
email: validEmail,
password: validPassword,
@ -209,13 +209,13 @@ void main() {
),
act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(),
expect: () => <SignUpState>[
const SignUpState(
SignUpState(
status: FormStatus.submissionInProgress,
email: validEmail,
password: validPassword,
data: FormData.empty(),
),
const SignUpState(
SignUpState(
status: FormStatus.submissionSuccess,
email: validEmail,
password: validPassword,
@ -238,9 +238,9 @@ void main() {
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
entries: const FormData.empty(),
entries: FormData.empty(),
),
seed: () => const SignUpState(
seed: () => SignUpState(
status: FormStatus.valid,
email: validEmail,
password: validPassword,
@ -248,13 +248,13 @@ void main() {
),
act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(),
expect: () => <SignUpState>[
const SignUpState(
SignUpState(
status: FormStatus.submissionInProgress,
email: validEmail,
password: validPassword,
data: FormData.empty(),
),
const SignUpState(
SignUpState(
status: FormStatus.submissionFailure,
email: validEmail,
password: validPassword,

View File

@ -26,10 +26,10 @@ void main() {
group('SignUpState', () {
test('supports value comparisons', () {
expect(
const SignUpState(
SignUpState(
data: FormData.empty(),
),
const SignUpState(
SignUpState(
data: FormData.empty(),
),
);
@ -37,10 +37,10 @@ void main() {
test('returns same object when no properties are passed', () {
expect(
const SignUpState(
SignUpState(
data: FormData.empty(),
).copyWith(),
const SignUpState(
SignUpState(
data: FormData.empty(),
),
);
@ -48,10 +48,10 @@ void main() {
test('returns object with updated status when status is passed', () {
expect(
const SignUpState(
SignUpState(
data: FormData.empty(),
).copyWith(status: FormStatus.pure),
const SignUpState(
SignUpState(
data: FormData.empty(),
),
);
@ -59,10 +59,10 @@ void main() {
test('returns object with updated email when email is passed', () {
expect(
const SignUpState(
SignUpState(
data: FormData.empty(),
).copyWith(email: email),
const SignUpState(
SignUpState(
email: email,
data: FormData.empty(),
),
@ -71,10 +71,10 @@ void main() {
test('returns object with updated password when password is passed', () {
expect(
const SignUpState(
SignUpState(
data: FormData.empty(),
).copyWith(password: password),
const SignUpState(
SignUpState(
password: password,
data: FormData.empty(),
),
@ -85,12 +85,12 @@ void main() {
'returns object with updated data'
' when data is passed', () {
expect(
const SignUpState(
SignUpState(
data: FormData.empty(),
).copyWith(
data: const FormData(
[
FormInput(
FormEntry(
'field',
Name.pure(),
),
@ -100,7 +100,7 @@ void main() {
const SignUpState(
data: FormData(
[
FormInput(
FormEntry(
'field',
Name.pure(),
),

View File

@ -18,13 +18,8 @@ part of 'form_data_cubit.dart';
@immutable
class FormDataState extends Equatable {
/// Global status of a form.
final FormStatus status;
/// FormData with all inputs, and associated metadata.
final FormData data;
/// Optional error message.
final String? errorMessage;
const FormDataState({

View File

@ -76,21 +76,11 @@ enum FormStatus {
/// Indicates whether the form submission has been canceled.
bool get isSubmissionCanceled => this == FormStatus.submissionCanceled;
/// Validate a list of inputs by processing them in `validate` as validators.
static FormStatus validateInputs(List<FormInput> inputs) {
return validate(
inputs
.map<FormInputValidator>((FormInput input) => input.validator)
.toList(),
);
}
/// Validate a list of validators.
static FormStatus validate(List<FormInputValidator> validators) {
return validators.every((FormInputValidator validator) => validator.pure)
/// Validate a list of inputs
static FormStatus validate(List<FormInput> inputs) {
return inputs.every((FormInput input) => input.validator.pure)
? FormStatus.pure
: validators
.any((FormInputValidator validator) => validator.valid == false)
: inputs.any((FormInput input) => input.validator.valid == false)
? FormStatus.invalid
: FormStatus.valid;
}

View File

@ -23,11 +23,8 @@ class FormData extends Equatable {
const FormData(this._inputs);
const FormData.empty() : this(const <FormInput>[]);
/// Returns all inputs as a list
List<FormInput> inputs() => _inputs;
/// Returns the input for the associated key
FormInput inputOf(String key) {
FormInput inputByKey(String key) {
if (contains(key)) {
return _inputs.firstWhere((FormInput input) => input.key == key);
} else {
@ -39,83 +36,65 @@ class FormData extends Equatable {
void updateInput(String key, FormInput input) {
if (contains(key)) {
final index = _inputs.indexOf(
inputOf(key),
inputByKey(key),
);
_inputs[index] = input;
}
}
/// Returns all associated validators as a list
List<FormInputValidator<V, E>> validators<V, E>() {
return _inputs
.map<FormInputValidator<V, E>>(
(FormInput input) => input.validator as FormInputValidator<V, E>,
)
.toList();
}
/// A [FormInputValidator] represents the value of a single form input field.
/// It contains information about the [FormInputStatus], value, as well
/// as validation status.
T validatorOf<T>(String key) {
return inputOf(key).validator as T;
}
/// Updates validator of a given input. (perform copyWith)
void updateValidator(String key, FormInputValidator dirtyValue) {
if (contains(key)) {
final index = _inputs.indexOf(
inputOf(key),
inputByKey(key),
);
_inputs[index] = _inputs[index].copyWith(validator: dirtyValue);
}
}
/// Returns a validation error if the [FormInputValidator] is invalid.
/// Returns null if the [FormInputValidator] is valid.
E? errorOf<V, E>(String key) {
return (inputOf(key).validator as FormInputValidator<V, E>).error;
}
/// The value of the associated [FormInputValidator]. For example,
/// if you have a FormInputValidator for FirstName, the value could be 'Joe'.
V valueOf<V, E>(String key) {
return (inputOf(key).validator as FormInputValidator<V, E>).value;
}
/// Returns `true` if the [FormInputValidator] is not valid.
/// Same as `E? errorOf<V, E>(String key) != null`
bool isNotValid(String key) {
return !inputOf(key).validator.valid;
}
/// Returns all associated metadata as a list
List<FormInputMetadata<M>> metadata<M>() {
return _inputs
.map<FormInputMetadata<M>>(
(FormInput input) => input.metadata as FormInputMetadata<M>,
)
.toList();
}
/// Returns the metadata associated. With `M` the type of extra data.
FormInputMetadata<M> metadataOf<M>(String key) {
return inputOf(key).metadata as FormInputMetadata<M>;
}
/// Updates metadata of a given input. (perform copyWith)
void updateMetadata(String key, FormInputMetadata meta) {
if (contains(key)) {
final index = _inputs.indexOf(
inputOf(key),
inputByKey(key),
);
_inputs[index] = _inputs[index].copyWith(metadata: meta);
}
}
/// A [FormInputValidator] represents the value of a single form input field.
/// It contains information about the [FormInputStatus], value, as well
/// as validation status.
T validatorOf<T>(String key) {
return inputByKey(key).validator as T;
}
/// Returns a validation error if the [FormInputValidator] is invalid.
/// Returns null if the [FormInputValidator] is valid.
E? errorOf<V, E>(String key) {
return (inputByKey(key).validator as FormInputValidator<V, E>).error;
}
/// The value of the associated [FormInputValidator]. For example,
/// if you have a FormInputValidator for FirstName, the value could be 'Joe'.
V valueOf<V, E>(String key) {
return (inputByKey(key).validator as FormInputValidator<V, E>).value;
}
/// Returns `true` if the [FormInputValidator] is not valid.
/// Same as `E? errorOf<V, E>(String key) != null`
bool isNotValid(String key) {
return !inputByKey(key).validator.valid;
}
/// Returns the metadata associated. With `M` the type of extra data.
FormInputMetadata<M> metadataOf<M>(String key) {
return inputByKey(key).metadata as FormInputMetadata<M>;
}
/// Validate self inputs.
FormStatus validate() {
return FormStatus.validateInputs(_inputs);
return FormStatus.validate(_inputs);
}
/// Check if this contains an input with the given key.
@ -192,7 +171,7 @@ class FormData extends Equatable {
@override
bool? get stringify => true;
@override
List<Object?> get props => _inputs;
}