<!--
 * Copyright (C) 2023 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 - Authentication 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>

Authentication Bloc for Flutter.

## Features

* 🧐 Wyatt Architecture
* 🧱 Entities
    - Account -> Contains account information from provider.
    - Session -> Contains account and associated data retrieved from an external source.
    - AuthenticationChangeEvent -> Describes an event in authentication change (sign in, sign up, sign out, etc...)
    - SessionWrapper -> Contains latest authentication change event and session.
* 🔑 Powerful and secured authentication repository
* 🔥 Multiple data sources
    - Mock
    - Firebase
* 🧊 Cubits, why make it complicated when you can make it simple?
    - Goes to the essential.
* 📐 Consistent
    - Every class have same naming convention
* 🧪 Tested
* 📚 Documented: [available here](./doc/api/index.md)

## Getting started

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

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

### Data source

The first step is to provide a data source.

```dart
getIt.registerLazySingleton<AuthenticationRemoteDataSource<int>>(
    () => AuthenticationFirebaseDataSourceImpl<int>(
        firebaseAuth: FirebaseAuth.instance,
        googleSignIn:
            GoogleSignIn(clientId: DefaultFirebaseOptions.ios.iosClientId)),
);
```

> Here we use GetIt (see example project)

### Repository

Then you can configure your repository.

```dart
final AuthenticationRepository<int> authenticationRepository = AuthenticationRepositoryImpl(
    authenticationRemoteDataSource:
        getIt<AuthenticationRemoteDataSource<int>>(),
    customPasswordValidator: const CustomPassword.pure(),
    extraSignUpInputs: [
        FormInput(
        AuthFormField.confirmPassword,
        const ConfirmedPassword.pure(),
        metadata: const FormInputMetadata<void>(export: false),
        ),
    ],
);
```

> Here we pass some extra inputs for the sign up, and a custom password validator.

### Cubits

It is necessary to implement each cubit. Don't panic, most of the work is already done 😊 you just have to customize the logic of these.

In each of these cubits it is necessary to overload the various callbacks.

> Here the associated data `Data` is a `int`

#### Authentication

In the authentication are managed, the refresh, the deletion of account or the disconnection.

```dart
class ExampleAuthenticationCubit extends AuthenticationCubit<int> {
  ExampleAuthenticationCubit({required super.authenticationRepository});

  @override
  FutureOrResult<int?> onReauthenticate(Result<Account, AppException> result) async {
    // TODO
  }

  @override
  FutureOrResult<int?> onRefresh(Result<Account, AppException> result) {
    // TODO
  }

  @override
  FutureOrResult<int?> onSignInFromCache(SessionWrapper<int> wrapper) {
    // TODO
  }

  @override
  FutureOrResult<void> onSignOut() {
    // TODO
  }

  @override
  FutureOrResult<void> onDelete() {
    // TODO
  }
}
```

#### Sign Up

```dart
class ExampleSignUpCubit extends SignUpCubit<int> {
  ExampleSignUpCubit({
    required super.authenticationRepository,
  });

  @override
  FutureOrResult<int?> onSignUpWithEmailAndPassword(Result<Account, AppException> result, WyattForm form) async {
    // TODO
  }
}

```

#### Sign In

```dart
class ExampleSignInCubit extends SignInCubit<int> {
  ExampleSignInCubit({
    required super.authenticationRepository,
  });

  @override
  FutureOrResult<int?> onSignInWithEmailAndPassword(Result<Account, AppException> result, WyattForm form) {
    // TODO
  }

  @override
  FutureOrResult<int?> onSignInAnonymously(Result<Account, AppException> result, WyattForm form) {
    // TODO
  }

  @override
  FutureOrResult<int?> onSignInWithGoogle(Result<Account, AppException> result, WyattForm form) {
    // TODO
  }
}
```

After setting up all these cubits you can provide them in the application. And that's it!

```dart
BlocProvider<SignUpCubit<int>>(
    create: (_) => ExampleSignUpCubit(
        authenticationRepository: authenticationRepository,
    ),
),
BlocProvider<SignInCubit<int>>(
    create: (_) => ExampleSignInCubit(
        authenticationRepository: authenticationRepository,
    ),
),
```

### Widgets

Widgets are provided to make your life easier. Starting with the `AuthenticationBuilder` which allows you to build according to the authentication state.

```dart
AuthenticationBuilder<int>(
    authenticated: (context, sessionWrapper) => Text(
        'Logged as ${sessionWrapper.session?.account.email} | GeneratedId is ${sessionWrapper.session?.data}'),
    unauthenticated: (context) =>
        const Text('Not logged (unauthenticated)'),
    unknown: (context) => const Text('Not logged (unknown)'),
),
```

A `BuildContext` extension is also available to access certain attributes more quickly.

```dart
Text('Home | ${context.account<AuthenticationCubit<int>, int>()?.email}'),
```

Listeners are used to listen to the status of the sign in and sign up forms.

```dart
return SignInListener<int>(
    onError: (context, status, errorMessage) => ScaffoldMessenger.of(context)
    ..hideCurrentSnackBar()
    ..showSnackBar(
        SnackBar(content: Text(errorMessage ?? 'Sign In Failure')),
    ),
    child: ...
);
```