Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c945cd435e
|
||
|
|
852d29ef0a
|
||
|
|
08383ec03b
|
||
|
|
ee8ce76839
|
||
|
|
f05f2230e9
|
||
|
|
54002beaab
|
||
|
|
d5698fbffc
|
||
|
|
31ad460757
|
||
|
|
4fd2a2a16c
|
||
|
|
6eb40de72c
|
||
|
|
cecbc94a95
|
||
|
|
3820fc4764
|
||
|
|
1b893a5b84
|
||
|
|
72661a343f
|
||
|
|
9ebe8e21dc
|
||
|
|
141f15b444
|
||
|
|
3a8a604a48
|
||
|
|
41493207b3
|
||
|
|
cbc4a2e149
|
||
|
|
7345e415a7
|
||
|
|
ff680614aa
|
||
|
|
3e110e2eb5
|
||
|
|
1ccb0a540e
|
||
|
|
f9da719b91
|
||
|
|
95f41025f4
|
||
|
|
eceda684f0
|
||
|
|
291c195ceb
|
||
|
|
1896497c1a
|
||
|
|
87254ef547
|
||
|
|
da34acd35b
|
||
|
|
d9d0625c67
|
||
|
|
08f789725b
|
||
|
|
33ac5c6280
|
||
|
|
762b9bcd11
|
||
|
|
38480d84f4
|
||
|
|
03a51b97ad
|
||
|
|
1dd49fa080
|
||
|
|
0219e3d557
|
||
|
|
3de7d01173
|
||
|
|
4183da6259
|
||
|
|
1f24a76717
|
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
// Dart
|
||||||
|
"dart.runPubGetOnPubspecChanges": "never",
|
||||||
|
// Gitea
|
||||||
|
"gitea.owner": "Wyatt-FOSS",
|
||||||
|
"gitea.repo": "wyatt-packages",
|
||||||
|
// Header
|
||||||
"psi-header.changes-tracking": {
|
"psi-header.changes-tracking": {
|
||||||
"isActive": true
|
"isActive": true
|
||||||
},
|
},
|
||||||
@@ -67,5 +73,4 @@
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dart.runPubGetOnPubspecChanges": false,
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,6 +3,142 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## 2022-11-16
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- [`wyatt_authentication_bloc` - `v0.4.0`](#wyatt_authentication_bloc---v040)
|
||||||
|
- [`wyatt_form_bloc` - `v0.2.0`](#wyatt_form_bloc---v020)
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- There are no other changes in this release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_authentication_bloc` - `v0.4.0`
|
||||||
|
|
||||||
|
- **REFACTOR**: update example with new input builders.
|
||||||
|
- **FIX**: add initial null account event in stream in the mock authentication.
|
||||||
|
- **BREAKING** **FIX**: add signInWithEmailAndPassword and signInAnonymously methods in SignInCubit #26.
|
||||||
|
|
||||||
|
#### `wyatt_form_bloc` - `v0.2.0`
|
||||||
|
|
||||||
|
- **FIX**: disable status selection and rebuild SubmitBuilder on every state changes #28.
|
||||||
|
- **FIX**: fix set operations behaviors.
|
||||||
|
- **FIX**: add nullable validator on boolean value.
|
||||||
|
- **FEAT**: add metadata tag to bypass validation #29.
|
||||||
|
- **FEAT**: add shorthands to access validator status.
|
||||||
|
- **BREAKING** **REFACTOR**: input builders provides FormInput.
|
||||||
|
|
||||||
|
|
||||||
|
## 2022-11-13
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- [`wyatt_crud_bloc` - `v0.1.0`](#wyatt_crud_bloc---v010)
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- There are no other changes in this release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_crud_bloc` - `v0.1.0`
|
||||||
|
|
||||||
|
- **REFACTOR**: add vscode config.
|
||||||
|
- **FIX**: update example.
|
||||||
|
- **BREAKING** **REFACTOR**: migrate to wyatt architecture.
|
||||||
|
|
||||||
|
|
||||||
|
## 2022-11-11
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`wyatt_authentication_bloc` - `v0.3.0+1`](#wyatt_authentication_bloc---v0301)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_authentication_bloc` - `v0.3.0+1`
|
||||||
|
|
||||||
|
- **FIX**: fix some bugs to validate publishing.
|
||||||
|
|
||||||
|
|
||||||
|
## 2022-11-11
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- [`wyatt_authentication_bloc` - `v0.3.0`](#wyatt_authentication_bloc---v030)
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- There are no other changes in this release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_authentication_bloc` - `v0.3.0`
|
||||||
|
|
||||||
|
- **REFACTOR**: use correct deps.
|
||||||
|
- **REFACTOR**: update example with new listeners.
|
||||||
|
- **FIX**: return exception and not throwing it anymore.
|
||||||
|
- **FIX**: exception returns msg as message.
|
||||||
|
- **FEAT**: add fully functionnal mock data source.
|
||||||
|
- **FEAT**: add toString and equality on entities and models.
|
||||||
|
- **BREAKING** **FEAT**: add email verification, password reset support.
|
||||||
|
|
||||||
|
|
||||||
|
## 2022-11-10
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`wyatt_architecture` - `v0.0.2`](#wyatt_architecture---v002)
|
||||||
|
- [`wyatt_authentication_bloc` - `v0.2.1+6`](#wyatt_authentication_bloc---v0216)
|
||||||
|
- [`wyatt_form_bloc` - `v0.1.0+1`](#wyatt_form_bloc---v0101)
|
||||||
|
|
||||||
|
Packages graduated to a stable release (see pre-releases prior to the stable version for changelog entries):
|
||||||
|
|
||||||
|
- `wyatt_architecture` - `v0.0.2`
|
||||||
|
|
||||||
|
Packages with dependency updates only:
|
||||||
|
|
||||||
|
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
||||||
|
|
||||||
|
- `wyatt_authentication_bloc` - `v0.2.1+6`
|
||||||
|
- `wyatt_form_bloc` - `v0.1.0+1`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_architecture` - `v0.0.2`
|
||||||
|
|
||||||
|
|
||||||
## 2022-11-10
|
## 2022-11-10
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 0.0.2
|
||||||
|
|
||||||
|
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
||||||
|
|
||||||
## 0.0.2-dev.0
|
## 0.0.2-dev.0
|
||||||
|
|
||||||
- **FEAT**: add exceptions, datasources, repositories, and usecases.
|
- **FEAT**: add exceptions, datasources, repositories, and usecases.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* any later version.
|
* any later version.
|
||||||
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
@@ -16,31 +16,159 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
# Flutter - Wyatt Architecture
|
# Flutter - Wyatt Architecture
|
||||||
|
|
||||||
<p align="left">
|
<p align="left">
|
||||||
<a href="https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis">
|
<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" />
|
<img src="https://img.shields.io/badge/Style-Wyatt%20Analysis-blue.svg?style=flat-square" alt="Style: Wyatt Analysis" />
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
<img src="https://img.shields.io/badge/SDK-Flutter-blue?style=flat-square" alt="SDK: Flutter" />
|
<img src="https://img.shields.io/badge/SDK-Flutter-blue?style=flat-square" alt="SDK: Flutter" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Architecture for Flutter.
|
The Wyatt Architecture for Flutter.
|
||||||
|
|
||||||
Following: <https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html>
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Usecase
|
* Usecase
|
||||||
- Repository
|
* Repository
|
||||||
- DataSource
|
* DataSource
|
||||||
- Entity
|
* Entity
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
<!-- TODO -->
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
<!-- TODO -->
|
### Domain
|
||||||
|
|
||||||
|
Create your entities by extending `Entity` :
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class Photo extends Entity {
|
||||||
|
final int id;
|
||||||
|
final String url;
|
||||||
|
|
||||||
|
const Photo({
|
||||||
|
required this.id,
|
||||||
|
required this.url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then create the data sources by extending `BaseLocalDataSource` or `BaseRemoteDataSource` depending the type of data source.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
abstract class PhotoRemoteDataSource extends BaseRemoteDataSource {
|
||||||
|
Future<Photo> getPhoto(int id);
|
||||||
|
Future<List<Photo>> getAllPhotos({int? start, int? limit});
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can create your repositories by extenting `BaseRepository` :
|
||||||
|
|
||||||
|
```dart
|
||||||
|
abstract class PhotoRepository extends BaseRepository {
|
||||||
|
FutureResult<Photo> getPhoto(int id);
|
||||||
|
FutureResult<List<Photo>> getAllPhotos({int? start, int? limit});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Here the repository is just a proxy of the data sources with result type (to have beautiful error handling).
|
||||||
|
|
||||||
|
And finaly create your different usecases by using `UseCase<Parameters, ReturnType>` :
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class RetrieveAllPhoto extends UseCase<QueryParameters, List<Photo>> {
|
||||||
|
final PhotoRepository _photoRepository;
|
||||||
|
|
||||||
|
RetrieveAllPhotos(this._photoRepository);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<List<Photo>> call(QueryParameters params) {
|
||||||
|
final photos = _photoRepository.getAllPhotos(
|
||||||
|
start: params.start,
|
||||||
|
limit: params.limit,
|
||||||
|
);
|
||||||
|
return photos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> In fact, here we need a new parameter object, so let's create it:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class QueryParameters {
|
||||||
|
final int? start;
|
||||||
|
final int? limit;
|
||||||
|
|
||||||
|
QueryParameters(this.start, this.limit);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data
|
||||||
|
|
||||||
|
We start by creating models for photos and list of photos. You can use `freezed`. The `PhotoModel` extends `Photo` with some de/serializer capabilities. And those are used only in data layer.
|
||||||
|
|
||||||
|
Then implements your data sources:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class PhotoApiDataSourceImpl extends PhotoRemoteDataSource {
|
||||||
|
final MiddlewareClient _client;
|
||||||
|
|
||||||
|
PhotoApiDataSourceImpl(this._client);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Photo> getPhoto(int id) async {
|
||||||
|
final response = await _client.get(Uri.parse('/photos/$id'));
|
||||||
|
final photo =
|
||||||
|
PhotoModel.fromJson(jsonDecode(response.body) as Map<String, Object?>);
|
||||||
|
return photo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Photo>> getAllPhotos({int? start, int? limit}) async {
|
||||||
|
final startQuery = start.isNotNull ? '_start=$start' : '';
|
||||||
|
final limitQuery = limit.isNotNull ? '_limit=$limit' : '';
|
||||||
|
final delimiter1 =
|
||||||
|
(startQuery.isNotEmpty || limitQuery.isNotEmpty) ? '?' : '';
|
||||||
|
final delimiter2 =
|
||||||
|
(startQuery.isNotEmpty && limitQuery.isNotEmpty) ? '&' : '';
|
||||||
|
final url = '/photos$delimiter1$startQuery$delimiter2$limitQuery';
|
||||||
|
final response = await _client.get(Uri.parse(url));
|
||||||
|
final photos =
|
||||||
|
ListPhotoModel.fromJson({'photos': jsonDecode(response.body)});
|
||||||
|
return photos.photos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> 1: Note that here we use `MiddlewareClient` from our http package.
|
||||||
|
|
||||||
|
> 2: You can create multiple implementations (one real and one mock for example).
|
||||||
|
|
||||||
|
And implement the repositories:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class PhotoRepositoryImpl extends PhotoRepository {
|
||||||
|
final PhotoRemoteDataSource _photoRemoteDataSource;
|
||||||
|
|
||||||
|
PhotoRepositoryImpl(
|
||||||
|
this._photoRemoteDataSource,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<Photo> getPhoto(int id) => Result.tryCatchAsync(
|
||||||
|
() => _photoRemoteDataSource.getPhoto(id),
|
||||||
|
(error) => ServerException('Cannot retrieve photo $id.'),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<List<Photo>> getAllPhotos({int? start, int? limit}) async =>
|
||||||
|
Result.tryCatchAsync(
|
||||||
|
() => _photoRemoteDataSource.getAllPhotos(start: start, limit: limit),
|
||||||
|
(error) => ServerException('Cannot retrieve all photos.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's all.
|
||||||
@@ -14,4 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
abstract class BaseDataSource {}
|
abstract class BaseDataSource {
|
||||||
|
const BaseDataSource();
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,4 +16,6 @@
|
|||||||
|
|
||||||
import 'package:wyatt_architecture/src/domain/data_sources/base_data_source.dart';
|
import 'package:wyatt_architecture/src/domain/data_sources/base_data_source.dart';
|
||||||
|
|
||||||
abstract class BaseLocalDataSource extends BaseDataSource {}
|
abstract class BaseLocalDataSource extends BaseDataSource {
|
||||||
|
const BaseLocalDataSource();
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,4 +16,6 @@
|
|||||||
|
|
||||||
import 'package:wyatt_architecture/src/domain/data_sources/base_data_source.dart';
|
import 'package:wyatt_architecture/src/domain/data_sources/base_data_source.dart';
|
||||||
|
|
||||||
abstract class BaseRemoteDataSource extends BaseDataSource {}
|
abstract class BaseRemoteDataSource extends BaseDataSource {
|
||||||
|
const BaseRemoteDataSource();
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,4 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
class BaseRepository {}
|
abstract class BaseRepository {
|
||||||
|
const BaseRepository();
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import 'package:wyatt_architecture/src/core/exceptions/exceptions.dart';
|
|||||||
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
||||||
|
|
||||||
typedef FutureResult<T> = Future<Result<T, AppException>>;
|
typedef FutureResult<T> = Future<Result<T, AppException>>;
|
||||||
|
typedef StreamResult<T> = Stream<Result<T, AppException>>;
|
||||||
|
typedef Res<T> = Result<T, AppException>;
|
||||||
|
|
||||||
// ignore: one_member_abstracts
|
// ignore: one_member_abstracts
|
||||||
abstract class UseCase<Parameters, ReturnType> {
|
abstract class UseCase<Parameters, ReturnType> {
|
||||||
|
|||||||
@@ -14,4 +14,5 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export 'no_param.dart';
|
||||||
export 'usecase.dart';
|
export 'usecase.dart';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: wyatt_architecture
|
name: wyatt_architecture
|
||||||
description: A new Wyatt package
|
description: A new Wyatt package
|
||||||
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_architecture
|
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_architecture
|
||||||
version: 0.0.2-dev.0
|
version: 0.0.2
|
||||||
|
|
||||||
publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub
|
publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub
|
||||||
|
|
||||||
|
|||||||
@@ -14,4 +14,4 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// TODO(wyatt): Add some tests
|
// Nothing to test as there is no logic in this package.
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
firebase_options.dart
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../.vscode/
|
||||||
@@ -1,3 +1,31 @@
|
|||||||
|
## 0.4.0
|
||||||
|
|
||||||
|
> Note: This release has breaking changes.
|
||||||
|
|
||||||
|
- **REFACTOR**: update example with new input builders.
|
||||||
|
- **FIX**: add initial null account event in stream in the mock authentication.
|
||||||
|
- **BREAKING** **FIX**: add signInWithEmailAndPassword and signInAnonymously methods in SignInCubit #26.
|
||||||
|
|
||||||
|
## 0.3.0+1
|
||||||
|
|
||||||
|
- **FIX**: fix some bugs to validate publishing.
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
> Note: This release has breaking changes.
|
||||||
|
|
||||||
|
- **REFACTOR**: use correct deps.
|
||||||
|
- **REFACTOR**: update example with new listeners.
|
||||||
|
- **FIX**: return exception and not throwing it anymore.
|
||||||
|
- **FIX**: exception returns msg as message.
|
||||||
|
- **FEAT**: add fully functionnal mock data source.
|
||||||
|
- **FEAT**: add toString and equality on entities and models.
|
||||||
|
- **BREAKING** **FEAT**: add email verification, password reset support.
|
||||||
|
|
||||||
|
## 0.2.1+6
|
||||||
|
|
||||||
|
- Update a dependency to the latest release.
|
||||||
|
|
||||||
## 0.2.1+5
|
## 0.2.1+5
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
- Update a dependency to the latest release.
|
||||||
|
|||||||
@@ -29,43 +29,28 @@ Authentication Bloc for Flutter.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- UserInterface
|
- Wyatt Architecture
|
||||||
* UserFirebase : FirebaseAuth user implementation
|
- Entities:
|
||||||
- AuthenticationRepositoryInterface
|
- Account : AccountModel -> Contains account information from provider
|
||||||
* AuthenticationRepositoryFirebase : FirebaseAuth implementation
|
- AccountWrapper : AccountWrapperModel -> Contains account and extra data.
|
||||||
- ExceptionsInterface
|
- Data Sources:
|
||||||
* ExceptionsFirebase : FirebaseAuth Exception parsing implementation
|
- Local:
|
||||||
- AuthenticationBloc
|
- Cached Authentication Data : AuthenticationCacheDataSourceImpl -> Provides a cache implementation
|
||||||
* Tracks every user changes
|
- Remote:
|
||||||
- Right after the listener has been registered.
|
- Remote Authentication Data : AuthenticationFirebaseDataSourceImpl -> Provides a proxy to FirebaseAuth
|
||||||
- When a user is signed in.
|
- Repositories:
|
||||||
- When the current user is signed out.
|
- AuthenticationRepository : AuthenticationRepositoryImpl -> Provides all authentication methods
|
||||||
- When there is a change in the current user's token.
|
- Features:
|
||||||
- On `refresh()`
|
- Authentication:
|
||||||
* Start/Stop listening on demand
|
- AuthenticationBuilder : widget to build reactive view from authentication state
|
||||||
- `start()` to listen to user changes
|
- AuthenticationCubit : tracks every auth changes, have sign out capability.
|
||||||
- `stop()` to cancel listener
|
- SignUp:
|
||||||
- SignUpCubit
|
- SignUpCubit: implementation of a FormDataCubit from `wyatt_form_bloc` for the sign up
|
||||||
* Handles email/password validation and password confirmation
|
- SignIn:
|
||||||
* Handles register with email/password
|
- SignUpCubit: implementation of a FormDataCubit from `wyatt_form_bloc` for the sign in
|
||||||
* Handles custom form fields thanks `wyatt_form_bloc`
|
|
||||||
- Use `entries` to pass a `FormData` object
|
|
||||||
- You can use several pre configured `FormInput` for validation
|
|
||||||
- You can use `updateFormData()` to change FormData and validators during runtime (intersection, union, difference or replace)
|
|
||||||
- SignInCubit
|
|
||||||
* Handles email/password validation
|
|
||||||
* Handles login with email/password
|
|
||||||
- EmailVerificationCubit
|
|
||||||
* Handles send email verification process
|
|
||||||
* Handles email verification check
|
|
||||||
- PasswordResetCubit
|
|
||||||
* Handles send password reset email process
|
|
||||||
- Builders
|
|
||||||
* AuthenticationBuilder to build widgets on user state changes
|
|
||||||
- Consistent
|
- Consistent
|
||||||
* Every class have same naming convention
|
* Every class have same naming convention
|
||||||
- Tested
|
- Tested
|
||||||
* Partially tested with *bloc_test*
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
@@ -77,227 +62,4 @@ import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Create an authentication repository:
|
// TODO
|
||||||
|
|
||||||
```dart
|
|
||||||
final AuthenticationRepositoryInterface _authenticationRepository = AuthenticationRepositoryFirebase();
|
|
||||||
```
|
|
||||||
|
|
||||||
Create an authentication cubit:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final AuthenticationCubit _authenticationCubit = AuthenticationCubit(
|
|
||||||
authenticationRepository: _authenticationRepository,
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a sign up cubit:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final SignUpCubit _signUpCubit = SignUpCubit(
|
|
||||||
authenticationRepository: _authenticationRepository,
|
|
||||||
authenticationCubit: _authenticationCubit,
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
You can use `AuthenticationBloc` to route your app.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
return MultiRepositoryProvider(
|
|
||||||
providers: [
|
|
||||||
RepositoryProvider<AuthenticationRepositoryInterface>(
|
|
||||||
create: (context) => _authenticationRepository,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: MultiBlocProvider(
|
|
||||||
providers: [
|
|
||||||
BlocProvider<AuthenticationCubit>(
|
|
||||||
create: (context) => _authenticationCubit..init(),
|
|
||||||
),
|
|
||||||
BlocProvider<SignUpCubit>(
|
|
||||||
create: (context) => _signUpCubit,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: const AppView(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
```
|
|
||||||
> Don't forget to call `init()` on authentication cubit.
|
|
||||||
|
|
||||||
And in `AppView` use an `AuthenticationBuilder`:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
AuthenticationBuilder(
|
|
||||||
unknown: (context) => const LoadingPage(),
|
|
||||||
unauthenticated: (context) => const LoginPage(),
|
|
||||||
authenticated: (context, user, userData) => const HomePage(),
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
To create a `SignInCubit` you'll need the same `AuthenticationRepository`, you can use the `context`:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
BlocProvider(
|
|
||||||
create: (_) => SignInCubit(context.read<AuthenticationRepositoryInterface>()),
|
|
||||||
child: const LoginForm(),
|
|
||||||
),
|
|
||||||
```
|
|
||||||
|
|
||||||
> In practice it's better to create it in the main `MultiBlocProvider` because the LoginPage can be destroyed, and cubit closed, before login flow ends
|
|
||||||
|
|
||||||
## Recipes
|
|
||||||
|
|
||||||
### Password confirmation
|
|
||||||
|
|
||||||
In this recipe we'll se how to create a custom `FormEntry` to confirm password.
|
|
||||||
|
|
||||||
First, create an entry at the SignUpCubit creation:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
SignUpCubit _signUpCubit = SignUpCubit(
|
|
||||||
authenticationRepository: _authenticationRepository,
|
|
||||||
authenticationCubit: _authenticationCubit,
|
|
||||||
entries: const FormData([
|
|
||||||
FormEntry('form_field_confirmPassword', ConfirmedPassword.pure()),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, in the sign up form, create an input for password confirmation:
|
|
||||||
|
|
||||||
- `ConfirmedPassword` validator need password value and confirm password value to compare.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (confirmPassword) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged(
|
|
||||||
'form_field_confirmPassword',
|
|
||||||
ConfirmedPassword.dirty(
|
|
||||||
password: context.read<SignUpCubit>().state.password.value,
|
|
||||||
value: confirmPassword,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
obscureText: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'confirm password',
|
|
||||||
errorText: state.data!.input('form_field_confirmPassword').invalid
|
|
||||||
? 'passwords do not match'
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
> `form_field_confirmPassword` is the field identifier used in all application to retrieve data. You can use a constant to avoid typos.
|
|
||||||
|
|
||||||
You'll need to update password input to update confirm state on password update !
|
|
||||||
|
|
||||||
```dart
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (password) {
|
|
||||||
context.read<SignUpCubit>().passwordChanged(password);
|
|
||||||
context.read<SignUpCubit>().dataChanged(
|
|
||||||
'form_field_confirmPassword',
|
|
||||||
ConfirmedPassword.dirty(
|
|
||||||
password: password,
|
|
||||||
value: context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.state
|
|
||||||
.data!
|
|
||||||
.input('form_field_confirmPassword')
|
|
||||||
.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
obscureText: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'password',
|
|
||||||
errorText: state.password.invalid ? 'invalid password' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
> Here you call standard `passwordChanged()` AND `dataChanged()`.
|
|
||||||
|
|
||||||
And voilà !
|
|
||||||
|
|
||||||
### Create Firestore Document on Sign Up
|
|
||||||
|
|
||||||
In this recipe we'll se how to create a Firestore Document on sign up success.
|
|
||||||
|
|
||||||
First create a callback function:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
Future<void> onSignUpSuccess(SignUpState state, String? uid) async {
|
|
||||||
if (uid != null) {
|
|
||||||
final user = {
|
|
||||||
'uid': uid,
|
|
||||||
'email': state.email.value,
|
|
||||||
...state.data.toMap(),
|
|
||||||
};
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).set(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then create SignUpCubit with custom entries and register callback:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
SignUpCubit _signUpCubit = SignUpCubit(
|
|
||||||
authenticationRepository: _authenticationRepository,
|
|
||||||
authenticationCubit: _authenticationCubit,
|
|
||||||
entries: const FormData([
|
|
||||||
FormEntry('form_field_name', Name.pure(), fieldName: 'name'),
|
|
||||||
FormEntry('form_field_phone', Phone.pure(), fieldName: 'phone'),
|
|
||||||
FormEntry('form_field_confirmPassword', ConfirmedPassword.pure(), export: false),
|
|
||||||
]),
|
|
||||||
onSignUpSuccess: onSignUpSuccess,
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
> Use `fieldName` and `export` to control `.toMap()` result on FormData ! Useful to disable exportation of sensible data like passwords.
|
|
||||||
|
|
||||||
Create widgets for each inputs:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
class _PhoneInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (phone) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged('form_field_phone', Phone.dirty(phone)),
|
|
||||||
keyboardType: TextInputType.phone,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'phone',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.data!.input('form_field_phone').invalid
|
|
||||||
? 'invalid phone'
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> Create widgets for Name and ConfirmedPassword too.
|
|
||||||
|
|
||||||
Then add a sign up button with:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
context.read<SignUpCubit>().signUpFormSubmitted()
|
|
||||||
```
|
|
||||||
|
|
||||||
And voilà, a document with `uid` as id, and fields `email`, `name`, `phone`, `uid` will be create in `users` collection.
|
|
||||||
@@ -1 +1,4 @@
|
|||||||
include: package:wyatt_analysis/analysis_options.flutter.yaml
|
include: package:wyatt_analysis/analysis_options.flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude: "!example/**"
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
.buildlog/
|
.buildlog/
|
||||||
.history
|
.history
|
||||||
.svn/
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
# IntelliJ related
|
# IntelliJ related
|
||||||
*.iml
|
*.iml
|
||||||
@@ -45,4 +46,4 @@ app.*.map.json
|
|||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
lib/firebase_options.dart
|
firebase_options.dart
|
||||||
@@ -1,10 +1,30 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
# This file tracks properties of this Flutter project.
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
#
|
#
|
||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: 5464c5bac742001448fe4fc0597be939379f88ea
|
revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
||||||
|
base_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
||||||
|
- platform: ios
|
||||||
|
create_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
||||||
|
base_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
## 1.0.1
|
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# authentication_bloc_example
|
# example_router
|
||||||
|
|
||||||
A new Flutter project.
|
A new Flutter project.
|
||||||
|
|
||||||
@@ -8,9 +8,9 @@ This project is a starting point for a Flutter application.
|
|||||||
|
|
||||||
A few resources to get you started if this is your first Flutter project:
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
|
||||||
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
|
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||||
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
|
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||||
|
|
||||||
For help getting started with Flutter, view our
|
For help getting started with Flutter development, view the
|
||||||
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||||
samples, guidance on mobile development, and a full API reference.
|
samples, guidance on mobile development, and a full API reference.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion flutter.compileSdkVersion
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
@@ -46,7 +47,9 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "com.example.authentication_bloc_example"
|
applicationId "com.example.example_router"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion flutter.targetSdkVersion
|
targetSdkVersion flutter.targetSdkVersion
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
{
|
|
||||||
"project_info": {
|
|
||||||
"project_number": "136771801992",
|
|
||||||
"firebase_url": "https://tchat-beta.firebaseio.com",
|
|
||||||
"project_id": "tchat-beta",
|
|
||||||
"storage_bucket": "tchat-beta.appspot.com"
|
|
||||||
},
|
|
||||||
"client": [
|
|
||||||
{
|
|
||||||
"client_info": {
|
|
||||||
"mobilesdk_app_id": "1:136771801992:android:4ff419f07afdad6097203d",
|
|
||||||
"android_client_info": {
|
|
||||||
"package_name": "com.example.authentication_bloc_example"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-nps7ic22shstvgdv0c2ojvbkj3dkntum.apps.googleusercontent.com",
|
|
||||||
"client_type": 1,
|
|
||||||
"android_info": {
|
|
||||||
"package_name": "com.example.authentication_bloc_example",
|
|
||||||
"certificate_hash": "5d8790309e13b68c35e5d4d8437c35c6d15e6131"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"api_key": [
|
|
||||||
{
|
|
||||||
"current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"services": {
|
|
||||||
"appinvite_service": {
|
|
||||||
"other_platform_oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
|
|
||||||
"client_type": 2,
|
|
||||||
"ios_info": {
|
|
||||||
"bundle_id": "com.example.example"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_info": {
|
|
||||||
"mobilesdk_app_id": "1:136771801992:android:8482c9b90bc29de697203d",
|
|
||||||
"android_client_info": {
|
|
||||||
"package_name": "com.example.crud_bloc_example"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"api_key": [
|
|
||||||
{
|
|
||||||
"current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"services": {
|
|
||||||
"appinvite_service": {
|
|
||||||
"other_platform_oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
|
|
||||||
"client_type": 2,
|
|
||||||
"ios_info": {
|
|
||||||
"bundle_id": "com.example.example"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"configuration_version": "1"
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.example.authentication_bloc_example">
|
package="com.example.example_router">
|
||||||
<!-- Flutter needs it to communicate with the running application
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.example.authentication_bloc_example">
|
package="com.example.example_router">
|
||||||
<application
|
<application
|
||||||
android:label="authentication_bloc_example"
|
android:label="example_router"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.example.authentication_bloc_example
|
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
Flutter draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
Flutter draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.example.authentication_bloc_example">
|
package="com.example.example_router">
|
||||||
<!-- Flutter needs it to communicate with the running application
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||||
// START: FlutterFire Configuration
|
// START: FlutterFire Configuration
|
||||||
classpath 'com.google.gms:google-services:4.3.10'
|
classpath 'com.google.gms:google-services:4.3.10'
|
||||||
// END: FlutterFire Configuration
|
// END: FlutterFire Configuration
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 564 B After Width: | Height: | Size: 564 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |
@@ -1,182 +0,0 @@
|
|||||||
// 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/>.
|
|
||||||
|
|
||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/constants.dart';
|
|
||||||
import 'package:authentication_bloc_example/home/home_page.dart';
|
|
||||||
import 'package:authentication_bloc_example/login/login_page.dart';
|
|
||||||
import 'package:authentication_bloc_example/model.dart';
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
|
||||||
|
|
||||||
class App extends StatelessWidget {
|
|
||||||
const App({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
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),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// On Authentication Success. (Authenticated or Anonymous)
|
|
||||||
// User callback.
|
|
||||||
Future<Map<String, dynamic>> onAuthSuccess(UserInterface user) async {
|
|
||||||
if (user.isNotEmpty && !user.isAnonymous) {
|
|
||||||
// Check if user is register in Firesore.
|
|
||||||
DocumentSnapshot firestoreUser = await FirebaseFirestore.instance
|
|
||||||
.collection(firestoreCollectionUsers)
|
|
||||||
.doc(user.uid)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (!firestoreUser.exists) {
|
|
||||||
// Register user in Firestore when sign in with social account.
|
|
||||||
final uid = user.uid;
|
|
||||||
final u = {'uid': uid, 'email': user.email};
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection(firestoreCollectionUsers)
|
|
||||||
.doc(uid)
|
|
||||||
.set(u);
|
|
||||||
return {
|
|
||||||
'user': UserFirestore(
|
|
||||||
uid: uid,
|
|
||||||
email: user.email ?? '',
|
|
||||||
name: user.displayName ?? '',
|
|
||||||
phone: user.phoneNumber ?? ''),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'user': UserFirestore.fromMap(
|
|
||||||
firestoreUser.data() as Map<String, dynamic>),
|
|
||||||
...firestoreUser.data() as Map<String, dynamic>? ?? {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// On Sign Up Success.
|
|
||||||
Future<void> onSignUpSuccess(SignUpState state, String? uid) async {
|
|
||||||
if (uid != null) {
|
|
||||||
final data = state.data.toMap();
|
|
||||||
final user = {'uid': uid, 'email': state.email.value, ...data};
|
|
||||||
log('onSignUpSuccess: $user');
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection(firestoreCollectionUsers)
|
|
||||||
.doc(uid)
|
|
||||||
.set(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
AuthenticationRepositoryInterface authenticationRepository =
|
|
||||||
AuthenticationRepositoryFirebase();
|
|
||||||
|
|
||||||
AuthenticationCubit authenticationCubit = AuthenticationCubit(
|
|
||||||
authenticationRepository: authenticationRepository,
|
|
||||||
onAuthSuccess: onAuthSuccess,
|
|
||||||
);
|
|
||||||
|
|
||||||
SignUpCubit signUpCubit = SignUpCubit(
|
|
||||||
authenticationRepository: authenticationRepository,
|
|
||||||
authenticationCubit: authenticationCubit,
|
|
||||||
entries: getNormalFormData(),
|
|
||||||
onSignUpSuccess: onSignUpSuccess,
|
|
||||||
);
|
|
||||||
|
|
||||||
SignInCubit signInCubit = SignInCubit(
|
|
||||||
authenticationRepository: authenticationRepository,
|
|
||||||
authenticationCubit: authenticationCubit,
|
|
||||||
);
|
|
||||||
|
|
||||||
return MultiRepositoryProvider(
|
|
||||||
providers: [
|
|
||||||
RepositoryProvider<AuthenticationRepositoryInterface>(
|
|
||||||
create: (context) => authenticationRepository,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: MultiBlocProvider(
|
|
||||||
providers: [
|
|
||||||
BlocProvider<AuthenticationCubit>(
|
|
||||||
create: (context) => authenticationCubit..init(),
|
|
||||||
),
|
|
||||||
BlocProvider<SignUpCubit>(
|
|
||||||
create: (context) => signUpCubit,
|
|
||||||
),
|
|
||||||
BlocProvider<SignInCubit>(
|
|
||||||
create: (context) => signInCubit,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: const AppView(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppView extends StatelessWidget {
|
|
||||||
const AppView({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return MaterialApp(
|
|
||||||
theme: ThemeData(
|
|
||||||
primarySwatch: Colors.blue,
|
|
||||||
buttonTheme: const ButtonThemeData(
|
|
||||||
buttonColor: Colors.blue,
|
|
||||||
textTheme: ButtonTextTheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
home: AuthenticationBuilder(
|
|
||||||
unknown: (context) {
|
|
||||||
return const Scaffold(
|
|
||||||
body: Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
authenticated: (context, user, userData) => const HomePage(),
|
|
||||||
unauthenticated: (context) => const LoginPage(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// -----
|
// -----
|
||||||
// File: bootstrap.dart
|
// File: bootstrap.dart
|
||||||
// Created Date: 19/08/2022 15:05:17
|
// Created Date: 19/08/2022 15:05:17
|
||||||
// Last Modified: Wed Nov 09 2022
|
// Last Modified: Fri Nov 11 2022
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2022
|
// Copyright (c) 2022
|
||||||
|
|
||||||
@@ -11,8 +11,6 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:example_router/core/dependency_injection/get_it.dart';
|
import 'package:example_router/core/dependency_injection/get_it.dart';
|
||||||
import 'package:example_router/core/utils/app_bloc_observer.dart';
|
import 'package:example_router/core/utils/app_bloc_observer.dart';
|
||||||
import 'package:example_router/firebase_options.dart';
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
@@ -25,11 +23,6 @@ Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
|
|||||||
FlutterError.onError = (details) {
|
FlutterError.onError = (details) {
|
||||||
debugPrint(details.toString());
|
debugPrint(details.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
await Firebase.initializeApp(
|
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
);
|
|
||||||
|
|
||||||
await GetItInitializer.init();
|
await GetItInitializer.init();
|
||||||
|
|
||||||
runApp(await builder());
|
runApp(await builder());
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
|
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
||||||
|
|
||||||
final getIt = GetIt.I;
|
final getIt = GetIt.I;
|
||||||
|
|
||||||
@@ -23,12 +24,33 @@ abstract class GetItInitializer {
|
|||||||
static Future<void> init() async {
|
static Future<void> init() async {
|
||||||
getIt
|
getIt
|
||||||
..registerLazySingleton<AuthenticationRemoteDataSource>(
|
..registerLazySingleton<AuthenticationRemoteDataSource>(
|
||||||
() => AuthenticationFirebaseDataSourceImpl(),
|
() => AuthenticationMockDataSourceImpl(registeredAccounts: [
|
||||||
|
Pair(
|
||||||
|
AccountModel(
|
||||||
|
uid: '1',
|
||||||
|
emailVerified: true,
|
||||||
|
isAnonymous: false,
|
||||||
|
providerId: 'wyatt',
|
||||||
|
email: 'toto@test.fr',
|
||||||
|
),
|
||||||
|
'toto1234',
|
||||||
|
),
|
||||||
|
Pair(
|
||||||
|
AccountModel(
|
||||||
|
uid: '2',
|
||||||
|
emailVerified: false,
|
||||||
|
isAnonymous: false,
|
||||||
|
providerId: 'wyatt',
|
||||||
|
email: 'tata@test.fr',
|
||||||
|
),
|
||||||
|
'tata1234',
|
||||||
|
),
|
||||||
|
]),
|
||||||
)
|
)
|
||||||
..registerLazySingleton<AuthenticationCacheDataSource<int>>(
|
..registerLazySingleton<AuthenticationCacheDataSource<int>>(
|
||||||
() => AuthenticationCacheDataSourceImpl<int>(),
|
() => AuthenticationCacheDataSourceImpl<int>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
await getIt.allReady();
|
await getIt.allReady();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// 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/>.
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/forgot_password/widgets/forgot_password_form.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
class ForgotPasswordPage extends StatelessWidget {
|
|
||||||
const ForgotPasswordPage({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
static Route route() {
|
|
||||||
return MaterialPageRoute<void>(builder: (_) => const ForgotPasswordPage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(title: const Text('Forgot Password')),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: BlocProvider(
|
|
||||||
create: (_) => PasswordResetCubit(
|
|
||||||
context.read<AuthenticationRepositoryInterface>(),
|
|
||||||
),
|
|
||||||
child: const ForgotPasswordForm(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
// 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/>.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
|
||||||
|
|
||||||
class _EmailInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<PasswordResetCubit, PasswordResetState>(
|
|
||||||
buildWhen: (previous, current) => previous.email != current.email,
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (email) =>
|
|
||||||
context.read<PasswordResetCubit>().emailChanged(email),
|
|
||||||
keyboardType: TextInputType.emailAddress,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'email',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.email.invalid ? 'invalid email' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ResetButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<PasswordResetCubit, PasswordResetState>(
|
|
||||||
buildWhen: (previous, current) => previous.status != current.status,
|
|
||||||
builder: (context, state) {
|
|
||||||
return state.status.isSubmissionInProgress
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: ElevatedButton(
|
|
||||||
onPressed: state.status.isValidated
|
|
||||||
? () => context
|
|
||||||
.read<PasswordResetCubit>()
|
|
||||||
.sendPasswordResetEmail()
|
|
||||||
: null,
|
|
||||||
child: const Text('SEND EMAIL'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ForgotPasswordForm extends StatelessWidget {
|
|
||||||
const ForgotPasswordForm({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocListener<PasswordResetCubit, PasswordResetState>(
|
|
||||||
listener: (context, state) {
|
|
||||||
if (state.status.isSubmissionSuccess) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
} else if (state.status.isSubmissionFailure) {
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
..hideCurrentSnackBar()
|
|
||||||
..showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(state.errorMessage ?? 'Password reset Failure'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Align(
|
|
||||||
alignment: const Alignment(0, -1 / 3),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [_EmailInput(), const SizedBox(height: 8), _ResetButton()],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
// 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/>.
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/home/widgets/infos.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/home/widgets/email_verification.dart';
|
|
||||||
|
|
||||||
class HomePage extends StatelessWidget {
|
|
||||||
const HomePage({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
static Route route() {
|
|
||||||
return MaterialPageRoute<void>(builder: (_) => const HomePage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final user = context.select((AuthenticationCubit cubit) => cubit.state.user);
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Home'),
|
|
||||||
actions: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.exit_to_app),
|
|
||||||
onPressed: () => context
|
|
||||||
.read<AuthenticationCubit>()
|
|
||||||
.logOut(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: BlocProvider(
|
|
||||||
create: (_) => EmailVerificationCubit(
|
|
||||||
context.read<AuthenticationRepositoryInterface>(),
|
|
||||||
)..checkEmailVerification(),
|
|
||||||
child: BlocBuilder<EmailVerificationCubit, EmailVerificationState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state.isVerified || user!.isAnonymous) {
|
|
||||||
return const UserInfo();
|
|
||||||
} else {
|
|
||||||
return const EmailVerification();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
// 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/>.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
class EmailVerification extends StatelessWidget {
|
|
||||||
const EmailVerification({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final user = context.select((AuthenticationCubit cubit) => cubit.state.user);
|
|
||||||
final userData = context.select((AuthenticationCubit cubit) => cubit.state.userData);
|
|
||||||
return Align(
|
|
||||||
alignment: const Alignment(0, -1 / 3),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
Text("Hello ${userData!['name'] ?? 'null'}!"),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("Email '${user?.email ?? 'null'}' is not verified"),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.read<EmailVerificationCubit>().sendEmailVerification();
|
|
||||||
},
|
|
||||||
child: const Text('(Re)send email'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.read<EmailVerificationCubit>().checkEmailVerification();
|
|
||||||
},
|
|
||||||
child: const Text('Refresh'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
// 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/>.
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/home/widgets/avatar.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:authentication_bloc_example/constants.dart';
|
|
||||||
|
|
||||||
class UserInfo extends StatelessWidget {
|
|
||||||
const UserInfo({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final textTheme = Theme.of(context).textTheme;
|
|
||||||
final user = context.select((AuthenticationCubit cubit) => cubit.state.user);
|
|
||||||
final userData = context.select((AuthenticationCubit cubit) => cubit.state.userData);
|
|
||||||
return Align(
|
|
||||||
alignment: const Alignment(0, -1 / 3),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
Avatar(photo: user?.photoURL),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("Email: ${user?.email ?? 'null'}", style: textTheme.headline6),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("Name: ${userData![formFieldName] ?? 'null'}",
|
|
||||||
style: textTheme.headline6),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("Phone: ${userData[formFieldPhone] ?? 'null'}",
|
|
||||||
style: textTheme.headline6),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("IsPro: ${userData[formFieldPro] ?? 'null'}",
|
|
||||||
style: textTheme.headline6),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("IsAnonymous: ${user?.isAnonymous.toString() ?? 'null'}",
|
|
||||||
style: textTheme.headline6),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("IsEmailVerified: ${user?.emailVerified.toString() ?? 'null'}",
|
|
||||||
style: textTheme.headline6),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
// 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/>.
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/app/app.dart';
|
|
||||||
import 'package:authentication_bloc_example/constants.dart';
|
|
||||||
import 'package:authentication_bloc_example/forgot_password/forgot_password_page.dart';
|
|
||||||
import 'package:authentication_bloc_example/sign_up/sign_up_page.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
|
||||||
|
|
||||||
class _EmailInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignInCubit, SignInState>(
|
|
||||||
buildWhen: (previous, current) => previous.email != current.email,
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (email) => context.read<SignInCubit>().emailChanged(email),
|
|
||||||
keyboardType: TextInputType.emailAddress,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'email',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.email.invalid ? 'invalid email' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PasswordInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignInCubit, SignInState>(
|
|
||||||
buildWhen: (previous, current) => previous.password != current.password,
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (password) =>
|
|
||||||
context.read<SignInCubit>().passwordChanged(password),
|
|
||||||
obscureText: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'password',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.password.invalid ? 'invalid password' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LoginAnonButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignInCubit, SignInState>(
|
|
||||||
buildWhen: (previous, current) => previous.status != current.status,
|
|
||||||
builder: (context, state) {
|
|
||||||
return state.status.isSubmissionInProgress
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: ElevatedButton(
|
|
||||||
onPressed: () =>
|
|
||||||
context.read<SignInCubit>().signInAnonymously(),
|
|
||||||
child: const Text('LOGIN ANONYMOUSLY'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LoginWithPasswordButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignInCubit, SignInState>(
|
|
||||||
buildWhen: (previous, current) => previous.status != current.status,
|
|
||||||
builder: (context, state) {
|
|
||||||
return state.status.isSubmissionInProgress
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: ElevatedButton(
|
|
||||||
onPressed: state.status.isValidated
|
|
||||||
? () =>
|
|
||||||
context.read<SignInCubit>().signInWithEmailAndPassword()
|
|
||||||
: null,
|
|
||||||
child: const Text('LOGIN WITH PASSWORD'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LoginWithGoogleButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignInCubit, SignInState>(
|
|
||||||
buildWhen: (previous, current) => previous.status != current.status,
|
|
||||||
builder: (context, state) {
|
|
||||||
return state.status.isSubmissionInProgress
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: ElevatedButton(
|
|
||||||
onPressed: () =>
|
|
||||||
context.read<SignInCubit>().signInWithGoogle(),
|
|
||||||
child: const Text('LOGIN GOOGLE'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SignUpButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
return TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.read<SignUpCubit>().updateFormData(App.getNormalFormData());
|
|
||||||
Navigator.of(context).push<void>(SignUpPage.route());
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'CREATE ACCOUNT',
|
|
||||||
style: TextStyle(color: theme.primaryColor),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SignUpAsProButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
return TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.read<SignUpCubit>().updateFormData(App.getProFormData());
|
|
||||||
context.read<SignUpCubit>().dataChanged(
|
|
||||||
formFieldPro,
|
|
||||||
const Boolean.dirty(value: true),
|
|
||||||
);
|
|
||||||
Navigator.of(context).push<void>(SignUpPage.route());
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'CREATE PRO ACCOUNT',
|
|
||||||
style: TextStyle(color: theme.primaryColor),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoginForm extends StatelessWidget {
|
|
||||||
const LoginForm({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocListener<SignInCubit, SignInState>(
|
|
||||||
listener: (context, state) {
|
|
||||||
if (state.status.isSubmissionFailure) {
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
..hideCurrentSnackBar()
|
|
||||||
..showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(state.errorMessage ?? 'Authentication Failure'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Align(
|
|
||||||
alignment: const Alignment(0, -1 / 3),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 120),
|
|
||||||
_EmailInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_PasswordInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).push(ForgotPasswordPage.route());
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'Forgot password ?',
|
|
||||||
style: Theme.of(context).textTheme.bodyText2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_LoginWithPasswordButton(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_LoginAnonButton(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_LoginWithGoogleButton(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_SignUpButton(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_SignUpAsProButton(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +1,6 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
import 'package:example_router/bootstrap.dart';
|
||||||
// Please see the AUTHORS file for details.
|
import 'package:example_router/presentation/features/app/app.dart';
|
||||||
//
|
|
||||||
// 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/>.
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/app/app.dart';
|
void main() {
|
||||||
import 'package:authentication_bloc_example/app/bloc_observer.dart';
|
bootstrap(App.new);
|
||||||
import 'package:authentication_bloc_example/firebase_options.dart';
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
Future<void> main() async {
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
await Firebase.initializeApp(
|
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
);
|
|
||||||
BlocOverrides.runZoned(
|
|
||||||
() => runApp(
|
|
||||||
const App(),
|
|
||||||
),
|
|
||||||
blocObserver: AppBlocObserver(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,11 @@
|
|||||||
// -----
|
// -----
|
||||||
// File: sign_in_form.dart
|
// File: sign_in_form.dart
|
||||||
// Created Date: 19/08/2022 15:24:37
|
// Created Date: 19/08/2022 15:24:37
|
||||||
// Last Modified: Thu Nov 10 2022
|
// Last Modified: Wed Nov 16 2022
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2022
|
// Copyright (c) 2022
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
@@ -17,14 +16,14 @@ class _EmailInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InputBuilder<SignInCubit<int>>(
|
return InputBuilder<SignInCubit<int>>(
|
||||||
field: AuthFormField.email,
|
field: AuthFormField.email,
|
||||||
builder: ((context, cubit, state, field, inputValid) {
|
builder: ((context, cubit, state, field, input) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (email) => cubit.emailChanged(email),
|
onChanged: (email) => cubit.emailChanged(email),
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Email',
|
labelText: 'Email',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: !inputValid ? 'Invalid email' : null,
|
errorText: input.validator.invalid ? 'Invalid email' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -37,14 +36,14 @@ class _PasswordInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InputBuilder<SignInCubit<int>>(
|
return InputBuilder<SignInCubit<int>>(
|
||||||
field: AuthFormField.password,
|
field: AuthFormField.password,
|
||||||
builder: ((context, cubit, state, field, inputValid) {
|
builder: ((context, cubit, state, field, input) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (pwd) => cubit.passwordChanged(pwd),
|
onChanged: (pwd) => cubit.passwordChanged(pwd),
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Password',
|
labelText: 'Password',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: !inputValid ? 'Invalid password' : null,
|
errorText: input.validator.invalid ? 'Invalid password' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -60,8 +59,24 @@ class _SignInButton extends StatelessWidget {
|
|||||||
return status.isSubmissionInProgress
|
return status.isSubmissionInProgress
|
||||||
? const CircularProgressIndicator()
|
? const CircularProgressIndicator()
|
||||||
: ElevatedButton(
|
: ElevatedButton(
|
||||||
onPressed: status.isValidated ? () => cubit.submit() : null,
|
onPressed: status.isValidated ? () => cubit.signInWithEmailAndPassword() : null,
|
||||||
child: const Text('Sign in'),
|
child: const Text('Sign in with credentials'),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SignInAnonymouslyButton extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SubmitBuilder<SignInCubit<int>>(
|
||||||
|
builder: ((context, cubit, status) {
|
||||||
|
return status.isSubmissionInProgress
|
||||||
|
? const CircularProgressIndicator()
|
||||||
|
: ElevatedButton(
|
||||||
|
onPressed: () => cubit.signInAnonymously(),
|
||||||
|
child: const Text('Sign in anonymously'),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -73,24 +88,23 @@ class SignInForm extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocListener<SignInCubit<int>, SignInState>(
|
return SignInListener<int>(
|
||||||
listener: (context, state) {
|
onError: (context, status, errorMessage) => ScaffoldMessenger.of(context)
|
||||||
if (state.status.isSubmissionFailure) {
|
..hideCurrentSnackBar()
|
||||||
ScaffoldMessenger.of(context)
|
..showSnackBar(
|
||||||
..hideCurrentSnackBar()
|
SnackBar(content: Text(errorMessage ?? 'Sign In Failure')),
|
||||||
..showSnackBar(
|
),
|
||||||
SnackBar(content: Text(state.errorMessage ?? 'Sign In Failure')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
_EmailInput(),
|
_EmailInput(),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_PasswordInput(),
|
_PasswordInput(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_SignInButton(),
|
_SignInButton(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_SignInAnonymouslyButton(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -3,13 +3,12 @@
|
|||||||
// -----
|
// -----
|
||||||
// File: sign_up_form.dart
|
// File: sign_up_form.dart
|
||||||
// Created Date: 19/08/2022 14:41:08
|
// Created Date: 19/08/2022 14:41:08
|
||||||
// Last Modified: Thu Nov 10 2022
|
// Last Modified: Wed Nov 16 2022
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2022
|
// Copyright (c) 2022
|
||||||
|
|
||||||
import 'package:example_router/core/constants/form_field.dart';
|
import 'package:example_router/core/constants/form_field.dart';
|
||||||
import 'package:flutter/material.dart' hide FormField;
|
import 'package:flutter/material.dart' hide FormField;
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
@@ -18,14 +17,14 @@ class _EmailInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InputBuilder<SignUpCubit<int>>(
|
return InputBuilder<SignUpCubit<int>>(
|
||||||
field: AuthFormField.email,
|
field: AuthFormField.email,
|
||||||
builder: ((context, cubit, state, field, inputValid) {
|
builder: ((context, cubit, state, field, input) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (email) => cubit.emailChanged(email),
|
onChanged: (email) => cubit.emailChanged(email),
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Email',
|
labelText: 'Email',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: !inputValid ? 'Invalid email' : null,
|
errorText: input.validator.invalid ? 'Invalid email' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -38,7 +37,7 @@ class _PasswordInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InputBuilder<SignUpCubit<int>>(
|
return InputBuilder<SignUpCubit<int>>(
|
||||||
field: AuthFormField.password,
|
field: AuthFormField.password,
|
||||||
builder: ((context, cubit, state, field, inputValid) {
|
builder: ((context, cubit, state, field, input) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (pwd) {
|
onChanged: (pwd) {
|
||||||
cubit.passwordChanged(pwd);
|
cubit.passwordChanged(pwd);
|
||||||
@@ -53,7 +52,7 @@ class _PasswordInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Password',
|
labelText: 'Password',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: !inputValid ? 'Invalid password' : null,
|
errorText: input.validator.invalid ? 'Invalid password' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -66,7 +65,7 @@ class _ConfirmPasswordInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InputBuilder<SignUpCubit<int>>(
|
return InputBuilder<SignUpCubit<int>>(
|
||||||
field: AppFormField.confirmedPassword,
|
field: AppFormField.confirmedPassword,
|
||||||
builder: ((context, cubit, state, field, inputValid) {
|
builder: ((context, cubit, state, field, input) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (pwd) {
|
onChanged: (pwd) {
|
||||||
cubit.dataChanged(
|
cubit.dataChanged(
|
||||||
@@ -81,7 +80,8 @@ class _ConfirmPasswordInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Confirm password',
|
labelText: 'Confirm password',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: !inputValid ? 'Passwords do not match' : null,
|
errorText:
|
||||||
|
input.validator.invalid ? 'Passwords do not match' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -110,29 +110,25 @@ class SignUpForm extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocListener<SignUpCubit<int>, SignUpState>(
|
return SignUpListener<int>(
|
||||||
listener: (context, state) {
|
onError: (context, status, errorMessage) =>
|
||||||
if (state.status.isSubmissionFailure) {
|
ScaffoldMessenger.of(context)
|
||||||
ScaffoldMessenger.of(context)
|
..hideCurrentSnackBar()
|
||||||
..hideCurrentSnackBar()
|
..showSnackBar(
|
||||||
..showSnackBar(
|
SnackBar(content: Text(errorMessage ?? 'Sign Up Failure')),
|
||||||
SnackBar(content: Text(state.errorMessage ?? 'Sign Up Failure')),
|
),
|
||||||
);
|
child: SingleChildScrollView(
|
||||||
}
|
child: Column(
|
||||||
},
|
children: [
|
||||||
child: SingleChildScrollView(
|
_EmailInput(),
|
||||||
child: Column(
|
const SizedBox(height: 8),
|
||||||
children: [
|
_PasswordInput(),
|
||||||
_EmailInput(),
|
const SizedBox(height: 8),
|
||||||
const SizedBox(height: 8),
|
_ConfirmPasswordInput(),
|
||||||
_PasswordInput(),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 8),
|
_SignUpButton(),
|
||||||
_ConfirmPasswordInput(),
|
],
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
_SignUpButton(),
|
));
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
// 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/>.
|
|
||||||
|
|
||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/app/app.dart';
|
|
||||||
import 'package:authentication_bloc_example/constants.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
|
||||||
|
|
||||||
class _NameInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (name) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged(formFieldName, Name.dirty(name)),
|
|
||||||
keyboardType: TextInputType.name,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'name',
|
|
||||||
helperText: '',
|
|
||||||
errorText:
|
|
||||||
state.data.isNotValid(formFieldName) ? 'invalid name' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PhoneInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (phone) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged(formFieldPhone, Phone.dirty(phone)),
|
|
||||||
keyboardType: TextInputType.phone,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'phone',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.data.isNotValid(formFieldPhone)
|
|
||||||
? 'invalid phone'
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SirenInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (siren) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged(formFieldSiren, Siren.dirty(siren)),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'siren',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.data.isNotValid(formFieldSiren)
|
|
||||||
? 'invalid SIREN'
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _IbanInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (iban) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged(formFieldIban, Iban.dirty(iban)),
|
|
||||||
keyboardType: TextInputType.text,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'iban',
|
|
||||||
helperText: '',
|
|
||||||
errorText:
|
|
||||||
state.data.isNotValid(formFieldIban) ? 'invalid IBAN' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _EmailInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
buildWhen: (previous, current) => previous.email != current.email,
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (email) => context.read<SignUpCubit>().emailChanged(email),
|
|
||||||
keyboardType: TextInputType.emailAddress,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'email',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.email.invalid ? 'invalid email' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PasswordInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (password) {
|
|
||||||
context.read<SignUpCubit>().passwordChanged(password);
|
|
||||||
context.read<SignUpCubit>().dataChanged(
|
|
||||||
formFieldConfirmedPassword,
|
|
||||||
ConfirmedPassword.dirty(
|
|
||||||
password: password,
|
|
||||||
value: context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.state
|
|
||||||
.data
|
|
||||||
.valueOf<String>(formFieldConfirmedPassword),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
obscureText: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'password',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.password.invalid ? 'invalid password' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ConfirmPasswordInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (confirmPassword) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged(
|
|
||||||
formFieldConfirmedPassword,
|
|
||||||
ConfirmedPassword.dirty(
|
|
||||||
password: context.read<SignUpCubit>().state.password.value,
|
|
||||||
value: confirmPassword,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
obscureText: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'confirm password',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.data.isNotValid(formFieldConfirmedPassword)
|
|
||||||
? 'passwords do not match'
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CheckIsProInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ListTile(
|
|
||||||
title: const Text('Are you a pro?'),
|
|
||||||
trailing: BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return Checkbox(
|
|
||||||
value: state.data.valueOf<bool>(formFieldPro),
|
|
||||||
onChanged: (isPro) {
|
|
||||||
final value =
|
|
||||||
isPro!; // tristate is false, so value can't be null
|
|
||||||
|
|
||||||
context.read<SignUpCubit>().dataChanged(
|
|
||||||
formFieldPro,
|
|
||||||
Boolean.dirty(value: value),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
context.read<SignUpCubit>().updateFormData(
|
|
||||||
App.getProFormData(),
|
|
||||||
operation: SetOperation.union);
|
|
||||||
} else {
|
|
||||||
context.read<SignUpCubit>().updateFormData(
|
|
||||||
App.getNormalFormData(),
|
|
||||||
operation: SetOperation.intersection);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SignUpButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
buildWhen: (previous, current) => previous.status != current.status,
|
|
||||||
builder: (context, state) {
|
|
||||||
return state.status.isSubmissionInProgress
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: ElevatedButton(
|
|
||||||
onPressed: state.status.isValidated
|
|
||||||
? () => context.read<SignUpCubit>().signUpFormSubmitted()
|
|
||||||
: null,
|
|
||||||
child: const Text('SIGN UP'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DebugButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// log(state.toString());
|
|
||||||
log(state.data.toMap().toString());
|
|
||||||
},
|
|
||||||
child: const Text('DEBUG'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SignUpForm extends StatelessWidget {
|
|
||||||
const SignUpForm({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocListener<SignUpCubit, SignUpState>(
|
|
||||||
listener: (context, state) {
|
|
||||||
if (state.status.isSubmissionSuccess) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
} else if (state.status.isSubmissionFailure) {
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
..hideCurrentSnackBar()
|
|
||||||
..showSnackBar(
|
|
||||||
SnackBar(content: Text(state.errorMessage ?? 'Sign Up Failure')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height,
|
|
||||||
child: Align(
|
|
||||||
alignment: const Alignment(0, -1 / 3),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
_NameInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_PhoneInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_CheckIsProInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state.data.valueOf<bool>(formFieldPro)) {
|
|
||||||
return Column(children: [
|
|
||||||
_SirenInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_IbanInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_EmailInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_PasswordInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_ConfirmPasswordInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_SignUpButton(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_DebugButton(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: authentication_bloc_example
|
name: example_router
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
|
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
@@ -15,10 +15,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.0.1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.16.2 <3.0.0"
|
sdk: ">=2.17.6 <3.0.0"
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
@@ -29,22 +29,30 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
go_router: ^5.1.5
|
||||||
|
firebase_core: ^2.1.1
|
||||||
|
flutter_bloc: ^8.1.1
|
||||||
|
get_it: ^7.2.0
|
||||||
|
|
||||||
firebase_core: ^1.14.1
|
|
||||||
flutter_bloc: ^8.0.1
|
|
||||||
wyatt_authentication_bloc:
|
wyatt_authentication_bloc:
|
||||||
path: "../"
|
path: "../"
|
||||||
|
|
||||||
wyatt_form_bloc:
|
wyatt_form_bloc:
|
||||||
git:
|
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
version: 0.2.0
|
||||||
ref: wyatt_form_bloc-v0.1.0
|
|
||||||
path: packages/wyatt_form_bloc
|
wyatt_architecture:
|
||||||
|
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
||||||
|
version: 0.0.2
|
||||||
|
|
||||||
|
wyatt_type_utils:
|
||||||
|
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
||||||
|
version: 0.0.3+1
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.4
|
cupertino_icons: ^1.0.5
|
||||||
cloud_firestore: ^3.1.12
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -60,7 +68,7 @@ dev_dependencies:
|
|||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
# The following section is specific to Flutter.
|
# The following section is specific to Flutter packages.
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
# The following line ensures that the Material Icons font is
|
# The following line ensures that the Material Icons font is
|
||||||
@@ -74,7 +82,7 @@ flutter:
|
|||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|
||||||
# For details regarding adding assets from package dependencies, see
|
# For details regarding adding assets from package dependencies, see
|
||||||
# https://flutter.dev/assets-and-images/#from-packages
|
# https://flutter.dev/assets-and-images/#from-packages
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
# Miscellaneous
|
|
||||||
*.class
|
|
||||||
*.log
|
|
||||||
*.pyc
|
|
||||||
*.swp
|
|
||||||
.DS_Store
|
|
||||||
.atom/
|
|
||||||
.buildlog/
|
|
||||||
.history
|
|
||||||
.svn/
|
|
||||||
migrate_working_dir/
|
|
||||||
|
|
||||||
# IntelliJ related
|
|
||||||
*.iml
|
|
||||||
*.ipr
|
|
||||||
*.iws
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in
|
|
||||||
# VS Code which you may wish to be included in version control, so this line
|
|
||||||
# is commented out by default.
|
|
||||||
#.vscode/
|
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
|
||||||
**/doc/api/
|
|
||||||
**/ios/Flutter/.last_build_id
|
|
||||||
.dart_tool/
|
|
||||||
.flutter-plugins
|
|
||||||
.flutter-plugins-dependencies
|
|
||||||
.packages
|
|
||||||
.pub-cache/
|
|
||||||
.pub/
|
|
||||||
/build/
|
|
||||||
|
|
||||||
# Web related
|
|
||||||
lib/generated_plugin_registrant.dart
|
|
||||||
|
|
||||||
# Symbolication related
|
|
||||||
app.*.symbols
|
|
||||||
|
|
||||||
# Obfuscation related
|
|
||||||
app.*.map.json
|
|
||||||
|
|
||||||
# Android Studio will place build artifacts here
|
|
||||||
/android/app/debug
|
|
||||||
/android/app/profile
|
|
||||||
/android/app/release
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
|
||||||
#
|
|
||||||
# This file should be version controlled.
|
|
||||||
|
|
||||||
version:
|
|
||||||
revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
|
||||||
channel: stable
|
|
||||||
|
|
||||||
project_type: app
|
|
||||||
|
|
||||||
# Tracks metadata for the flutter migrate command
|
|
||||||
migration:
|
|
||||||
platforms:
|
|
||||||
- platform: root
|
|
||||||
create_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
|
||||||
base_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
|
||||||
- platform: ios
|
|
||||||
create_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
|
||||||
base_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
|
||||||
|
|
||||||
# User provided section
|
|
||||||
|
|
||||||
# List of Local paths (relative to this file) that should be
|
|
||||||
# ignored by the migrate tool.
|
|
||||||
#
|
|
||||||
# Files that are not part of the templates will be ignored by default.
|
|
||||||
unmanaged_files:
|
|
||||||
- 'lib/main.dart'
|
|
||||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# example_router
|
|
||||||
|
|
||||||
A new Flutter project.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
This project is a starting point for a Flutter application.
|
|
||||||
|
|
||||||
A few resources to get you started if this is your first Flutter project:
|
|
||||||
|
|
||||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
|
||||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
|
||||||
|
|
||||||
For help getting started with Flutter development, view the
|
|
||||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
|
||||||
samples, guidance on mobile development, and a full API reference.
|
|
||||||