<!--
 * Copyright (C) 2022 WYATT GROUP
 * Please see the AUTHORS file for details.

 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
-->

# Flutter - Form BLoC

<p align="left">
  <a href="https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis">
    <img src="https://img.shields.io/badge/Style-Wyatt%20Analysis-blue.svg?style=flat-square" alt="Style: Wyatt Analysis" />
  </a>
  <img src="https://img.shields.io/badge/SDK-Flutter-blue?style=flat-square" alt="SDK: Flutter" />
</p>

Form Bloc for Flutter.

## Features

- Form
    * FormInputValidator:
        - Store data
        - Validate this data
    * FormInputMetadata:
        - Store infos and options about an input.
    * FormInput:
        - Associaite a key, to a validator and metadata.
    * FormData: *collection of inputs*
        - Contain all inputs
        - Basic set operation

- FormDataCubit
    * Data management behind a form.
        - Use entries to pass a FormData object
        - You can use several pre configured `FormInputValidator` for validation
        - You can use updateFormData() to change `FormData` during runtime (intersection, union, difference or replace)

- Consistent
    * Every class have same naming convention

## Getting started

Simply add `wyatt_form_bloc` in `pubspec.yaml`, then

```dart
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
```

## Usage

Firstly, you have to create your form inputs.

```dart
static List<FormInput> getNormalEntries() {
    return const [
        FormInput(formFieldName, Name.pure()),
        FormInput(formFieldEmail, Email.pure()),
        FormInput(formFieldPhone, Phone.pure()),
        FormInput(formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])),
        FormInput(formFieldRadio, TextString.pure()),
        FormInput(formFieldPro, Boolean.pure(), metadata: FormInputMetadata<void>(name: 'business')),
        FormInput(formFieldHidden, Boolean.pure(), metadata: FormInputMetadata<void>(export: false)),
    ];
}

static List<FormInput> getBusinessEntries() {
    const entries = [
        FormInput(formFieldSiren, Siren.pure()),
        FormInput(formFieldIban, Iban.pure()),
    ];
    return getNormalEntries() + entries;
}

static FormData getNormalFormData() {
    return FormData(getNormalEntries());
}

static FormData getProFormData() {
    return FormData(getBusinessEntries());
}
```

> Let's create functions to enable dynamic changes in runtime.

> When creating metadata with `FormInputMetadata<T>`, `T` is the type of `extra` object.

Then a `FormData` is a collection of inputs. You can `validate`, `contains`, `intersection`, `difference`, `union`, or `clone` this data.

After that, you have to extends `FormDataCubit`.

```dart
class CustomFormCubit extends FormDataCubit {
  CustomFormCubit({required FormData inputs}) : super(inputs: inputs);

  @override
  Future<void> submitForm() {
    log(state.data.toMap().toString());
    // Handle all your logic here.
    emit(...)
  }
}
```

> The submited form is in `state.data` !

Don't forget to create and provide it !

```dart
FormDataCubit _formCubit = CustomFormCubit(inputs: getNormalFormData());

return BlocProvider(
    create: (context) => _formCubit,
    child: const WidgetTree(),
);
```

You can now create the first form input !

```dart
class _EmailInput extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<FormDataCubit, FormDataState>(
      builder: (context, state) {
        return TextField(
          onChanged: (email) => context
              .read<FormDataCubit>()
              .dataChanged(formFieldEmail, Email.dirty(email)),
          keyboardType: TextInputType.emailAddress,
          decoration: InputDecoration(
            labelText: 'email',
            helperText: '',
            errorText: state.data.isNotValid(formFieldEmail)
                ? 'invalid email'
                : null,
          ),
        );
      },
    );
  }
}
```

Then, you can create the trigger

```dart
class _SignUpButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<FormDataCubit, FormDataState>(
      buildWhen: (previous, current) => previous.status != current.status,
      builder: (context, state) {
        return state.status.isSubmissionInProgress
            ? const CircularProgressIndicator()
            : ElevatedButton(
                onPressed: state.status.isValidated
                    ? () => context.read<FormDataCubit>().submitForm()
                    : null,
                child: const Text('SIGN UP'),
              );
      },
    );
  }
}
```

> With `state.status` you can access the status of the form to check if there is an error or if it's in submission.