Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7345e415a7
|
||
|
|
ff680614aa
|
||
|
|
3e110e2eb5
|
||
|
|
1ccb0a540e
|
||
|
|
f9da719b91
|
||
|
|
95f41025f4
|
||
|
|
eceda684f0
|
||
|
|
291c195ceb
|
||
|
|
1896497c1a
|
||
|
|
87254ef547
|
||
|
|
da34acd35b
|
||
|
|
d9d0625c67
|
||
|
|
08f789725b
|
||
|
|
33ac5c6280
|
||
|
|
762b9bcd11
|
||
|
|
38480d84f4
|
||
|
|
03a51b97ad
|
||
|
|
1dd49fa080
|
||
|
|
0219e3d557
|
||
|
|
3de7d01173
|
||
|
|
4183da6259
|
||
|
|
1f24a76717
|
@@ -3,6 +3,109 @@
|
|||||||
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-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
|
||||||
@@ -1,3 +1,23 @@
|
|||||||
|
## 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,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"
|
|
||||||
}
|
|
||||||
@@ -38,7 +38,7 @@ dependencies:
|
|||||||
wyatt_form_bloc:
|
wyatt_form_bloc:
|
||||||
git:
|
git:
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
||||||
ref: wyatt_form_bloc-v0.1.0
|
ref: wyatt_form_bloc-v0.1.0+1
|
||||||
path: packages/wyatt_form_bloc
|
path: packages/wyatt_form_bloc
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
|
|||||||
@@ -45,3 +45,5 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
firebase_options.dart
|
||||||
@@ -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,66 +0,0 @@
|
|||||||
// File generated by FlutterFire CLI.
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
|
|
||||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
|
||||||
import 'package:flutter/foundation.dart'
|
|
||||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
|
||||||
|
|
||||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```dart
|
|
||||||
/// import 'firebase_options.dart';
|
|
||||||
/// // ...
|
|
||||||
/// await Firebase.initializeApp(
|
|
||||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
class DefaultFirebaseOptions {
|
|
||||||
static FirebaseOptions get currentPlatform {
|
|
||||||
if (kIsWeb) {
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for web - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
switch (defaultTargetPlatform) {
|
|
||||||
case TargetPlatform.android:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for android - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
case TargetPlatform.iOS:
|
|
||||||
return ios;
|
|
||||||
case TargetPlatform.macOS:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for macos - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
case TargetPlatform.windows:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for windows - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
case TargetPlatform.linux:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for linux - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions are not supported for this platform.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const FirebaseOptions ios = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyCDbbhjbFrQwLXuIANdJzjkDk8uOETnn7w',
|
|
||||||
appId: '1:136771801992:ios:bcdca68d2b7d227097203d',
|
|
||||||
messagingSenderId: '136771801992',
|
|
||||||
projectId: 'tchat-beta',
|
|
||||||
databaseURL: 'https://tchat-beta.firebaseio.com',
|
|
||||||
storageBucket: 'tchat-beta.appspot.com',
|
|
||||||
androidClientId: '136771801992-n2pq8oqutvrqj58e05hbavvc7n1jdfjb.apps.googleusercontent.com',
|
|
||||||
iosClientId: '136771801992-p629tpo9bk3hcm2955s5ahivdla57ln9.apps.googleusercontent.com',
|
|
||||||
iosBundleId: 'com.example.exampleRouter',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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: Fri Nov 11 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';
|
||||||
|
|
||||||
@@ -73,16 +72,12 @@ 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(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -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: Fri Nov 11 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';
|
||||||
|
|
||||||
@@ -110,29 +109,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(),
|
));
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,25 +33,26 @@ dependencies:
|
|||||||
go_router: ^5.1.5
|
go_router: ^5.1.5
|
||||||
firebase_core: ^2.1.1
|
firebase_core: ^2.1.1
|
||||||
flutter_bloc: ^8.1.1
|
flutter_bloc: ^8.1.1
|
||||||
|
get_it: ^7.2.0
|
||||||
|
|
||||||
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.1.0+1
|
||||||
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:
|
wyatt_type_utils:
|
||||||
git:
|
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
version: 0.0.3+1
|
||||||
ref: wyatt_type_utils-v0.0.3+1
|
|
||||||
path: packages/wyatt_type_utils
|
|
||||||
|
|
||||||
# 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.5
|
cupertino_icons: ^1.0.5
|
||||||
get_it: ^7.2.0
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -17,4 +17,5 @@
|
|||||||
abstract class AuthFormName {
|
abstract class AuthFormName {
|
||||||
static const String signUpForm = 'wyattSignUpForm';
|
static const String signUpForm = 'wyattSignUpForm';
|
||||||
static const String signInForm = 'wyattSignInForm';
|
static const String signInForm = 'wyattSignInForm';
|
||||||
|
static const String passwordResetForm = 'wyattPasswordResetForm';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ abstract class AuthenticationFailureInterface extends AppException
|
|||||||
String code;
|
String code;
|
||||||
String msg;
|
String msg;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get message => msg;
|
||||||
|
|
||||||
AuthenticationFailureInterface(this.code, this.msg);
|
AuthenticationFailureInterface(this.code, this.msg);
|
||||||
AuthenticationFailureInterface.fromCode(this.code)
|
AuthenticationFailureInterface.fromCode(this.code)
|
||||||
: msg = 'An unknown error occurred.';
|
: msg = 'An unknown error occurred.';
|
||||||
@@ -246,3 +249,10 @@ abstract class SignOutFailureInterface extends AuthenticationFailureInterface {
|
|||||||
/// {@macro sign_out_failure}
|
/// {@macro sign_out_failure}
|
||||||
SignOutFailureInterface.fromCode(super.code) : super.fromCode();
|
SignOutFailureInterface.fromCode(super.code) : super.fromCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class GetIdTokenFailureInterface
|
||||||
|
extends AuthenticationFailureInterface {
|
||||||
|
GetIdTokenFailureInterface(super.code, super.msg);
|
||||||
|
|
||||||
|
GetIdTokenFailureInterface.fromCode(super.code) : super.fromCode();
|
||||||
|
}
|
||||||
|
|||||||
@@ -270,3 +270,10 @@ class SignOutFailureFirebase extends SignOutFailureInterface {
|
|||||||
|
|
||||||
SignOutFailureFirebase.fromCode(super.code) : super.fromCode();
|
SignOutFailureFirebase.fromCode(super.code) : super.fromCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GetIdTokenFailureFirebase extends GetIdTokenFailureInterface {
|
||||||
|
GetIdTokenFailureFirebase([String? code, String? msg])
|
||||||
|
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
|
||||||
|
|
||||||
|
GetIdTokenFailureFirebase.fromCode(super.code) : super.fromCode();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
// 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.
|
||||||
//
|
//
|
||||||
// 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 'local/authentication_cache_data_source_impl.dart';
|
export 'local/authentication_cache_data_source_impl.dart';
|
||||||
export 'remote/authentication_firebase_data_source_impl.dart';
|
export 'remote/authentication_firebase_data_source_impl.dart';
|
||||||
|
export 'remote/authentication_mock_data_source_impl.dart';
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import 'package:wyatt_architecture/wyatt_architecture.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';
|
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
||||||
|
|
||||||
@@ -28,7 +27,18 @@ class AuthenticationFirebaseDataSourceImpl
|
|||||||
|
|
||||||
Account _mapper(User user) => AccountModel(
|
Account _mapper(User user) => AccountModel(
|
||||||
uid: user.uid,
|
uid: user.uid,
|
||||||
|
emailVerified: user.emailVerified,
|
||||||
|
isAnonymous: user.isAnonymous,
|
||||||
|
providerId: user.providerData.first.providerId,
|
||||||
|
creationTime: user.metadata.creationTime,
|
||||||
|
lastSignInTime: user.metadata.lastSignInTime,
|
||||||
|
isNewUser: (user.metadata.creationTime != null &&
|
||||||
|
user.metadata.lastSignInTime != null)
|
||||||
|
? user.metadata.lastSignInTime! == user.metadata.creationTime!
|
||||||
|
: null,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
phoneNumber: user.phoneNumber,
|
||||||
|
photoURL: user.photoURL,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -55,6 +65,8 @@ class AuthenticationFirebaseDataSourceImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
||||||
|
/// {@macro signup}
|
||||||
Future<Account> signUp({
|
Future<Account> signUp({
|
||||||
required String email,
|
required String email,
|
||||||
required String password,
|
required String password,
|
||||||
@@ -95,9 +107,10 @@ class AuthenticationFirebaseDataSourceImpl
|
|||||||
} else {
|
} else {
|
||||||
throw Exception(); // Get caught just after.
|
throw Exception(); // Get caught just after.
|
||||||
}
|
}
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw GetIdTokenFailureFirebase.fromCode(e.code);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// TODO(hpcl): implement a non ambiguous exception for this case
|
throw GetIdTokenFailureFirebase();
|
||||||
throw ServerException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,4 +120,83 @@ class AuthenticationFirebaseDataSourceImpl
|
|||||||
final Account? account = (user.isNotNull) ? _mapper(user!) : null;
|
final Account? account = (user.isNotNull) ? _mapper(user!) : null;
|
||||||
return account;
|
return account;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> confirmPasswordReset({
|
||||||
|
required String code,
|
||||||
|
required String newPassword,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
await _firebaseAuth.confirmPasswordReset(
|
||||||
|
code: code,
|
||||||
|
newPassword: newPassword,
|
||||||
|
);
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw ConfirmPasswordResetFailureFirebase.fromCode(e.code);
|
||||||
|
} catch (_) {
|
||||||
|
throw ConfirmPasswordResetFailureFirebase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendEmailVerification() async {
|
||||||
|
try {
|
||||||
|
await _firebaseAuth.currentUser!.sendEmailVerification();
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw SendEmailVerificationFailureFirebase.fromCode(e.code);
|
||||||
|
} catch (_) {
|
||||||
|
throw SendEmailVerificationFailureFirebase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendPasswordResetEmail({required String email}) async {
|
||||||
|
try {
|
||||||
|
await _firebaseAuth.sendPasswordResetEmail(email: email);
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw SendPasswordResetEmailFailureFirebase.fromCode(e.code);
|
||||||
|
} catch (_) {
|
||||||
|
throw SendPasswordResetEmailFailureFirebase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Account> signInAnonymously() async {
|
||||||
|
try {
|
||||||
|
final userCredential = await _firebaseAuth.signInAnonymously();
|
||||||
|
final user = userCredential.user;
|
||||||
|
if (user.isNotNull) {
|
||||||
|
return _mapper(user!);
|
||||||
|
} else {
|
||||||
|
throw Exception(); // Get caught just after.
|
||||||
|
}
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw SignInAnonymouslyFailureFirebase.fromCode(e.code);
|
||||||
|
} catch (_) {
|
||||||
|
throw SignInAnonymouslyFailureFirebase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> verifyPasswordResetCode({required String code}) async {
|
||||||
|
try {
|
||||||
|
final email = await _firebaseAuth.verifyPasswordResetCode(code);
|
||||||
|
return email.isNotNullOrEmpty;
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw VerifyPasswordResetCodeFailureFirebase.fromCode(e.code);
|
||||||
|
} catch (_) {
|
||||||
|
throw VerifyPasswordResetCodeFailureFirebase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> refresh() async {
|
||||||
|
try {
|
||||||
|
await _firebaseAuth.currentUser!.reload();
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw RefreshFailureFirebase.fromCode(e.code);
|
||||||
|
} catch (_) {
|
||||||
|
throw RefreshFailureFirebase();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
// 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:async';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
|
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
||||||
|
|
||||||
|
class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource {
|
||||||
|
Pair<Account, String>? _connectedMock;
|
||||||
|
Pair<Account, String>? _registeredMock;
|
||||||
|
final StreamController<Account?> _streamAccount = StreamController();
|
||||||
|
|
||||||
|
final List<Pair<Account, String>>? registeredAccounts;
|
||||||
|
final String idToken;
|
||||||
|
|
||||||
|
AuthenticationMockDataSourceImpl({
|
||||||
|
this.idToken = 'fake-id-token',
|
||||||
|
this.registeredAccounts,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> _randomDelay() async {
|
||||||
|
await Future<void>.delayed(
|
||||||
|
Duration(milliseconds: Random().nextInt(400) + 200),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> confirmPasswordReset({
|
||||||
|
required String code,
|
||||||
|
required String newPassword,
|
||||||
|
}) async {
|
||||||
|
await _randomDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> getIdentityToken() async {
|
||||||
|
await _randomDelay();
|
||||||
|
return idToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> refresh() async {
|
||||||
|
await _randomDelay();
|
||||||
|
if (_connectedMock.isNull) {
|
||||||
|
throw RefreshFailureFirebase();
|
||||||
|
}
|
||||||
|
final refresh = DateTime.now();
|
||||||
|
final mock = (_connectedMock?.left as AccountModel?)
|
||||||
|
?.copyWith(lastSignInTime: refresh);
|
||||||
|
_connectedMock = _connectedMock?.copyWith(left: mock);
|
||||||
|
_streamAccount.add(mock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendEmailVerification() async {
|
||||||
|
await _randomDelay();
|
||||||
|
if (_connectedMock.isNotNull) {
|
||||||
|
final refresh = DateTime.now();
|
||||||
|
final mock = (_connectedMock?.left as AccountModel?)?.copyWith(
|
||||||
|
emailVerified: false,
|
||||||
|
lastSignInTime: refresh,
|
||||||
|
);
|
||||||
|
_streamAccount.add(mock);
|
||||||
|
_connectedMock = _connectedMock?.copyWith(left: mock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw SendEmailVerificationFailureFirebase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendPasswordResetEmail({required String email}) async {
|
||||||
|
await _randomDelay();
|
||||||
|
if (registeredAccounts.isNotNull) {
|
||||||
|
final accounts =
|
||||||
|
registeredAccounts?.where((pair) => pair.left?.email == email);
|
||||||
|
if (accounts.isNotNullOrEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_registeredMock.isNotNull) {
|
||||||
|
if (_registeredMock?.left?.email != email) {
|
||||||
|
throw SendPasswordResetEmailFailureFirebase();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw SendPasswordResetEmailFailureFirebase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Account> signInAnonymously() async {
|
||||||
|
await _randomDelay();
|
||||||
|
final creation = DateTime.now();
|
||||||
|
final mock = AccountModel(
|
||||||
|
uid: 'mock-id-anom',
|
||||||
|
emailVerified: false,
|
||||||
|
isAnonymous: true,
|
||||||
|
providerId: 'wyatt',
|
||||||
|
creationTime: creation,
|
||||||
|
lastSignInTime: creation,
|
||||||
|
);
|
||||||
|
_streamAccount.add(mock);
|
||||||
|
_connectedMock = _connectedMock?.copyWith(left: mock);
|
||||||
|
return Future.value(mock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Account> signInWithEmailAndPassword({
|
||||||
|
required String email,
|
||||||
|
required String password,
|
||||||
|
}) async {
|
||||||
|
await _randomDelay();
|
||||||
|
if (registeredAccounts.isNotNull) {
|
||||||
|
final accounts =
|
||||||
|
registeredAccounts?.where((pair) => pair.left?.email == email);
|
||||||
|
if (accounts.isNotNullOrEmpty) {
|
||||||
|
final account = accounts?.first;
|
||||||
|
if (account?.right != password) {
|
||||||
|
throw SignInWithCredentialFailureFirebase.fromCode('wrong-password');
|
||||||
|
}
|
||||||
|
_streamAccount.add(account!.left);
|
||||||
|
_connectedMock = account.copyWith();
|
||||||
|
return account.left!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_registeredMock.isNotNull) {
|
||||||
|
if (_registeredMock?.left?.email != email) {
|
||||||
|
throw SignInWithCredentialFailureFirebase.fromCode('user-not-found');
|
||||||
|
}
|
||||||
|
if (_registeredMock?.right != password) {
|
||||||
|
throw SignInWithCredentialFailureFirebase.fromCode('wrong-password');
|
||||||
|
}
|
||||||
|
_streamAccount.add(_registeredMock!.left);
|
||||||
|
_connectedMock = _registeredMock!.copyWith();
|
||||||
|
return _registeredMock!.left!;
|
||||||
|
}
|
||||||
|
throw SignInWithCredentialFailureFirebase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> signOut() async {
|
||||||
|
_connectedMock = null;
|
||||||
|
_streamAccount.add(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Account> signUp({
|
||||||
|
required String email,
|
||||||
|
required String password,
|
||||||
|
}) async {
|
||||||
|
await _randomDelay();
|
||||||
|
if (registeredAccounts.isNotNull) {
|
||||||
|
final accounts =
|
||||||
|
registeredAccounts?.where((pair) => pair.left?.email == email);
|
||||||
|
if (accounts.isNotNullOrEmpty) {
|
||||||
|
throw SignUpWithEmailAndPasswordFailureFirebase.fromCode(
|
||||||
|
'email-already-in-use',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_registeredMock?.left?.email == email) {
|
||||||
|
throw SignUpWithEmailAndPasswordFailureFirebase.fromCode(
|
||||||
|
'email-already-in-use',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final creation = DateTime.now();
|
||||||
|
final mock = AccountModel(
|
||||||
|
uid: 'mock-id-email',
|
||||||
|
emailVerified: false,
|
||||||
|
isAnonymous: false,
|
||||||
|
providerId: 'wyatt',
|
||||||
|
email: email,
|
||||||
|
creationTime: creation,
|
||||||
|
lastSignInTime: creation,
|
||||||
|
);
|
||||||
|
_streamAccount.add(mock);
|
||||||
|
_registeredMock = Pair(mock, password);
|
||||||
|
return Future.value(mock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Account?> streamAccount() => _streamAccount.stream.asBroadcastStream();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> verifyPasswordResetCode({required String code}) async {
|
||||||
|
await _randomDelay();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
@@ -16,12 +17,71 @@
|
|||||||
|
|
||||||
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
|
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
|
||||||
|
|
||||||
class AccountModel implements Account {
|
class AccountModel extends Account {
|
||||||
@override
|
@override
|
||||||
final String uid;
|
final String uid;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String? email;
|
final String? email;
|
||||||
|
|
||||||
AccountModel({required this.uid, required this.email});
|
@override
|
||||||
|
final DateTime? creationTime;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool emailVerified;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool isAnonymous;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool? isNewUser;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final DateTime? lastSignInTime;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? phoneNumber;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? photoURL;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String providerId;
|
||||||
|
|
||||||
|
AccountModel({
|
||||||
|
required this.uid,
|
||||||
|
required this.emailVerified,
|
||||||
|
required this.isAnonymous,
|
||||||
|
required this.providerId,
|
||||||
|
this.lastSignInTime,
|
||||||
|
this.creationTime,
|
||||||
|
this.isNewUser,
|
||||||
|
this.email,
|
||||||
|
this.phoneNumber,
|
||||||
|
this.photoURL,
|
||||||
|
});
|
||||||
|
|
||||||
|
AccountModel copyWith({
|
||||||
|
String? uid,
|
||||||
|
String? email,
|
||||||
|
DateTime? creationTime,
|
||||||
|
bool? emailVerified,
|
||||||
|
bool? isAnonymous,
|
||||||
|
bool? isNewUser,
|
||||||
|
DateTime? lastSignInTime,
|
||||||
|
String? phoneNumber,
|
||||||
|
String? photoURL,
|
||||||
|
String? providerId,
|
||||||
|
}) => AccountModel(
|
||||||
|
uid: uid ?? this.uid,
|
||||||
|
email: email ?? this.email,
|
||||||
|
creationTime: creationTime ?? this.creationTime,
|
||||||
|
emailVerified: emailVerified ?? this.emailVerified,
|
||||||
|
isAnonymous: isAnonymous ?? this.isAnonymous,
|
||||||
|
isNewUser: isNewUser ?? this.isNewUser,
|
||||||
|
lastSignInTime: lastSignInTime ?? this.lastSignInTime,
|
||||||
|
phoneNumber: phoneNumber ?? this.phoneNumber,
|
||||||
|
photoURL: photoURL ?? this.photoURL,
|
||||||
|
providerId: providerId ?? this.providerId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
@@ -24,4 +25,12 @@ class AccountWrapperModel<T> extends AccountWrapper<T> {
|
|||||||
final T? data;
|
final T? data;
|
||||||
|
|
||||||
AccountWrapperModel(this.account, this.data);
|
AccountWrapperModel(this.account, this.data);
|
||||||
|
|
||||||
|
AccountWrapperModel<T> copyWith({
|
||||||
|
Account? account,
|
||||||
|
T? data,
|
||||||
|
}) => AccountWrapperModel<T>(
|
||||||
|
account ?? this.account,
|
||||||
|
data ?? this.data,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class AuthenticationRepositoryImpl<T extends Object>
|
|||||||
);
|
);
|
||||||
await dataResult.foldAsync(
|
await dataResult.foldAsync(
|
||||||
_authenticationLocalDataSource.storeData,
|
_authenticationLocalDataSource.storeData,
|
||||||
(error) => throw error,
|
(error) async => error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return account;
|
return account;
|
||||||
@@ -208,4 +208,70 @@ class AuthenticationRepositoryImpl<T extends Object>
|
|||||||
AccountWrapperModel<T>(account, null),
|
AccountWrapperModel<T>(account, null),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<void> confirmPasswordReset({
|
||||||
|
required String code,
|
||||||
|
required String newPassword,
|
||||||
|
}) =>
|
||||||
|
Result.tryCatchAsync<void, AppException, AppException>(
|
||||||
|
() async {
|
||||||
|
await _authenticationRemoteDataSource.confirmPasswordReset(
|
||||||
|
code: code,
|
||||||
|
newPassword: newPassword,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<void> sendEmailVerification() =>
|
||||||
|
Result.tryCatchAsync<void, AppException, AppException>(
|
||||||
|
() async {
|
||||||
|
await _authenticationRemoteDataSource.sendEmailVerification();
|
||||||
|
},
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<void> sendPasswordResetEmail({required String email}) =>
|
||||||
|
Result.tryCatchAsync<void, AppException, AppException>(
|
||||||
|
() async {
|
||||||
|
await _authenticationRemoteDataSource.sendPasswordResetEmail(
|
||||||
|
email: email,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<Account> signInAnonymously() =>
|
||||||
|
Result.tryCatchAsync<Account, AppException, AppException>(
|
||||||
|
() async {
|
||||||
|
final account =
|
||||||
|
await _authenticationRemoteDataSource.signInAnonymously();
|
||||||
|
return account;
|
||||||
|
},
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<bool> verifyPasswordResetCode({required String code}) =>
|
||||||
|
Result.tryCatchAsync<bool, AppException, AppException>(
|
||||||
|
() async {
|
||||||
|
final response = await _authenticationRemoteDataSource
|
||||||
|
.verifyPasswordResetCode(code: code);
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<void> refresh() =>
|
||||||
|
Result.tryCatchAsync<void, AppException, AppException>(
|
||||||
|
() async {
|
||||||
|
await _authenticationRemoteDataSource.refresh();
|
||||||
|
},
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,22 @@ abstract class AuthenticationRemoteDataSource extends BaseRemoteDataSource {
|
|||||||
|
|
||||||
Future<void> signOut();
|
Future<void> signOut();
|
||||||
|
|
||||||
|
Future<void> refresh();
|
||||||
|
|
||||||
Stream<Account?> streamAccount();
|
Stream<Account?> streamAccount();
|
||||||
|
|
||||||
Future<String> getIdentityToken();
|
Future<String> getIdentityToken();
|
||||||
|
|
||||||
|
Future<void> sendEmailVerification();
|
||||||
|
|
||||||
|
Future<void> sendPasswordResetEmail({required String email});
|
||||||
|
|
||||||
|
Future<void> confirmPasswordReset({
|
||||||
|
required String code,
|
||||||
|
required String newPassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<bool> verifyPasswordResetCode({required String code});
|
||||||
|
|
||||||
|
Future<Account> signInAnonymously();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,10 @@
|
|||||||
// 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/>.
|
||||||
|
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:wyatt_architecture/wyatt_architecture.dart';
|
import 'package:wyatt_architecture/wyatt_architecture.dart';
|
||||||
|
|
||||||
abstract class Account extends Entity {
|
abstract class Account extends Equatable implements Entity {
|
||||||
/// The user's unique ID.
|
/// The user's unique ID.
|
||||||
String get uid;
|
String get uid;
|
||||||
|
|
||||||
@@ -24,4 +25,59 @@ abstract class Account extends Entity {
|
|||||||
///
|
///
|
||||||
/// Will be `null` if signing in anonymously.
|
/// Will be `null` if signing in anonymously.
|
||||||
String? get email;
|
String? get email;
|
||||||
|
|
||||||
|
/// Returns whether the users email address has been verified.
|
||||||
|
///
|
||||||
|
/// To send a verification email, see `SendEmailVerification`.
|
||||||
|
bool get emailVerified;
|
||||||
|
|
||||||
|
/// Returns whether the user is a anonymous.
|
||||||
|
bool get isAnonymous;
|
||||||
|
|
||||||
|
/// Returns the users account creation time.
|
||||||
|
///
|
||||||
|
/// When this account was created as dictated by the server clock.
|
||||||
|
DateTime? get creationTime;
|
||||||
|
|
||||||
|
/// When the user last signed in as dictated by the server clock.
|
||||||
|
DateTime? get lastSignInTime;
|
||||||
|
|
||||||
|
/// Returns the users phone number.
|
||||||
|
///
|
||||||
|
/// This property will be `null` if the user has not signed in or been has
|
||||||
|
/// their phone number linked.
|
||||||
|
String? get phoneNumber;
|
||||||
|
|
||||||
|
/// Returns a photo URL for the user.
|
||||||
|
///
|
||||||
|
/// This property will be populated if the user has signed in or been linked
|
||||||
|
/// with a 3rd party OAuth provider (such as Google).
|
||||||
|
String? get photoURL;
|
||||||
|
|
||||||
|
/// The provider ID for the user.
|
||||||
|
String get providerId;
|
||||||
|
|
||||||
|
/// Whether the user account has been recently created.
|
||||||
|
bool? get isNewUser;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
uid,
|
||||||
|
email,
|
||||||
|
emailVerified,
|
||||||
|
isAnonymous,
|
||||||
|
creationTime,
|
||||||
|
lastSignInTime,
|
||||||
|
phoneNumber,
|
||||||
|
photoURL,
|
||||||
|
providerId,
|
||||||
|
isNewUser,
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AccountModel(uid: $uid, email: $email, '
|
||||||
|
'creationTime: $creationTime, emailVerified: $emailVerified, '
|
||||||
|
'isAnonymous: $isAnonymous, isNewUser: $isNewUser, lastSignInTime: '
|
||||||
|
'$lastSignInTime, phoneNumber: $phoneNumber, photoURL: $photoURL, '
|
||||||
|
'providerId: $providerId)';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,17 @@
|
|||||||
// 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/>.
|
||||||
|
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:wyatt_architecture/wyatt_architecture.dart';
|
import 'package:wyatt_architecture/wyatt_architecture.dart';
|
||||||
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
|
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
|
||||||
|
|
||||||
abstract class AccountWrapper<T> extends Entity {
|
abstract class AccountWrapper<T> extends Equatable implements Entity {
|
||||||
Account? get account;
|
Account? get account;
|
||||||
T? get data;
|
T? get data;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [account, data];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AccountWrapper($account, data: $data)';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,18 +22,83 @@ import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
|||||||
abstract class AuthenticationRepository<T> extends BaseRepository {
|
abstract class AuthenticationRepository<T> extends BaseRepository {
|
||||||
FormRepository get formRepository;
|
FormRepository get formRepository;
|
||||||
|
|
||||||
|
/// {@template signup}
|
||||||
|
/// Creates a new user with the provided [email] and [password].
|
||||||
|
///
|
||||||
|
/// Returns the newly created user's unique identifier.
|
||||||
|
///
|
||||||
|
/// Throws a SignUpWithEmailAndPasswordFailureInterface if
|
||||||
|
/// an exception occurs.
|
||||||
|
/// {@endtemplate}
|
||||||
FutureResult<Account> signUp({
|
FutureResult<Account> signUp({
|
||||||
required String email,
|
required String email,
|
||||||
required String password,
|
required String password,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// {@template send_email_verification}
|
||||||
|
/// Sends verification email to the account email.
|
||||||
|
///
|
||||||
|
/// Throws a SendEmailVerificationFailureInterface if an exception occurs.
|
||||||
|
/// {@endtemplate}
|
||||||
|
FutureResult<void> sendEmailVerification();
|
||||||
|
|
||||||
|
/// {@template send_password_reset_email}
|
||||||
|
/// Sends a password reset email to the provided [email].
|
||||||
|
///
|
||||||
|
/// Throws a SendPasswordResetEmailFailureInterface if an exception occurs.
|
||||||
|
/// {@endtemplate}
|
||||||
|
FutureResult<void> sendPasswordResetEmail({required String email});
|
||||||
|
|
||||||
|
/// {@template confirm_password_reset}
|
||||||
|
/// Confirms the password reset with the provided [newPassword] and [code].
|
||||||
|
///
|
||||||
|
/// Throws a ConfirmPasswordResetFailureInterface if an exception occurs.
|
||||||
|
/// {@endtemplate}
|
||||||
|
FutureResult<void> confirmPasswordReset({
|
||||||
|
required String code,
|
||||||
|
required String newPassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// {@template verify_password_reset_code}
|
||||||
|
/// Verify password reset code.
|
||||||
|
///
|
||||||
|
/// Throws a VerifyPasswordResetCodeFailureInterface if an exception occurs.
|
||||||
|
/// {@endtemplate}
|
||||||
|
FutureResult<bool> verifyPasswordResetCode({required String code});
|
||||||
|
|
||||||
|
/// {@template signin_anom}
|
||||||
|
/// Sign in anonymously.
|
||||||
|
///
|
||||||
|
/// Throws a SignInAnonymouslyFailureInterface if an exception occurs.
|
||||||
|
/// {@endtemplate}
|
||||||
|
FutureResult<Account> signInAnonymously();
|
||||||
|
|
||||||
|
/// {@template signin_pwd}
|
||||||
|
/// Signs in with the provided [email] and [password].
|
||||||
|
///
|
||||||
|
/// Throws a SignInWithEmailAndPasswordFailureInterface if
|
||||||
|
/// an exception occurs.
|
||||||
|
/// {@endtemplate}
|
||||||
FutureResult<Account> signInWithEmailAndPassword({
|
FutureResult<Account> signInWithEmailAndPassword({
|
||||||
required String email,
|
required String email,
|
||||||
required String password,
|
required String password,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// {@template signout}
|
||||||
|
/// Signs out the current user.
|
||||||
|
/// It also clears the cache and the associated data.
|
||||||
|
/// {@endtemplate}
|
||||||
FutureResult<void> signOut();
|
FutureResult<void> signOut();
|
||||||
|
|
||||||
|
FutureResult<void> refresh();
|
||||||
|
|
||||||
|
/// {@template stream_account}
|
||||||
|
/// Stream of [AccountWrapper] which will emit the current account when
|
||||||
|
/// the authentication state changes.
|
||||||
|
///
|
||||||
|
/// Emits [AccountWrapper] with null [Account] if the user is not
|
||||||
|
/// authenticated.
|
||||||
|
/// {@endtemplate}
|
||||||
Stream<FutureResult<AccountWrapper<T>>> streamAccount();
|
Stream<FutureResult<AccountWrapper<T>>> streamAccount();
|
||||||
|
|
||||||
FutureResult<String> getIdentityToken();
|
FutureResult<String> getIdentityToken();
|
||||||
|
|||||||
@@ -40,16 +40,16 @@ class AuthenticationCubit<Extra> extends Cubit<AuthenticationState<Extra>> {
|
|||||||
accountFutureResult.fold(
|
accountFutureResult.fold(
|
||||||
(value) {
|
(value) {
|
||||||
if (value.account.isNotNull) {
|
if (value.account.isNotNull) {
|
||||||
emit(AuthenticationState.authenticated(value));
|
emit(AuthenticationState<Extra>.authenticated(value));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_authenticationRepository.destroyCache();
|
_authenticationRepository.destroyCache();
|
||||||
emit(const AuthenticationState.unauthenticated());
|
emit(AuthenticationState<Extra>.unauthenticated());
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
(error) {
|
(error) {
|
||||||
_authenticationRepository.destroyCache();
|
_authenticationRepository.destroyCache();
|
||||||
emit(const AuthenticationState.unauthenticated());
|
emit(AuthenticationState<Extra>.unauthenticated());
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class AuthenticationState<Extra> extends Equatable {
|
|||||||
: this._(status: AuthenticationStatus.unauthenticated);
|
: this._(status: AuthenticationStatus.unauthenticated);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [status];
|
List<Object?> get props => [status, accountWrapper];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// 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/src/features/email_verification/cubit/email_verification_cubit.dart';
|
||||||
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
|
class EmailVerificationBuilder<Extra> extends StatelessWidget {
|
||||||
|
const EmailVerificationBuilder({
|
||||||
|
required this.verified,
|
||||||
|
required this.notVerified,
|
||||||
|
required this.onError,
|
||||||
|
this.customBuilder,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget Function(BuildContext context) verified;
|
||||||
|
final Widget Function(BuildContext context) notVerified;
|
||||||
|
final Widget Function(
|
||||||
|
BuildContext context,
|
||||||
|
FormStatus status,
|
||||||
|
String? errorMessage,
|
||||||
|
) onError;
|
||||||
|
|
||||||
|
final Widget Function(BuildContext context, EmailVerificationState)?
|
||||||
|
customBuilder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) =>
|
||||||
|
BlocBuilder<EmailVerificationCubit<Extra>, EmailVerificationState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (customBuilder != null) {
|
||||||
|
return customBuilder!(context, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.status.isSubmissionFailure ||
|
||||||
|
state.status.isSubmissionCanceled) {
|
||||||
|
return onError(
|
||||||
|
context,
|
||||||
|
state.status,
|
||||||
|
state.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.isVerified) {
|
||||||
|
return verified(context);
|
||||||
|
}
|
||||||
|
return notVerified(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// 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:async';
|
||||||
|
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:wyatt_authentication_bloc/src/domain/repositories/repositories.dart';
|
||||||
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
|
part 'email_verification_state.dart';
|
||||||
|
|
||||||
|
class EmailVerificationCubit<Extra> extends Cubit<EmailVerificationState> {
|
||||||
|
final AuthenticationRepository<Extra> _authenticationRepository;
|
||||||
|
|
||||||
|
EmailVerificationCubit({
|
||||||
|
required AuthenticationRepository<Extra> authenticationRepository,
|
||||||
|
}) : _authenticationRepository = authenticationRepository,
|
||||||
|
super(const EmailVerificationState());
|
||||||
|
|
||||||
|
FutureOr<void> sendEmailVerification() async {
|
||||||
|
emit(state.copyWith(status: FormStatus.submissionInProgress));
|
||||||
|
final response = await _authenticationRepository.sendEmailVerification();
|
||||||
|
emit(
|
||||||
|
response.fold(
|
||||||
|
(value) => state.copyWith(status: FormStatus.submissionSuccess),
|
||||||
|
(error) => state.copyWith(
|
||||||
|
errorMessage: error.message,
|
||||||
|
status: FormStatus.submissionFailure,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> checkEmailVerification() async {
|
||||||
|
emit(state.copyWith(status: FormStatus.submissionInProgress));
|
||||||
|
|
||||||
|
final refresh = await _authenticationRepository.refresh();
|
||||||
|
if (refresh.isErr) {
|
||||||
|
final refreshError = refresh.err!;
|
||||||
|
emit(
|
||||||
|
EmailVerificationState(
|
||||||
|
errorMessage: refreshError.message,
|
||||||
|
status: FormStatus.submissionFailure,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentAccount = await _authenticationRepository.getAccount();
|
||||||
|
emit(
|
||||||
|
currentAccount.fold(
|
||||||
|
(value) => state.copyWith(
|
||||||
|
isVerified: value.emailVerified,
|
||||||
|
status: FormStatus.submissionSuccess,
|
||||||
|
),
|
||||||
|
(error) => EmailVerificationState(
|
||||||
|
errorMessage: error.message,
|
||||||
|
status: FormStatus.submissionFailure,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
@@ -14,47 +15,35 @@
|
|||||||
// 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/>.
|
||||||
|
|
||||||
part of 'crud_cubit.dart';
|
part of 'email_verification_cubit.dart';
|
||||||
|
|
||||||
enum CrudStatus {
|
class EmailVerificationState extends Equatable {
|
||||||
idle,
|
final FormStatus status;
|
||||||
loading,
|
final bool isVerified;
|
||||||
success,
|
|
||||||
failure,
|
|
||||||
}
|
|
||||||
|
|
||||||
class CrudState<T> extends Equatable {
|
|
||||||
final CrudStatus status;
|
|
||||||
final List<T?> data;
|
|
||||||
final Stream<List<T?>>? stream;
|
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
|
||||||
const CrudState({
|
const EmailVerificationState({
|
||||||
this.status = CrudStatus.idle,
|
this.isVerified = false,
|
||||||
this.data = const [],
|
this.status = FormStatus.pure,
|
||||||
this.stream,
|
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
EmailVerificationState copyWith({
|
||||||
List<Object?> get props => [status, data];
|
FormStatus? status,
|
||||||
|
bool? isVerified,
|
||||||
CrudState<T> copyWith({
|
|
||||||
CrudStatus? status,
|
|
||||||
List<T?>? data,
|
|
||||||
Stream<List<T?>>? stream,
|
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
}) {
|
}) =>
|
||||||
return CrudState<T>(
|
EmailVerificationState(
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
data: data ?? this.data,
|
isVerified: isVerified ?? this.isVerified,
|
||||||
stream: stream ?? this.stream,
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
errorMessage: errorMessage ?? this.errorMessage,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
List<Object?> get props => [status, isVerified, errorMessage];
|
||||||
// ignore: lines_longer_than_80_chars
|
|
||||||
'CrudState(status: $status, data: $data, stream: ${stream != null ? 'listening' : 'null'}, errorMessage: $errorMessage)';
|
@override
|
||||||
|
String toString() => 'EmailVerificationState(status: ${status.name} '
|
||||||
|
'${(errorMessage != null) ? " [$errorMessage]" : ""}, '
|
||||||
|
'isVerified: $isVerified)';
|
||||||
}
|
}
|
||||||
@@ -14,8 +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/>.
|
||||||
|
|
||||||
abstract class Model<O, T> {
|
export 'builder/email_verification_builder.dart';
|
||||||
String? get id;
|
export 'cubit/email_verification_cubit.dart';
|
||||||
Map<String, Object> toMap();
|
|
||||||
T? from(O? object);
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,21 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
// 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.
|
||||||
//
|
//
|
||||||
// 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 'authentication/authentication.dart';
|
export 'authentication/authentication.dart';
|
||||||
|
export 'email_verification/email_verification.dart';
|
||||||
|
export 'password_reset/password_reset.dart';
|
||||||
export 'sign_in/sign_in.dart';
|
export 'sign_in/sign_in.dart';
|
||||||
export 'sign_up/sign_up.dart';
|
export 'sign_up/sign_up.dart';
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
// 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:async';
|
||||||
|
|
||||||
|
import 'package:wyatt_authentication_bloc/src/core/constants/form_field.dart';
|
||||||
|
import 'package:wyatt_authentication_bloc/src/core/constants/form_name.dart';
|
||||||
|
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
|
||||||
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
||||||
|
|
||||||
|
part 'password_reset_state.dart';
|
||||||
|
|
||||||
|
class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> {
|
||||||
|
final AuthenticationRepository<Extra> _authenticationRepository;
|
||||||
|
FormRepository get _formRepository =>
|
||||||
|
_authenticationRepository.formRepository;
|
||||||
|
|
||||||
|
PasswordResetCubit({
|
||||||
|
required AuthenticationRepository<Extra> authenticationRepository,
|
||||||
|
}) : _authenticationRepository = authenticationRepository,
|
||||||
|
super(
|
||||||
|
PasswordResetState(
|
||||||
|
form: authenticationRepository.formRepository
|
||||||
|
.accessForm(AuthFormName.passwordResetForm),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get formName => AuthFormName.passwordResetForm;
|
||||||
|
|
||||||
|
void emailChanged(String value) {
|
||||||
|
final Email email = Email.dirty(value);
|
||||||
|
dataChanged(AuthFormField.email, email);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> dataChanged<Value>(
|
||||||
|
String key,
|
||||||
|
FormInputValidator<Value, ValidationError> dirtyValue,
|
||||||
|
) {
|
||||||
|
final form = _formRepository.accessForm(formName).clone();
|
||||||
|
|
||||||
|
try {
|
||||||
|
form.updateValidator(key, dirtyValue);
|
||||||
|
_formRepository.updateForm(form);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(
|
||||||
|
state.copyWith(form: form, status: form.validate()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> reset() {
|
||||||
|
final form = state.form.reset();
|
||||||
|
_formRepository.updateForm(form);
|
||||||
|
emit(
|
||||||
|
state.copyWith(form: form, status: form.validate()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> submit() async {
|
||||||
|
if (!state.status.isValidated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(state.copyWith(status: FormStatus.submissionInProgress));
|
||||||
|
|
||||||
|
final form = _formRepository.accessForm(formName);
|
||||||
|
final email = form.valueOf<String?>(AuthFormField.email);
|
||||||
|
|
||||||
|
if (email.isNullOrEmpty) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
errorMessage: 'An error occured while retrieving data from the form.',
|
||||||
|
status: FormStatus.submissionFailure,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await _authenticationRepository.sendPasswordResetEmail(
|
||||||
|
email: email!,
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(
|
||||||
|
response.fold(
|
||||||
|
(value) => state.copyWith(status: FormStatus.submissionSuccess),
|
||||||
|
(error) => state.copyWith(
|
||||||
|
errorMessage: error.message,
|
||||||
|
status: FormStatus.submissionFailure,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> update(
|
||||||
|
WyattForm form, {
|
||||||
|
SetOperation operation = SetOperation.replace,
|
||||||
|
}) {
|
||||||
|
final WyattForm current = _formRepository.accessForm(formName).clone();
|
||||||
|
final WyattForm newForm = operation.operation.call(current, form);
|
||||||
|
_formRepository.updateForm(newForm);
|
||||||
|
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
form: newForm,
|
||||||
|
status: newForm.validate(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> validate() {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: _formRepository.accessForm(formName).validate(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
part of 'password_reset_cubit.dart';
|
||||||
|
|
||||||
|
class PasswordResetState extends FormDataState {
|
||||||
|
Email get email => form.validatorOf(AuthFormField.email);
|
||||||
|
|
||||||
|
const PasswordResetState({
|
||||||
|
required super.form,
|
||||||
|
super.status = FormStatus.pure,
|
||||||
|
super.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
PasswordResetState copyWith({
|
||||||
|
WyattForm? form,
|
||||||
|
FormStatus? status,
|
||||||
|
String? errorMessage,
|
||||||
|
}) =>
|
||||||
|
PasswordResetState(
|
||||||
|
form: form ?? this.form,
|
||||||
|
status: status ?? this.status,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [email, status];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'PasswordResetState(status: ${status.name} '
|
||||||
|
'${(errorMessage != null) ? " [$errorMessage]" : ""}, $form)';
|
||||||
|
}
|
||||||
@@ -14,5 +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/>.
|
||||||
|
|
||||||
export 'queries_firestore.dart';
|
export 'cubit/password_reset_cubit.dart';
|
||||||
export 'queries_interface.dart';
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// 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/src/features/sign_in/sign_in.dart';
|
||||||
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
|
class SignInListener<Extra> extends StatelessWidget {
|
||||||
|
const SignInListener({
|
||||||
|
required this.child,
|
||||||
|
this.onProgress,
|
||||||
|
this.onSuccess,
|
||||||
|
this.onError,
|
||||||
|
this.customBuilder,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final void Function(BuildContext context)? onProgress;
|
||||||
|
final void Function(BuildContext context)? onSuccess;
|
||||||
|
final void Function(
|
||||||
|
BuildContext context,
|
||||||
|
FormStatus status,
|
||||||
|
String? errorMessage,
|
||||||
|
)? onError;
|
||||||
|
final void Function(BuildContext context, SignInState state)? customBuilder;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) =>
|
||||||
|
BlocListener<SignInCubit<Extra>, SignInState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (customBuilder != null) {
|
||||||
|
return customBuilder!(context, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onSuccess != null &&
|
||||||
|
state.status == FormStatus.submissionSuccess) {
|
||||||
|
return onSuccess!(context);
|
||||||
|
}
|
||||||
|
if (onProgress != null &&
|
||||||
|
state.status == FormStatus.submissionInProgress) {
|
||||||
|
return onProgress!(context);
|
||||||
|
}
|
||||||
|
if (onError != null &&
|
||||||
|
(state.status == FormStatus.submissionCanceled ||
|
||||||
|
state.status == FormStatus.submissionFailure)) {
|
||||||
|
return onError!(context, state.status, state.errorMessage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
// 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.
|
||||||
//
|
//
|
||||||
// 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 'cubit/sign_in_cubit.dart';
|
export 'cubit/sign_in_cubit.dart';
|
||||||
|
export 'listener/sign_in_listener.dart';
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// 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/src/features/sign_up/cubit/sign_up_cubit.dart';
|
||||||
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
|
class SignUpListener<Extra> extends StatelessWidget {
|
||||||
|
const SignUpListener({
|
||||||
|
required this.child,
|
||||||
|
this.onProgress,
|
||||||
|
this.onSuccess,
|
||||||
|
this.onError,
|
||||||
|
this.customBuilder,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final void Function(BuildContext context)? onProgress;
|
||||||
|
final void Function(BuildContext context)? onSuccess;
|
||||||
|
final void Function(
|
||||||
|
BuildContext context,
|
||||||
|
FormStatus status,
|
||||||
|
String? errorMessage,
|
||||||
|
)? onError;
|
||||||
|
final void Function(BuildContext context, SignUpState state)? customBuilder;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) =>
|
||||||
|
BlocListener<SignUpCubit<Extra>, SignUpState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (customBuilder != null) {
|
||||||
|
return customBuilder!(context, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onSuccess != null &&
|
||||||
|
state.status == FormStatus.submissionSuccess) {
|
||||||
|
return onSuccess!(context);
|
||||||
|
}
|
||||||
|
if (onProgress != null &&
|
||||||
|
state.status == FormStatus.submissionInProgress) {
|
||||||
|
return onProgress!(context);
|
||||||
|
}
|
||||||
|
if (onError != null &&
|
||||||
|
(state.status == FormStatus.submissionCanceled ||
|
||||||
|
state.status == FormStatus.submissionFailure)) {
|
||||||
|
return onError!(context, state.status, state.errorMessage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
// 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.
|
||||||
//
|
//
|
||||||
// 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 'cubit/sign_up_cubit.dart';
|
export 'cubit/sign_up_cubit.dart';
|
||||||
|
export 'listener/sign_up_listener.dart';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: wyatt_authentication_bloc
|
name: wyatt_authentication_bloc
|
||||||
description: Authentication BLoC for Flutter
|
description: Authentication BLoC for Flutter
|
||||||
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_authentication_bloc
|
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_authentication_bloc
|
||||||
version: 0.2.1+5
|
version: 0.3.0+1
|
||||||
|
|
||||||
publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub
|
publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
crypto: ^3.0.2
|
crypto: ^3.0.2
|
||||||
flutter_bloc: ^8.1.1
|
flutter_bloc: ^8.1.1
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
@@ -23,28 +23,23 @@ dependencies:
|
|||||||
twitter_login: ^4.2.3
|
twitter_login: ^4.2.3
|
||||||
|
|
||||||
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.1.0+1
|
||||||
ref: wyatt_form_bloc-v0.1.0
|
|
||||||
path: packages/wyatt_form_bloc
|
|
||||||
|
|
||||||
wyatt_architecture:
|
wyatt_architecture:
|
||||||
git:
|
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
version: ^0.0.2
|
||||||
ref: wyatt_architecture-v0.0.2-dev.0
|
|
||||||
path: packages/wyatt_architecture
|
|
||||||
|
|
||||||
wyatt_type_utils:
|
wyatt_type_utils:
|
||||||
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
||||||
version: 0.0.3+1
|
version: ^0.0.3+1
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
bloc_test: ^9.1.0
|
bloc_test: ^9.1.0
|
||||||
mocktail: ^0.3.0
|
mocktail: ^0.3.0
|
||||||
|
|
||||||
wyatt_analysis:
|
wyatt_analysis:
|
||||||
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
||||||
version: 2.2.2
|
version: 2.2.2
|
||||||
|
|||||||
@@ -17,100 +17,114 @@
|
|||||||
import 'package:bloc_test/bloc_test.dart';
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:wyatt_architecture/wyatt_architecture.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';
|
||||||
|
|
||||||
class MockAuthenticationRepository extends Mock
|
class MockAuthenticationRepository extends Mock
|
||||||
implements AuthenticationRepository {}
|
implements AuthenticationRepository<int> {}
|
||||||
|
|
||||||
class MockUser extends Mock implements User {}
|
class MockAccount extends Mock implements Account {}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('AuthenticationCubit<T>', () {
|
group('AuthenticationCubit<T>', () {
|
||||||
final MockUser user = MockUser();
|
final MockAccount account = MockAccount();
|
||||||
late AuthenticationRepository authenticationRepository;
|
final AccountWrapper<int> wrapper = AccountWrapperModel(account, 10);
|
||||||
|
late AuthenticationRepository<int> authenticationRepository;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
authenticationRepository = MockAuthenticationRepository();
|
authenticationRepository = MockAuthenticationRepository();
|
||||||
when(() => authenticationRepository.user).thenAnswer(
|
when(() => authenticationRepository.streamAccount()).thenAnswer(
|
||||||
(_) => const Stream.empty(),
|
(_) => const Stream.empty(),
|
||||||
);
|
);
|
||||||
when(() => authenticationRepository.cubitStatus).thenAnswer(
|
|
||||||
(_) => Stream.fromIterable([AuthCubitStatus.stoped]),
|
|
||||||
);
|
|
||||||
when(
|
when(
|
||||||
() => authenticationRepository.currentUser,
|
() => authenticationRepository.getAccount(),
|
||||||
).thenReturn(user);
|
).thenAnswer((_) async => Ok(account));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('initial auth state is `unknown`', () {
|
test('initial auth state is `unknown`', () {
|
||||||
expect(
|
expect(
|
||||||
AuthenticationCubit<void>(
|
AuthenticationCubit<int>(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
).state,
|
).state,
|
||||||
const AuthenticationState<Never>.unknown(),
|
const AuthenticationState<Never>.unknown(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('initial cubit status is `stoped`', () async {
|
group('ListenForAuthenticationChanges', () {
|
||||||
expect(
|
blocTest<AuthenticationCubit<int>, AuthenticationState<int>>(
|
||||||
await AuthenticationCubit<void>(
|
'emits authenticated when stream contains account',
|
||||||
authenticationRepository: authenticationRepository,
|
|
||||||
).status,
|
|
||||||
AuthCubitStatus.stoped,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
group('UserChanged', () {
|
|
||||||
blocTest<AuthenticationCubit<void>, AuthenticationState<void>>(
|
|
||||||
'emits authenticated when user is not empty',
|
|
||||||
setUp: () {
|
setUp: () {
|
||||||
when(() => user.isNotEmpty).thenReturn(true);
|
when(() => authenticationRepository.streamAccount()).thenAnswer(
|
||||||
when(() => authenticationRepository.user).thenAnswer(
|
(_) => Stream.fromIterable([
|
||||||
(_) => Stream.value(user),
|
Future.value(
|
||||||
|
Ok(wrapper),
|
||||||
|
)
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
when(() => authenticationRepository.cubitStatus).thenAnswer(
|
|
||||||
(_) => Stream.value(AuthCubitStatus.started),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
build: () => AuthenticationCubit(
|
|
||||||
authenticationRepository: authenticationRepository,
|
|
||||||
)..init(),
|
|
||||||
seed: () => const AuthenticationState.unknown(),
|
|
||||||
expect: () => [AuthenticationState<void>.authenticated(user, null)],
|
|
||||||
);
|
|
||||||
|
|
||||||
blocTest<AuthenticationCubit<void>, AuthenticationState<void>>(
|
|
||||||
'emits unauthenticated when user is empty',
|
|
||||||
setUp: () {
|
|
||||||
when(() => user.isEmpty).thenReturn(true);
|
|
||||||
when(() => user.isNotEmpty).thenReturn(false);
|
|
||||||
when(() => authenticationRepository.user).thenAnswer(
|
|
||||||
(_) => Stream.value(user),
|
|
||||||
);
|
|
||||||
when(() => authenticationRepository.cubitStatus).thenAnswer(
|
|
||||||
(_) => Stream.value(AuthCubitStatus.started),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
build: () => AuthenticationCubit(
|
|
||||||
authenticationRepository: authenticationRepository,
|
|
||||||
)..init(),
|
|
||||||
seed: () => const AuthenticationState.unknown(),
|
|
||||||
expect: () => [const AuthenticationState<Never>.unauthenticated()],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
group('LogoutRequested', () {
|
|
||||||
blocTest<AuthenticationCubit<void>, AuthenticationState<void>>(
|
|
||||||
'invokes signOut',
|
|
||||||
setUp: () {
|
|
||||||
when(
|
|
||||||
() => authenticationRepository.signOut(),
|
|
||||||
).thenAnswer((_) async {});
|
|
||||||
},
|
},
|
||||||
build: () => AuthenticationCubit(
|
build: () => AuthenticationCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.logOut(),
|
seed: () => const AuthenticationState.unknown(),
|
||||||
|
expect: () => [AuthenticationState<int>.authenticated(wrapper)],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<AuthenticationCubit<int>, AuthenticationState<int>>(
|
||||||
|
'emits unauthenticated when account stream is empty',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => authenticationRepository.destroyCache(),
|
||||||
|
).thenAnswer((_) async => const Ok(null));
|
||||||
|
when(() => authenticationRepository.streamAccount()).thenAnswer(
|
||||||
|
(_) => Stream.fromIterable([
|
||||||
|
Future.value(
|
||||||
|
Ok(AccountWrapperModel(null, 1)),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
build: () => AuthenticationCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
seed: () => const AuthenticationState.unknown(),
|
||||||
|
expect: () => [const AuthenticationState<int>.unauthenticated()],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<AuthenticationCubit<int>, AuthenticationState<int>>(
|
||||||
|
'emits unauthenticated when there is an error in stream',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => authenticationRepository.destroyCache(),
|
||||||
|
).thenAnswer((_) async => const Ok(null));
|
||||||
|
when(() => authenticationRepository.streamAccount()).thenAnswer(
|
||||||
|
(_) => Stream.fromIterable([
|
||||||
|
Future.value(
|
||||||
|
Err(ServerException()),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
build: () => AuthenticationCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
seed: () => const AuthenticationState.unknown(),
|
||||||
|
expect: () => [const AuthenticationState<int>.unauthenticated()],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('SignOut', () {
|
||||||
|
blocTest<AuthenticationCubit<int>, AuthenticationState<int>>(
|
||||||
|
'invokes signOut',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => authenticationRepository.signOut(),
|
||||||
|
).thenAnswer((_) async => const Ok(null));
|
||||||
|
},
|
||||||
|
build: () => AuthenticationCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
act: (cubit) => cubit.signOut(),
|
||||||
verify: (_) {
|
verify: (_) {
|
||||||
verify(() => authenticationRepository.signOut()).called(1);
|
verify(() => authenticationRepository.signOut()).called(1);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
|
|
||||||
class MockUser extends Mock implements User {}
|
class MockAccount extends Mock implements Account {}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('AuthenticationState', () {
|
group('AuthenticationState', () {
|
||||||
@@ -27,29 +27,33 @@ void main() {
|
|||||||
const AuthenticationState<void> state =
|
const AuthenticationState<void> state =
|
||||||
AuthenticationState.unauthenticated();
|
AuthenticationState.unauthenticated();
|
||||||
expect(state.status, AuthenticationStatus.unauthenticated);
|
expect(state.status, AuthenticationStatus.unauthenticated);
|
||||||
expect(state.user, null);
|
expect(state.accountWrapper, null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('authenticated', () {
|
group('authenticated', () {
|
||||||
test('has correct status', () {
|
test('has correct status', () {
|
||||||
final MockUser user = MockUser();
|
final MockAccount account = MockAccount();
|
||||||
final AuthenticationState<void> state =
|
final AuthenticationState<void> state =
|
||||||
AuthenticationState.authenticated(user, null);
|
AuthenticationState.authenticated(
|
||||||
|
AccountWrapperModel<void>(account, null),
|
||||||
|
);
|
||||||
expect(state.status, AuthenticationStatus.authenticated);
|
expect(state.status, AuthenticationStatus.authenticated);
|
||||||
expect(state.user, user);
|
expect(state.accountWrapper?.account, account);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('authenticated with extra data', () {
|
group('authenticated with extra data', () {
|
||||||
test('has correct status', () {
|
test('has correct status', () {
|
||||||
final MockUser user = MockUser();
|
final MockAccount account = MockAccount();
|
||||||
const String extra = 'AwesomeExtraData';
|
const String extra = 'AwesomeExtraData';
|
||||||
final AuthenticationState<String> state =
|
final AuthenticationState<String> state =
|
||||||
AuthenticationState.authenticated(user, extra);
|
AuthenticationState.authenticated(
|
||||||
|
AccountWrapperModel(account, extra),
|
||||||
|
);
|
||||||
expect(state.status, AuthenticationStatus.authenticated);
|
expect(state.status, AuthenticationStatus.authenticated);
|
||||||
expect(state.user, user);
|
expect(state.accountWrapper?.account, account);
|
||||||
expect(state.extra, extra);
|
expect(state.accountWrapper?.data, extra);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
// 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:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:wyatt_architecture/wyatt_architecture.dart';
|
||||||
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
||||||
|
|
||||||
|
class MockAuthenticationRepository extends Mock
|
||||||
|
implements AuthenticationRepository<int> {}
|
||||||
|
|
||||||
|
class MockAuthenticationCubit extends Mock implements AuthenticationCubit<int> {
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockAccount extends Mock implements Account {}
|
||||||
|
|
||||||
|
class MockFormRepository extends Mock implements FormRepository {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('EmailVerificationCubit<T>', () {
|
||||||
|
late MockAccount account;
|
||||||
|
late AuthenticationRepository<int> authenticationRepository;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
authenticationRepository = MockAuthenticationRepository();
|
||||||
|
when(
|
||||||
|
() => authenticationRepository.getAccount(),
|
||||||
|
).thenAnswer((_) async => Ok(account));
|
||||||
|
|
||||||
|
when(
|
||||||
|
() => authenticationRepository.refresh(),
|
||||||
|
).thenAnswer((_) async => const Ok(null));
|
||||||
|
|
||||||
|
account = MockAccount();
|
||||||
|
when(
|
||||||
|
() => account.emailVerified,
|
||||||
|
).thenAnswer((_) => true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initial state is `false`', () {
|
||||||
|
expect(
|
||||||
|
EmailVerificationCubit<int>(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
).state,
|
||||||
|
const EmailVerificationState(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('SendVerificationEmail', () {
|
||||||
|
blocTest<EmailVerificationCubit<int>, EmailVerificationState>(
|
||||||
|
'invokes sendEmailVerification,',
|
||||||
|
setUp: () {
|
||||||
|
when(() => authenticationRepository.sendEmailVerification())
|
||||||
|
.thenAnswer((_) async => const Ok(null));
|
||||||
|
},
|
||||||
|
build: () => EmailVerificationCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
act: (cubit) => cubit.sendEmailVerification(),
|
||||||
|
verify: (_) {
|
||||||
|
verify(() => authenticationRepository.sendEmailVerification())
|
||||||
|
.called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<EmailVerificationCubit<int>, EmailVerificationState>(
|
||||||
|
'emits success',
|
||||||
|
setUp: () {
|
||||||
|
when(() => authenticationRepository.sendEmailVerification())
|
||||||
|
.thenAnswer((_) async => const Ok(null));
|
||||||
|
},
|
||||||
|
build: () => EmailVerificationCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
seed: () => const EmailVerificationState(),
|
||||||
|
act: (cubit) => cubit.sendEmailVerification(),
|
||||||
|
expect: () => [
|
||||||
|
const EmailVerificationState(
|
||||||
|
status: FormStatus.submissionInProgress,
|
||||||
|
),
|
||||||
|
const EmailVerificationState(
|
||||||
|
status: FormStatus.submissionSuccess,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<EmailVerificationCubit<int>, EmailVerificationState>(
|
||||||
|
'emits failure',
|
||||||
|
setUp: () {
|
||||||
|
when(() => authenticationRepository.sendEmailVerification())
|
||||||
|
.thenAnswer((_) async => Err(ServerException('erreur')));
|
||||||
|
},
|
||||||
|
build: () => EmailVerificationCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
seed: () => const EmailVerificationState(),
|
||||||
|
act: (cubit) => cubit.sendEmailVerification(),
|
||||||
|
expect: () => [
|
||||||
|
const EmailVerificationState(
|
||||||
|
status: FormStatus.submissionInProgress,
|
||||||
|
),
|
||||||
|
const EmailVerificationState(
|
||||||
|
errorMessage: 'erreur',
|
||||||
|
status: FormStatus.submissionFailure,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('CheckEmailVerification', () {
|
||||||
|
blocTest<EmailVerificationCubit<int>, EmailVerificationState>(
|
||||||
|
'invokes refresh,',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => authenticationRepository.refresh(),
|
||||||
|
).thenAnswer((_) async => const Ok(null));
|
||||||
|
},
|
||||||
|
build: () => EmailVerificationCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
act: (cubit) => cubit.checkEmailVerification(),
|
||||||
|
verify: (_) {
|
||||||
|
verify(() => authenticationRepository.refresh()).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<EmailVerificationCubit<int>, EmailVerificationState>(
|
||||||
|
'invokes emailVerified,',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => authenticationRepository.refresh(),
|
||||||
|
).thenAnswer((_) async => const Ok(null));
|
||||||
|
when(() => account.emailVerified).thenAnswer((_) => false);
|
||||||
|
},
|
||||||
|
build: () => EmailVerificationCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
act: (cubit) => cubit.checkEmailVerification(),
|
||||||
|
verify: (_) {
|
||||||
|
verify(() => account.emailVerified).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<EmailVerificationCubit<int>, EmailVerificationState>(
|
||||||
|
'emits success with true if verified',
|
||||||
|
setUp: () {
|
||||||
|
when(() => authenticationRepository.refresh())
|
||||||
|
.thenAnswer((_) async => const Ok(null));
|
||||||
|
},
|
||||||
|
build: () => EmailVerificationCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
seed: () => const EmailVerificationState(),
|
||||||
|
act: (cubit) => cubit.checkEmailVerification(),
|
||||||
|
expect: () => [
|
||||||
|
const EmailVerificationState(
|
||||||
|
status: FormStatus.submissionInProgress,
|
||||||
|
),
|
||||||
|
const EmailVerificationState(
|
||||||
|
isVerified: true,
|
||||||
|
status: FormStatus.submissionSuccess,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<EmailVerificationCubit<int>, EmailVerificationState>(
|
||||||
|
'emits success with false if not verified',
|
||||||
|
setUp: () {
|
||||||
|
when(() => authenticationRepository.refresh())
|
||||||
|
.thenAnswer((_) async => const Ok(null));
|
||||||
|
when(() => account.emailVerified).thenAnswer((_) => false);
|
||||||
|
},
|
||||||
|
build: () => EmailVerificationCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
seed: () => const EmailVerificationState(),
|
||||||
|
act: (cubit) => cubit.checkEmailVerification(),
|
||||||
|
expect: () => [
|
||||||
|
const EmailVerificationState(
|
||||||
|
status: FormStatus.submissionInProgress,
|
||||||
|
),
|
||||||
|
const EmailVerificationState(
|
||||||
|
status: FormStatus.submissionSuccess,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<EmailVerificationCubit<int>, EmailVerificationState>(
|
||||||
|
'emits failure on refresh error',
|
||||||
|
setUp: () {
|
||||||
|
when(() => authenticationRepository.refresh())
|
||||||
|
.thenAnswer((_) async => Err(ServerException('erreur')));
|
||||||
|
},
|
||||||
|
build: () => EmailVerificationCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
seed: () => const EmailVerificationState(),
|
||||||
|
act: (cubit) => cubit.checkEmailVerification(),
|
||||||
|
expect: () => [
|
||||||
|
const EmailVerificationState(
|
||||||
|
status: FormStatus.submissionInProgress,
|
||||||
|
),
|
||||||
|
const EmailVerificationState(
|
||||||
|
errorMessage: 'erreur',
|
||||||
|
status: FormStatus.submissionFailure,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<EmailVerificationCubit<int>, EmailVerificationState>(
|
||||||
|
'emits failure on get account error',
|
||||||
|
setUp: () {
|
||||||
|
when(() => authenticationRepository.getAccount())
|
||||||
|
.thenAnswer((_) async => Err(ServerException('erreur')));
|
||||||
|
},
|
||||||
|
build: () => EmailVerificationCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
seed: () => const EmailVerificationState(),
|
||||||
|
act: (cubit) => cubit.checkEmailVerification(),
|
||||||
|
expect: () => [
|
||||||
|
const EmailVerificationState(
|
||||||
|
status: FormStatus.submissionInProgress,
|
||||||
|
),
|
||||||
|
const EmailVerificationState(
|
||||||
|
errorMessage: 'erreur',
|
||||||
|
status: FormStatus.submissionFailure,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// 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_test/flutter_test.dart';
|
||||||
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('EmailVerificationState', () {
|
||||||
|
test('supports value comparisons', () {
|
||||||
|
expect(
|
||||||
|
const EmailVerificationState(isVerified: true),
|
||||||
|
const EmailVerificationState(isVerified: true),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns same object when no properties are passed', () {
|
||||||
|
expect(
|
||||||
|
const EmailVerificationState(isVerified: true).copyWith(),
|
||||||
|
const EmailVerificationState(isVerified: true),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns object with updated status when status is passed', () {
|
||||||
|
expect(
|
||||||
|
const EmailVerificationState(isVerified: true)
|
||||||
|
.copyWith(status: FormStatus.invalid),
|
||||||
|
const EmailVerificationState(
|
||||||
|
isVerified: true,
|
||||||
|
status: FormStatus.invalid,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
// 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:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:wyatt_architecture/wyatt_architecture.dart';
|
||||||
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
||||||
|
|
||||||
|
class MockAuthenticationRepository extends Mock
|
||||||
|
implements AuthenticationRepository<int> {}
|
||||||
|
|
||||||
|
class MockAuthenticationCubit extends Mock implements AuthenticationCubit<int> {
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockAccount extends Mock implements Account {}
|
||||||
|
|
||||||
|
class MockFormRepository extends Mock implements FormRepository {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
const String invalidEmailString = 'invalid';
|
||||||
|
|
||||||
|
const String validEmailString = 'test@gmail.com';
|
||||||
|
|
||||||
|
group('PasswordResetCubit', () {
|
||||||
|
final WyattForm form = WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(AuthFormField.email, const Email.pure()),
|
||||||
|
FormInput(AuthFormField.password, const Password.pure())
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
);
|
||||||
|
|
||||||
|
late MockFormRepository formRepository;
|
||||||
|
late AuthenticationRepository<int> authenticationRepository;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
authenticationRepository = MockAuthenticationRepository();
|
||||||
|
formRepository = MockFormRepository();
|
||||||
|
|
||||||
|
when(
|
||||||
|
() => authenticationRepository.sendPasswordResetEmail(
|
||||||
|
email: any(named: 'email'),
|
||||||
|
),
|
||||||
|
).thenAnswer((_) async => const Ok(null));
|
||||||
|
|
||||||
|
when(
|
||||||
|
() => authenticationRepository.formRepository,
|
||||||
|
).thenAnswer((_) => formRepository);
|
||||||
|
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.passwordResetForm),
|
||||||
|
).thenAnswer((_) => form);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initial state is pure', () {
|
||||||
|
expect(
|
||||||
|
PasswordResetCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
).state,
|
||||||
|
PasswordResetState(form: form),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('emailChanged', () {
|
||||||
|
blocTest<PasswordResetCubit<int>, PasswordResetState>(
|
||||||
|
'emits [invalid] when email is invalid',
|
||||||
|
build: () => PasswordResetCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
act: (cubit) => cubit.emailChanged(invalidEmailString),
|
||||||
|
expect: () => <PasswordResetState>[
|
||||||
|
PasswordResetState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(invalidEmailString),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.invalid,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<PasswordResetCubit<int>, PasswordResetState>(
|
||||||
|
'emits [valid] when email is valid',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.passwordResetForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.pure(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
build: () => PasswordResetCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
seed: () => PasswordResetState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.pure(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.invalid,
|
||||||
|
),
|
||||||
|
act: (cubit) => cubit.emailChanged(validEmailString),
|
||||||
|
expect: () => <PasswordResetState>[
|
||||||
|
PasswordResetState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.valid,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('submit', () {
|
||||||
|
blocTest<PasswordResetCubit<int>, PasswordResetState>(
|
||||||
|
'does nothing when status is not validated',
|
||||||
|
build: () => PasswordResetCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
act: (cubit) => cubit.submit(),
|
||||||
|
expect: () => const <PasswordResetState>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<PasswordResetCubit<int>, PasswordResetState>(
|
||||||
|
'calls sendPasswordResetEmail with correct email',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.passwordResetForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
build: () => PasswordResetCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
seed: () => PasswordResetState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.valid,
|
||||||
|
),
|
||||||
|
act: (cubit) => cubit.submit(),
|
||||||
|
verify: (_) {
|
||||||
|
verify(
|
||||||
|
() => authenticationRepository.sendPasswordResetEmail(
|
||||||
|
email: validEmailString,
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<PasswordResetCubit<int>, PasswordResetState>(
|
||||||
|
'emits [submissionInProgress, submissionSuccess] '
|
||||||
|
'when sendPasswordResetEmail succeeds',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.passwordResetForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
build: () => PasswordResetCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
seed: () => PasswordResetState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.valid,
|
||||||
|
),
|
||||||
|
act: (cubit) => cubit.submit(),
|
||||||
|
expect: () => <PasswordResetState>[
|
||||||
|
PasswordResetState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.submissionInProgress,
|
||||||
|
),
|
||||||
|
PasswordResetState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.submissionSuccess,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<PasswordResetCubit<int>, PasswordResetState>(
|
||||||
|
'emits [submissionInProgress, submissionFailure] '
|
||||||
|
'when sendPasswordResetEmail fails',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => authenticationRepository.sendPasswordResetEmail(
|
||||||
|
email: any(named: 'email'),
|
||||||
|
),
|
||||||
|
).thenAnswer((_) async => Err(ServerException()));
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.passwordResetForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
build: () => PasswordResetCubit(
|
||||||
|
authenticationRepository: authenticationRepository,
|
||||||
|
),
|
||||||
|
seed: () => PasswordResetState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.valid,
|
||||||
|
),
|
||||||
|
act: (cubit) => cubit.submit(),
|
||||||
|
expect: () => <PasswordResetState>[
|
||||||
|
PasswordResetState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.submissionInProgress,
|
||||||
|
),
|
||||||
|
PasswordResetState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.submissionFailure,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// 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_test/flutter_test.dart';
|
||||||
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final WyattForm form = WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(AuthFormField.email, const Email.pure()),
|
||||||
|
],
|
||||||
|
name: AuthFormName.passwordResetForm,
|
||||||
|
);
|
||||||
|
|
||||||
|
group('PasswordResetState', () {
|
||||||
|
test('supports value comparisons', () {
|
||||||
|
expect(
|
||||||
|
PasswordResetState(
|
||||||
|
form: form,
|
||||||
|
),
|
||||||
|
PasswordResetState(form: form),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns same object when no properties are passed', () {
|
||||||
|
expect(
|
||||||
|
PasswordResetState(form: form).copyWith(),
|
||||||
|
PasswordResetState(form: form),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns object with updated status when status is passed', () {
|
||||||
|
expect(
|
||||||
|
PasswordResetState(form: form).copyWith(status: FormStatus.invalid),
|
||||||
|
PasswordResetState(
|
||||||
|
form: form,
|
||||||
|
status: FormStatus.invalid,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -17,46 +17,61 @@
|
|||||||
import 'package:bloc_test/bloc_test.dart';
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:wyatt_architecture/wyatt_architecture.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';
|
||||||
|
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
||||||
|
|
||||||
class MockAuthenticationRepository extends Mock
|
class MockAuthenticationRepository extends Mock
|
||||||
implements AuthenticationRepository {}
|
implements AuthenticationRepository<int> {}
|
||||||
|
|
||||||
class MockAuthenticationCubit extends Mock
|
class MockAuthenticationCubit extends Mock implements AuthenticationCubit<int> {
|
||||||
implements AuthenticationCubit<void> {}
|
}
|
||||||
|
|
||||||
|
class MockAccount extends Mock implements Account {}
|
||||||
|
|
||||||
|
class MockFormRepository extends Mock implements FormRepository {}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
const String invalidEmailString = 'invalid';
|
const String invalidEmailString = 'invalid';
|
||||||
const Email invalidEmail = Email.dirty(invalidEmailString);
|
|
||||||
|
|
||||||
const String validEmailString = 'test@gmail.com';
|
const String validEmailString = 'test@gmail.com';
|
||||||
const Email validEmail = Email.dirty(validEmailString);
|
|
||||||
|
|
||||||
const String invalidPasswordString = 'invalid';
|
const String invalidPasswordString = 'invalid';
|
||||||
const Password invalidPassword = Password.dirty(invalidPasswordString);
|
|
||||||
|
|
||||||
const String validPasswordString = 't0pS3cret1234';
|
const String validPasswordString = 't0pS3cret1234';
|
||||||
const Password validPassword = Password.dirty(validPasswordString);
|
|
||||||
|
|
||||||
group('SignInCubit', () {
|
group('SignInCubit', () {
|
||||||
late AuthenticationRepository authenticationRepository;
|
final MockAccount account = MockAccount();
|
||||||
late AuthenticationCubit<void> authenticationCubit;
|
final WyattForm form = WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(AuthFormField.email, const Email.pure()),
|
||||||
|
FormInput(AuthFormField.password, const Password.pure())
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
);
|
||||||
|
|
||||||
|
late MockFormRepository formRepository;
|
||||||
|
late AuthenticationRepository<int> authenticationRepository;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
authenticationRepository = MockAuthenticationRepository();
|
authenticationRepository = MockAuthenticationRepository();
|
||||||
authenticationCubit = MockAuthenticationCubit();
|
formRepository = MockFormRepository();
|
||||||
|
|
||||||
when(
|
when(
|
||||||
() => authenticationRepository.signInWithEmailAndPassword(
|
() => authenticationRepository.signInWithEmailAndPassword(
|
||||||
email: any(named: 'email'),
|
email: any(named: 'email'),
|
||||||
password: any(named: 'password'),
|
password: any(named: 'password'),
|
||||||
),
|
),
|
||||||
).thenAnswer((_) async {});
|
).thenAnswer((_) async => Ok(account));
|
||||||
|
|
||||||
when(
|
when(
|
||||||
() => authenticationCubit.start(),
|
() => authenticationRepository.formRepository,
|
||||||
).thenReturn(true);
|
).thenAnswer((_) => formRepository);
|
||||||
|
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.signInForm),
|
||||||
|
).thenAnswer((_) => form);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('initial state is SignInState', () {
|
test('initial state is SignInState', () {
|
||||||
@@ -64,33 +79,90 @@ void main() {
|
|||||||
SignInCubit(
|
SignInCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
).state,
|
).state,
|
||||||
const SignInState(),
|
SignInState(form: form),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('emailChanged', () {
|
group('emailChanged', () {
|
||||||
blocTest<SignInCubit, SignInState>(
|
blocTest<SignInCubit<int>, SignInState>(
|
||||||
'emits [invalid] when email/password are invalid',
|
'emits [invalid] when email/password are invalid',
|
||||||
build: () => SignInCubit(
|
build: () => SignInCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.emailChanged(invalidEmailString),
|
act: (cubit) => cubit.emailChanged(invalidEmailString),
|
||||||
expect: () => const <SignInState>[
|
expect: () => <SignInState>[
|
||||||
SignInState(email: invalidEmail, status: FormStatus.invalid),
|
SignInState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(invalidEmailString),
|
||||||
|
),
|
||||||
|
FormInput(AuthFormField.password, const Password.pure())
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.invalid,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
blocTest<SignInCubit, SignInState>(
|
blocTest<SignInCubit<int>, SignInState>(
|
||||||
'emits [valid] when email/password are valid',
|
'emits [valid] when email/password are valid',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.signInForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.pure(),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
build: () => SignInCubit(
|
build: () => SignInCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
),
|
),
|
||||||
seed: () => const SignInState(password: validPassword),
|
seed: () => SignInState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.pure(),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.invalid,
|
||||||
|
),
|
||||||
act: (cubit) => cubit.emailChanged(validEmailString),
|
act: (cubit) => cubit.emailChanged(validEmailString),
|
||||||
expect: () => const <SignInState>[
|
expect: () => <SignInState>[
|
||||||
SignInState(
|
SignInState(
|
||||||
email: validEmail,
|
form: WyattFormImpl(
|
||||||
password: validPassword,
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -98,58 +170,145 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('passwordChanged', () {
|
group('passwordChanged', () {
|
||||||
blocTest<SignInCubit, SignInState>(
|
blocTest<SignInCubit<int>, SignInState>(
|
||||||
'emits [invalid] when email/password are invalid',
|
'emits [invalid] when email/password are invalid',
|
||||||
build: () => SignInCubit(
|
build: () => SignInCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.passwordChanged(invalidPasswordString),
|
act: (cubit) => cubit.passwordChanged(invalidPasswordString),
|
||||||
expect: () => const <SignInState>[
|
expect: () => <SignInState>[
|
||||||
SignInState(
|
SignInState(
|
||||||
password: invalidPassword,
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.pure(),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(invalidPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
status: FormStatus.invalid,
|
status: FormStatus.invalid,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
blocTest<SignInCubit, SignInState>(
|
blocTest<SignInCubit<int>, SignInState>(
|
||||||
'emits [valid] when email/password are valid',
|
'emits [valid] when email/password are valid',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.signInForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.pure(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
build: () => SignInCubit(
|
build: () => SignInCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
),
|
),
|
||||||
seed: () => const SignInState(email: validEmail),
|
seed: () => SignInState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.pure(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.invalid,
|
||||||
|
),
|
||||||
act: (cubit) => cubit.passwordChanged(validPasswordString),
|
act: (cubit) => cubit.passwordChanged(validPasswordString),
|
||||||
expect: () => const <SignInState>[
|
expect: () => <SignInState>[
|
||||||
SignInState(
|
SignInState(
|
||||||
email: validEmail,
|
form: WyattFormImpl(
|
||||||
password: validPassword,
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('logInWithCredentials', () {
|
group('submit', () {
|
||||||
blocTest<SignInCubit, SignInState>(
|
blocTest<SignInCubit<int>, SignInState>(
|
||||||
'does nothing when status is not validated',
|
'does nothing when status is not validated',
|
||||||
build: () => SignInCubit(
|
build: () => SignInCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.signInWithEmailAndPassword(),
|
act: (cubit) => cubit.submit(),
|
||||||
expect: () => const <SignInState>[],
|
expect: () => const <SignInState>[],
|
||||||
);
|
);
|
||||||
|
|
||||||
blocTest<SignInCubit, SignInState>(
|
blocTest<SignInCubit<int>, SignInState>(
|
||||||
'calls signInWithEmailAndPassword with correct email/password',
|
'calls signInWithEmailAndPassword with correct email/password',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.signInForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
build: () => SignInCubit(
|
build: () => SignInCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
),
|
),
|
||||||
seed: () => const SignInState(
|
seed: () => SignInState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
email: validEmail,
|
|
||||||
password: validPassword,
|
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.signInWithEmailAndPassword(),
|
act: (cubit) => cubit.submit(),
|
||||||
verify: (_) {
|
verify: (_) {
|
||||||
verify(
|
verify(
|
||||||
() => authenticationRepository.signInWithEmailAndPassword(
|
() => authenticationRepository.signInWithEmailAndPassword(
|
||||||
@@ -160,33 +319,85 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
blocTest<SignInCubit, SignInState>(
|
blocTest<SignInCubit<int>, SignInState>(
|
||||||
'emits [submissionInProgress, submissionSuccess] '
|
'emits [submissionInProgress, submissionSuccess] '
|
||||||
'when signInWithEmailAndPassword succeeds',
|
'when signInWithEmailAndPassword succeeds',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.signInForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
build: () => SignInCubit(
|
build: () => SignInCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
),
|
),
|
||||||
seed: () => const SignInState(
|
seed: () => SignInState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
email: validEmail,
|
|
||||||
password: validPassword,
|
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.signInWithEmailAndPassword(),
|
act: (cubit) => cubit.submit(),
|
||||||
expect: () => const <SignInState>[
|
expect: () => <SignInState>[
|
||||||
SignInState(
|
SignInState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
status: FormStatus.submissionInProgress,
|
status: FormStatus.submissionInProgress,
|
||||||
email: validEmail,
|
|
||||||
password: validPassword,
|
|
||||||
),
|
),
|
||||||
SignInState(
|
SignInState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
status: FormStatus.submissionSuccess,
|
status: FormStatus.submissionSuccess,
|
||||||
email: validEmail,
|
|
||||||
password: validPassword,
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
blocTest<SignInCubit, SignInState>(
|
blocTest<SignInCubit<int>, SignInState>(
|
||||||
'emits [submissionInProgress, submissionFailure] '
|
'emits [submissionInProgress, submissionFailure] '
|
||||||
'when signInWithEmailAndPassword fails',
|
'when signInWithEmailAndPassword fails',
|
||||||
setUp: () {
|
setUp: () {
|
||||||
@@ -195,27 +406,77 @@ void main() {
|
|||||||
email: any(named: 'email'),
|
email: any(named: 'email'),
|
||||||
password: any(named: 'password'),
|
password: any(named: 'password'),
|
||||||
),
|
),
|
||||||
).thenThrow(Exception('oops'));
|
).thenAnswer((_) async => Err(ServerException()));
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.signInForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
build: () => SignInCubit(
|
build: () => SignInCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
),
|
),
|
||||||
seed: () => const SignInState(
|
seed: () => SignInState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
email: validEmail,
|
|
||||||
password: validPassword,
|
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.signInWithEmailAndPassword(),
|
act: (cubit) => cubit.submit(),
|
||||||
expect: () => const <SignInState>[
|
expect: () => <SignInState>[
|
||||||
SignInState(
|
SignInState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
status: FormStatus.submissionInProgress,
|
status: FormStatus.submissionInProgress,
|
||||||
email: validEmail,
|
|
||||||
password: validPassword,
|
|
||||||
),
|
),
|
||||||
SignInState(
|
SignInState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
),
|
||||||
status: FormStatus.submissionFailure,
|
status: FormStatus.submissionFailure,
|
||||||
email: validEmail,
|
|
||||||
password: validPassword,
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,36 +19,38 @@ 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';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
const Email email = Email.dirty('email');
|
final WyattForm form = WyattFormImpl(
|
||||||
const Password password = Password.dirty('password');
|
[
|
||||||
|
FormInput(AuthFormField.email, const Email.pure()),
|
||||||
|
FormInput(AuthFormField.password, const Password.pure())
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
);
|
||||||
|
|
||||||
group('SignInState', () {
|
group('SignInState', () {
|
||||||
test('supports value comparisons', () {
|
test('supports value comparisons', () {
|
||||||
expect(const SignInState(), const SignInState());
|
expect(
|
||||||
|
SignInState(
|
||||||
|
form: form,
|
||||||
|
),
|
||||||
|
SignInState(form: form),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns same object when no properties are passed', () {
|
test('returns same object when no properties are passed', () {
|
||||||
expect(const SignInState().copyWith(), const SignInState());
|
expect(
|
||||||
|
SignInState(form: form).copyWith(),
|
||||||
|
SignInState(form: form),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns object with updated status when status is passed', () {
|
test('returns object with updated status when status is passed', () {
|
||||||
expect(
|
expect(
|
||||||
const SignInState().copyWith(status: FormStatus.pure),
|
SignInState(form: form).copyWith(status: FormStatus.invalid),
|
||||||
const SignInState(),
|
SignInState(
|
||||||
);
|
form: form,
|
||||||
});
|
status: FormStatus.invalid,
|
||||||
|
),
|
||||||
test('returns object with updated email when email is passed', () {
|
|
||||||
expect(
|
|
||||||
const SignInState().copyWith(email: email),
|
|
||||||
const SignInState(email: email),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns object with updated password when password is passed', () {
|
|
||||||
expect(
|
|
||||||
const SignInState().copyWith(password: password),
|
|
||||||
const SignInState(password: password),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,163 +17,298 @@
|
|||||||
import 'package:bloc_test/bloc_test.dart';
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:wyatt_architecture/wyatt_architecture.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';
|
||||||
|
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
||||||
|
|
||||||
class MockAuthenticationRepository extends Mock
|
class MockAuthenticationRepository extends Mock
|
||||||
implements AuthenticationRepository {}
|
implements AuthenticationRepository<int> {}
|
||||||
|
|
||||||
class MockAuthenticationCubit extends Mock
|
class MockAuthenticationCubit extends Mock implements AuthenticationCubit<int> {
|
||||||
implements AuthenticationCubit<void> {}
|
}
|
||||||
|
|
||||||
|
class MockAccount extends Mock implements Account {}
|
||||||
|
|
||||||
|
class MockFormRepository extends Mock implements FormRepository {}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
const String invalidEmailString = 'invalid';
|
const String invalidEmailString = 'invalid';
|
||||||
const Email invalidEmail = Email.dirty(invalidEmailString);
|
|
||||||
|
|
||||||
const String validEmailString = 'test@gmail.com';
|
const String validEmailString = 'test@gmail.com';
|
||||||
const Email validEmail = Email.dirty(validEmailString);
|
|
||||||
|
|
||||||
const String invalidPasswordString = 'invalid';
|
const String invalidPasswordString = 'invalid';
|
||||||
const Password invalidPassword = Password.dirty(invalidPasswordString);
|
|
||||||
|
|
||||||
const String validPasswordString = 't0pS3cret1234';
|
const String validPasswordString = 't0pS3cret1234';
|
||||||
const Password validPassword = Password.dirty(validPasswordString);
|
|
||||||
|
|
||||||
group('SignUpCubit', () {
|
group('SignUpCubit', () {
|
||||||
late AuthenticationRepository authenticationRepository;
|
final MockAccount account = MockAccount();
|
||||||
late AuthenticationCubit<void> authenticationCubit;
|
final WyattForm form = WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(AuthFormField.email, const Email.pure()),
|
||||||
|
FormInput(AuthFormField.password, const Password.pure())
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
);
|
||||||
|
|
||||||
|
late MockFormRepository formRepository;
|
||||||
|
late AuthenticationRepository<int> authenticationRepository;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
authenticationRepository = MockAuthenticationRepository();
|
authenticationRepository = MockAuthenticationRepository();
|
||||||
authenticationCubit = MockAuthenticationCubit();
|
formRepository = MockFormRepository();
|
||||||
|
|
||||||
when(
|
when(
|
||||||
() => authenticationRepository.signUp(
|
() => authenticationRepository.signUp(
|
||||||
email: any(named: 'email'),
|
email: any(named: 'email'),
|
||||||
password: any(named: 'password'),
|
password: any(named: 'password'),
|
||||||
),
|
),
|
||||||
).thenAnswer((_) async => 'uid');
|
).thenAnswer((_) async => Ok(account));
|
||||||
|
|
||||||
when(
|
when(
|
||||||
() => authenticationCubit.start(),
|
() => authenticationRepository.formRepository,
|
||||||
).thenReturn(true);
|
).thenAnswer((_) => formRepository);
|
||||||
|
|
||||||
when(
|
when(
|
||||||
() => authenticationCubit.stop(),
|
() => formRepository.accessForm(AuthFormName.signUpForm),
|
||||||
).thenReturn(true);
|
).thenAnswer((_) => form);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('initial state is SignUpState', () {
|
test('initial state is SignUpState', () {
|
||||||
expect(
|
expect(
|
||||||
SignUpCubit(
|
SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
formData: const FormData.empty(),
|
|
||||||
).state,
|
).state,
|
||||||
const SignUpState(data: FormData.empty()),
|
SignUpState(form: form),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('emailChanged', () {
|
group('emailChanged', () {
|
||||||
blocTest<SignUpCubit, SignUpState>(
|
blocTest<SignUpCubit<int>, SignUpState>(
|
||||||
'emits [invalid] when email/password are invalid',
|
'emits [invalid] when email/password are invalid',
|
||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
formData: const FormData.empty(),
|
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.emailChanged(invalidEmailString),
|
act: (cubit) => cubit.emailChanged(invalidEmailString),
|
||||||
expect: () => <SignUpState>[
|
expect: () => <SignUpState>[
|
||||||
const SignUpState(
|
SignUpState(
|
||||||
email: invalidEmail,
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(invalidEmailString),
|
||||||
|
),
|
||||||
|
FormInput(AuthFormField.password, const Password.pure())
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
status: FormStatus.invalid,
|
status: FormStatus.invalid,
|
||||||
data: FormData.empty(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
blocTest<SignUpCubit, SignUpState>(
|
blocTest<SignUpCubit<int>, SignUpState>(
|
||||||
'emits [valid] when email/password are valid',
|
'emits [valid] when email/password are valid',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.signUpForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.pure(),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
formData: const FormData.empty(),
|
|
||||||
),
|
),
|
||||||
seed: () => const SignUpState(
|
seed: () => SignUpState(
|
||||||
password: validPassword,
|
form: WyattFormImpl(
|
||||||
data: FormData.empty(),
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.pure(),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.invalid,
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.emailChanged(validEmailString),
|
act: (cubit) => cubit.emailChanged(validEmailString),
|
||||||
expect: () => <SignUpState>[
|
expect: () => <SignUpState>[
|
||||||
const SignUpState(
|
SignUpState(
|
||||||
email: validEmail,
|
form: WyattFormImpl(
|
||||||
password: validPassword,
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
data: FormData.empty(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('passwordChanged', () {
|
group('passwordChanged', () {
|
||||||
blocTest<SignUpCubit, SignUpState>(
|
blocTest<SignUpCubit<int>, SignUpState>(
|
||||||
'emits [invalid] when email/password are invalid',
|
'emits [invalid] when email/password are invalid',
|
||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
formData: const FormData.empty(),
|
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.passwordChanged(invalidPasswordString),
|
act: (cubit) => cubit.passwordChanged(invalidPasswordString),
|
||||||
expect: () => <SignUpState>[
|
expect: () => <SignUpState>[
|
||||||
const SignUpState(
|
SignUpState(
|
||||||
password: invalidPassword,
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.pure(),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(invalidPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
status: FormStatus.invalid,
|
status: FormStatus.invalid,
|
||||||
data: FormData.empty(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
blocTest<SignUpCubit, SignUpState>(
|
blocTest<SignUpCubit<int>, SignUpState>(
|
||||||
'emits [valid] when email/password are valid',
|
'emits [valid] when email/password are valid',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.signUpForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.pure(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
formData: const FormData.empty(),
|
|
||||||
),
|
),
|
||||||
seed: () => const SignUpState(
|
seed: () => SignUpState(
|
||||||
email: validEmail,
|
form: WyattFormImpl(
|
||||||
data: FormData.empty(),
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.pure(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.invalid,
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.passwordChanged(validPasswordString),
|
act: (cubit) => cubit.passwordChanged(validPasswordString),
|
||||||
expect: () => <SignUpState>[
|
expect: () => <SignUpState>[
|
||||||
const SignUpState(
|
SignUpState(
|
||||||
email: validEmail,
|
form: WyattFormImpl(
|
||||||
password: validPassword,
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
data: FormData.empty(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('signUpFormSubmitted', () {
|
group('submit', () {
|
||||||
blocTest<SignUpCubit, SignUpState>(
|
blocTest<SignUpCubit<int>, SignUpState>(
|
||||||
'does nothing when status is not validated',
|
'does nothing when status is not validated',
|
||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
formData: const FormData.empty(),
|
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.signUpFormSubmitted(),
|
act: (cubit) => cubit.submit(),
|
||||||
expect: () => const <SignUpState>[],
|
expect: () => const <SignUpState>[],
|
||||||
);
|
);
|
||||||
|
|
||||||
blocTest<SignUpCubit, SignUpState>(
|
blocTest<SignUpCubit<int>, SignUpState>(
|
||||||
'calls signUp with correct email/password/confirmedPassword',
|
'calls signUp with correct email/password',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.signUpForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
formData: const FormData.empty(),
|
|
||||||
),
|
),
|
||||||
seed: () => const SignUpState(
|
seed: () => SignUpState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
email: validEmail,
|
|
||||||
password: validPassword,
|
|
||||||
data: FormData.empty(),
|
|
||||||
),
|
),
|
||||||
act: (cubit) => cubit.signUpFormSubmitted(),
|
act: (cubit) => cubit.submit(),
|
||||||
verify: (_) {
|
verify: (_) {
|
||||||
verify(
|
verify(
|
||||||
() => authenticationRepository.signUp(
|
() => authenticationRepository.signUp(
|
||||||
@@ -184,37 +319,85 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
blocTest<SignUpCubit, SignUpState>(
|
blocTest<SignUpCubit<int>, SignUpState>(
|
||||||
'emits [submissionInProgress, submissionSuccess] '
|
'emits [submissionInProgress, submissionSuccess] '
|
||||||
'when signUp succeeds',
|
'when signUp succeeds',
|
||||||
|
setUp: () {
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.signUpForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
formData: const FormData.empty(),
|
|
||||||
),
|
),
|
||||||
seed: () => const SignUpState(
|
seed: () => SignUpState(
|
||||||
status: FormStatus.valid,
|
form: WyattFormImpl(
|
||||||
email: validEmail,
|
[
|
||||||
password: validPassword,
|
FormInput(
|
||||||
data: FormData.empty(),
|
AuthFormField.email,
|
||||||
),
|
const Email.dirty(validEmailString),
|
||||||
act: (cubit) => cubit.signUpFormSubmitted(),
|
),
|
||||||
expect: () => <SignUpState>[
|
FormInput(
|
||||||
const SignUpState(
|
AuthFormField.password,
|
||||||
status: FormStatus.submissionInProgress,
|
const Password.dirty(validPasswordString),
|
||||||
email: validEmail,
|
)
|
||||||
password: validPassword,
|
],
|
||||||
data: FormData.empty(),
|
name: AuthFormName.signUpForm,
|
||||||
),
|
),
|
||||||
const SignUpState(
|
status: FormStatus.valid,
|
||||||
|
),
|
||||||
|
act: (cubit) => cubit.submit(),
|
||||||
|
expect: () => <SignUpState>[
|
||||||
|
SignUpState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.submissionInProgress,
|
||||||
|
),
|
||||||
|
SignUpState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
status: FormStatus.submissionSuccess,
|
status: FormStatus.submissionSuccess,
|
||||||
email: validEmail,
|
|
||||||
password: validPassword,
|
|
||||||
data: FormData.empty(),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
blocTest<SignUpCubit, SignUpState>(
|
blocTest<SignUpCubit<int>, SignUpState>(
|
||||||
'emits [submissionInProgress, submissionFailure] '
|
'emits [submissionInProgress, submissionFailure] '
|
||||||
'when signUp fails',
|
'when signUp fails',
|
||||||
setUp: () {
|
setUp: () {
|
||||||
@@ -223,31 +406,77 @@ void main() {
|
|||||||
email: any(named: 'email'),
|
email: any(named: 'email'),
|
||||||
password: any(named: 'password'),
|
password: any(named: 'password'),
|
||||||
),
|
),
|
||||||
).thenThrow(Exception('oops'));
|
).thenAnswer((_) async => Err(ServerException()));
|
||||||
|
when(
|
||||||
|
() => formRepository.accessForm(AuthFormName.signUpForm),
|
||||||
|
).thenAnswer(
|
||||||
|
(_) => WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
formData: const FormData.empty(),
|
|
||||||
),
|
),
|
||||||
seed: () => const SignUpState(
|
seed: () => SignUpState(
|
||||||
status: FormStatus.valid,
|
form: WyattFormImpl(
|
||||||
email: validEmail,
|
[
|
||||||
password: validPassword,
|
FormInput(
|
||||||
data: FormData.empty(),
|
AuthFormField.email,
|
||||||
),
|
const Email.dirty(validEmailString),
|
||||||
act: (cubit) => cubit.signUpFormSubmitted(),
|
),
|
||||||
expect: () => <SignUpState>[
|
FormInput(
|
||||||
const SignUpState(
|
AuthFormField.password,
|
||||||
status: FormStatus.submissionInProgress,
|
const Password.dirty(validPasswordString),
|
||||||
email: validEmail,
|
)
|
||||||
password: validPassword,
|
],
|
||||||
data: FormData.empty(),
|
name: AuthFormName.signUpForm,
|
||||||
),
|
),
|
||||||
const SignUpState(
|
status: FormStatus.valid,
|
||||||
|
),
|
||||||
|
act: (cubit) => cubit.submit(),
|
||||||
|
expect: () => <SignUpState>[
|
||||||
|
SignUpState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
|
status: FormStatus.submissionInProgress,
|
||||||
|
),
|
||||||
|
SignUpState(
|
||||||
|
form: WyattFormImpl(
|
||||||
|
[
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.email,
|
||||||
|
const Email.dirty(validEmailString),
|
||||||
|
),
|
||||||
|
FormInput(
|
||||||
|
AuthFormField.password,
|
||||||
|
const Password.dirty(validPasswordString),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
name: AuthFormName.signUpForm,
|
||||||
|
),
|
||||||
status: FormStatus.submissionFailure,
|
status: FormStatus.submissionFailure,
|
||||||
email: validEmail,
|
|
||||||
password: validPassword,
|
|
||||||
data: FormData.empty(),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,93 +19,37 @@ 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';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
const Email email = Email.dirty('email');
|
final WyattForm form = WyattFormImpl(
|
||||||
const String passwordString = 'password';
|
[
|
||||||
const Password password = Password.dirty(passwordString);
|
FormInput(AuthFormField.email, const Email.pure()),
|
||||||
|
FormInput(AuthFormField.password, const Password.pure())
|
||||||
|
],
|
||||||
|
name: AuthFormName.signInForm,
|
||||||
|
);
|
||||||
|
|
||||||
group('SignUpState', () {
|
group('SignUpState', () {
|
||||||
test('supports value comparisons', () {
|
test('supports value comparisons', () {
|
||||||
expect(
|
expect(
|
||||||
const SignUpState(
|
SignUpState(
|
||||||
data: FormData.empty(),
|
form: form,
|
||||||
),
|
|
||||||
const SignUpState(
|
|
||||||
data: FormData.empty(),
|
|
||||||
),
|
),
|
||||||
|
SignUpState(form: form),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns same object when no properties are passed', () {
|
test('returns same object when no properties are passed', () {
|
||||||
expect(
|
expect(
|
||||||
const SignUpState(
|
SignUpState(form: form).copyWith(),
|
||||||
data: FormData.empty(),
|
SignUpState(form: form),
|
||||||
).copyWith(),
|
|
||||||
const SignUpState(
|
|
||||||
data: FormData.empty(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns object with updated status when status is passed', () {
|
test('returns object with updated status when status is passed', () {
|
||||||
expect(
|
expect(
|
||||||
const SignUpState(
|
SignUpState(form: form).copyWith(status: FormStatus.invalid),
|
||||||
data: FormData.empty(),
|
SignUpState(
|
||||||
).copyWith(status: FormStatus.pure),
|
form: form,
|
||||||
const SignUpState(
|
status: FormStatus.invalid,
|
||||||
data: FormData.empty(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns object with updated email when email is passed', () {
|
|
||||||
expect(
|
|
||||||
const SignUpState(
|
|
||||||
data: FormData.empty(),
|
|
||||||
).copyWith(email: email),
|
|
||||||
const SignUpState(
|
|
||||||
email: email,
|
|
||||||
data: FormData.empty(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns object with updated password when password is passed', () {
|
|
||||||
expect(
|
|
||||||
const SignUpState(
|
|
||||||
data: FormData.empty(),
|
|
||||||
).copyWith(password: password),
|
|
||||||
const SignUpState(
|
|
||||||
password: password,
|
|
||||||
data: FormData.empty(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test(
|
|
||||||
'returns object with updated data'
|
|
||||||
' when data is passed', () {
|
|
||||||
expect(
|
|
||||||
const SignUpState(
|
|
||||||
data: FormData.empty(),
|
|
||||||
).copyWith(
|
|
||||||
data: const FormData(
|
|
||||||
[
|
|
||||||
FormInput(
|
|
||||||
'field',
|
|
||||||
Name.pure(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SignUpState(
|
|
||||||
data: FormData(
|
|
||||||
[
|
|
||||||
FormInput(
|
|
||||||
'field',
|
|
||||||
Name.pure(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"psioniq.psi-header",
|
||||||
|
"blaugold.melos-code"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"psi-header.changes-tracking": {
|
||||||
|
"isActive": true
|
||||||
|
},
|
||||||
|
"psi-header.config": {
|
||||||
|
"blankLinesAfter": 1,
|
||||||
|
"forceToTop": true
|
||||||
|
},
|
||||||
|
"psi-header.lang-config": [
|
||||||
|
{
|
||||||
|
"beforeHeader": [
|
||||||
|
"# -*- coding:utf-8 -*-",
|
||||||
|
"#!/usr/bin/env python3"
|
||||||
|
],
|
||||||
|
"begin": "###",
|
||||||
|
"end": "###",
|
||||||
|
"language": "python",
|
||||||
|
"prefix": "# "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"beforeHeader": [
|
||||||
|
"#!/usr/bin/env sh",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"language": "shellscript",
|
||||||
|
"begin": "",
|
||||||
|
"end": "",
|
||||||
|
"prefix": "# "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"begin": "",
|
||||||
|
"end": "",
|
||||||
|
"language": "dart",
|
||||||
|
"prefix": "// "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"begin": "",
|
||||||
|
"end": "",
|
||||||
|
"language": "yaml",
|
||||||
|
"prefix": "# "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"begin": "<!--",
|
||||||
|
"end": "-->",
|
||||||
|
"language": "markdown",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"psi-header.templates": [
|
||||||
|
{
|
||||||
|
"language": "*",
|
||||||
|
"template": [
|
||||||
|
"Copyright (C) <<year>> 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/>."
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dart.runPubGetOnPubspecChanges": false,
|
||||||
|
}
|
||||||
@@ -1,3 +1,11 @@
|
|||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
> Note: This release has breaking changes.
|
||||||
|
|
||||||
|
- **REFACTOR**: add vscode config.
|
||||||
|
- **FIX**: update example.
|
||||||
|
- **BREAKING** **REFACTOR**: migrate to wyatt architecture.
|
||||||
|
|
||||||
## 0.0.2
|
## 0.0.2
|
||||||
|
|
||||||
- Migrate crud_package in monorepo
|
- Migrate crud_package in monorepo
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
include: package:wyatt_analysis/analysis_options.flutter.yaml
|
include: package:wyatt_analysis/analysis_options.flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude: "!example/**"
|
||||||
|
|||||||
@@ -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: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||||
|
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||||
|
- platform: ios
|
||||||
|
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||||
|
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||||
|
|
||||||
|
# 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'
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>11.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Uncomment this line to define a global platform for your project
|
||||||
|
# platform :ios, '11.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,757 @@
|
|||||||
|
PODS:
|
||||||
|
- abseil/algorithm (1.20211102.0):
|
||||||
|
- abseil/algorithm/algorithm (= 1.20211102.0)
|
||||||
|
- abseil/algorithm/container (= 1.20211102.0)
|
||||||
|
- abseil/algorithm/algorithm (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/algorithm/container (1.20211102.0):
|
||||||
|
- abseil/algorithm/algorithm
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/base (1.20211102.0):
|
||||||
|
- abseil/base/atomic_hook (= 1.20211102.0)
|
||||||
|
- abseil/base/base (= 1.20211102.0)
|
||||||
|
- abseil/base/base_internal (= 1.20211102.0)
|
||||||
|
- abseil/base/config (= 1.20211102.0)
|
||||||
|
- abseil/base/core_headers (= 1.20211102.0)
|
||||||
|
- abseil/base/dynamic_annotations (= 1.20211102.0)
|
||||||
|
- abseil/base/endian (= 1.20211102.0)
|
||||||
|
- abseil/base/errno_saver (= 1.20211102.0)
|
||||||
|
- abseil/base/fast_type_id (= 1.20211102.0)
|
||||||
|
- abseil/base/log_severity (= 1.20211102.0)
|
||||||
|
- abseil/base/malloc_internal (= 1.20211102.0)
|
||||||
|
- abseil/base/pretty_function (= 1.20211102.0)
|
||||||
|
- abseil/base/raw_logging_internal (= 1.20211102.0)
|
||||||
|
- abseil/base/spinlock_wait (= 1.20211102.0)
|
||||||
|
- abseil/base/strerror (= 1.20211102.0)
|
||||||
|
- abseil/base/throw_delegate (= 1.20211102.0)
|
||||||
|
- abseil/base/atomic_hook (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/base (1.20211102.0):
|
||||||
|
- abseil/base/atomic_hook
|
||||||
|
- abseil/base/base_internal
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/dynamic_annotations
|
||||||
|
- abseil/base/log_severity
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/base/spinlock_wait
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/base/base_internal (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/base/config (1.20211102.0)
|
||||||
|
- abseil/base/core_headers (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/dynamic_annotations (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/endian (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/errno_saver (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/fast_type_id (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/log_severity (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/malloc_internal (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/base_internal
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/dynamic_annotations
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/base/pretty_function (1.20211102.0)
|
||||||
|
- abseil/base/raw_logging_internal (1.20211102.0):
|
||||||
|
- abseil/base/atomic_hook
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/log_severity
|
||||||
|
- abseil/base/spinlock_wait (1.20211102.0):
|
||||||
|
- abseil/base/base_internal
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/errno_saver
|
||||||
|
- abseil/base/strerror (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/errno_saver
|
||||||
|
- abseil/base/throw_delegate (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/container/common (1.20211102.0):
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/types/optional
|
||||||
|
- abseil/container/compressed_tuple (1.20211102.0):
|
||||||
|
- abseil/utility/utility
|
||||||
|
- abseil/container/container_memory (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/memory/memory
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/utility/utility
|
||||||
|
- abseil/container/fixed_array (1.20211102.0):
|
||||||
|
- abseil/algorithm/algorithm
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/dynamic_annotations
|
||||||
|
- abseil/base/throw_delegate
|
||||||
|
- abseil/container/compressed_tuple
|
||||||
|
- abseil/memory/memory
|
||||||
|
- abseil/container/flat_hash_map (1.20211102.0):
|
||||||
|
- abseil/algorithm/container
|
||||||
|
- abseil/container/container_memory
|
||||||
|
- abseil/container/hash_function_defaults
|
||||||
|
- abseil/container/raw_hash_map
|
||||||
|
- abseil/memory/memory
|
||||||
|
- abseil/container/hash_function_defaults (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/hash/hash
|
||||||
|
- abseil/strings/cord
|
||||||
|
- abseil/strings/strings
|
||||||
|
- abseil/container/hash_policy_traits (1.20211102.0):
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/container/hashtable_debug_hooks (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/container/hashtablez_sampler (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/container/have_sse
|
||||||
|
- abseil/debugging/stacktrace
|
||||||
|
- abseil/memory/memory
|
||||||
|
- abseil/profiling/exponential_biased
|
||||||
|
- abseil/profiling/sample_recorder
|
||||||
|
- abseil/synchronization/synchronization
|
||||||
|
- abseil/utility/utility
|
||||||
|
- abseil/container/have_sse (1.20211102.0)
|
||||||
|
- abseil/container/inlined_vector (1.20211102.0):
|
||||||
|
- abseil/algorithm/algorithm
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/throw_delegate
|
||||||
|
- abseil/container/inlined_vector_internal
|
||||||
|
- abseil/memory/memory
|
||||||
|
- abseil/container/inlined_vector_internal (1.20211102.0):
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/container/compressed_tuple
|
||||||
|
- abseil/memory/memory
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/types/span
|
||||||
|
- abseil/container/layout (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/strings/strings
|
||||||
|
- abseil/types/span
|
||||||
|
- abseil/utility/utility
|
||||||
|
- abseil/container/raw_hash_map (1.20211102.0):
|
||||||
|
- abseil/base/throw_delegate
|
||||||
|
- abseil/container/container_memory
|
||||||
|
- abseil/container/raw_hash_set
|
||||||
|
- abseil/container/raw_hash_set (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/endian
|
||||||
|
- abseil/container/common
|
||||||
|
- abseil/container/compressed_tuple
|
||||||
|
- abseil/container/container_memory
|
||||||
|
- abseil/container/hash_policy_traits
|
||||||
|
- abseil/container/hashtable_debug_hooks
|
||||||
|
- abseil/container/hashtablez_sampler
|
||||||
|
- abseil/container/have_sse
|
||||||
|
- abseil/memory/memory
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/numeric/bits
|
||||||
|
- abseil/utility/utility
|
||||||
|
- abseil/debugging/debugging_internal (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/dynamic_annotations
|
||||||
|
- abseil/base/errno_saver
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/debugging/demangle_internal (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/debugging/stacktrace (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/debugging/debugging_internal
|
||||||
|
- abseil/debugging/symbolize (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/dynamic_annotations
|
||||||
|
- abseil/base/malloc_internal
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/debugging/debugging_internal
|
||||||
|
- abseil/debugging/demangle_internal
|
||||||
|
- abseil/strings/strings
|
||||||
|
- abseil/functional/bind_front (1.20211102.0):
|
||||||
|
- abseil/base/base_internal
|
||||||
|
- abseil/container/compressed_tuple
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/utility/utility
|
||||||
|
- abseil/functional/function_ref (1.20211102.0):
|
||||||
|
- abseil/base/base_internal
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/hash/city (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/endian
|
||||||
|
- abseil/hash/hash (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/endian
|
||||||
|
- abseil/container/fixed_array
|
||||||
|
- abseil/hash/city
|
||||||
|
- abseil/hash/low_level_hash
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/numeric/int128
|
||||||
|
- abseil/strings/strings
|
||||||
|
- abseil/types/optional
|
||||||
|
- abseil/types/variant
|
||||||
|
- abseil/utility/utility
|
||||||
|
- abseil/hash/low_level_hash (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/endian
|
||||||
|
- abseil/numeric/bits
|
||||||
|
- abseil/numeric/int128
|
||||||
|
- abseil/memory (1.20211102.0):
|
||||||
|
- abseil/memory/memory (= 1.20211102.0)
|
||||||
|
- abseil/memory/memory (1.20211102.0):
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/meta (1.20211102.0):
|
||||||
|
- abseil/meta/type_traits (= 1.20211102.0)
|
||||||
|
- abseil/meta/type_traits (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/numeric/bits (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/numeric/int128 (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/numeric/bits
|
||||||
|
- abseil/numeric/representation (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/profiling/exponential_biased (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/profiling/sample_recorder (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/synchronization/synchronization
|
||||||
|
- abseil/time/time
|
||||||
|
- abseil/random/distributions (1.20211102.0):
|
||||||
|
- abseil/base/base_internal
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/numeric/bits
|
||||||
|
- abseil/random/internal/distribution_caller
|
||||||
|
- abseil/random/internal/fast_uniform_bits
|
||||||
|
- abseil/random/internal/fastmath
|
||||||
|
- abseil/random/internal/generate_real
|
||||||
|
- abseil/random/internal/iostream_state_saver
|
||||||
|
- abseil/random/internal/traits
|
||||||
|
- abseil/random/internal/uniform_helper
|
||||||
|
- abseil/random/internal/wide_multiply
|
||||||
|
- abseil/strings/strings
|
||||||
|
- abseil/random/internal/distribution_caller (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/fast_type_id
|
||||||
|
- abseil/utility/utility
|
||||||
|
- abseil/random/internal/fast_uniform_bits (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/random/internal/fastmath (1.20211102.0):
|
||||||
|
- abseil/numeric/bits
|
||||||
|
- abseil/random/internal/generate_real (1.20211102.0):
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/numeric/bits
|
||||||
|
- abseil/random/internal/fastmath
|
||||||
|
- abseil/random/internal/traits
|
||||||
|
- abseil/random/internal/iostream_state_saver (1.20211102.0):
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/numeric/int128
|
||||||
|
- abseil/random/internal/nonsecure_base (1.20211102.0):
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/random/internal/pool_urbg
|
||||||
|
- abseil/random/internal/salted_seed_seq
|
||||||
|
- abseil/random/internal/seed_material
|
||||||
|
- abseil/types/optional
|
||||||
|
- abseil/types/span
|
||||||
|
- abseil/random/internal/pcg_engine (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/numeric/bits
|
||||||
|
- abseil/numeric/int128
|
||||||
|
- abseil/random/internal/fastmath
|
||||||
|
- abseil/random/internal/iostream_state_saver
|
||||||
|
- abseil/random/internal/platform (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/random/internal/pool_urbg (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/endian
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/random/internal/randen
|
||||||
|
- abseil/random/internal/seed_material
|
||||||
|
- abseil/random/internal/traits
|
||||||
|
- abseil/random/seed_gen_exception
|
||||||
|
- abseil/types/span
|
||||||
|
- abseil/random/internal/randen (1.20211102.0):
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/random/internal/platform
|
||||||
|
- abseil/random/internal/randen_hwaes
|
||||||
|
- abseil/random/internal/randen_slow
|
||||||
|
- abseil/random/internal/randen_engine (1.20211102.0):
|
||||||
|
- abseil/base/endian
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/random/internal/iostream_state_saver
|
||||||
|
- abseil/random/internal/randen
|
||||||
|
- abseil/random/internal/randen_hwaes (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/random/internal/platform
|
||||||
|
- abseil/random/internal/randen_hwaes_impl
|
||||||
|
- abseil/random/internal/randen_hwaes_impl (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/numeric/int128
|
||||||
|
- abseil/random/internal/platform
|
||||||
|
- abseil/random/internal/randen_slow (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/endian
|
||||||
|
- abseil/numeric/int128
|
||||||
|
- abseil/random/internal/platform
|
||||||
|
- abseil/random/internal/salted_seed_seq (1.20211102.0):
|
||||||
|
- abseil/container/inlined_vector
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/random/internal/seed_material
|
||||||
|
- abseil/types/optional
|
||||||
|
- abseil/types/span
|
||||||
|
- abseil/random/internal/seed_material (1.20211102.0):
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/dynamic_annotations
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/random/internal/fast_uniform_bits
|
||||||
|
- abseil/strings/strings
|
||||||
|
- abseil/types/optional
|
||||||
|
- abseil/types/span
|
||||||
|
- abseil/random/internal/traits (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/random/internal/uniform_helper (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/random/internal/traits
|
||||||
|
- abseil/random/internal/wide_multiply (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/numeric/bits
|
||||||
|
- abseil/numeric/int128
|
||||||
|
- abseil/random/internal/traits
|
||||||
|
- abseil/random/random (1.20211102.0):
|
||||||
|
- abseil/random/distributions
|
||||||
|
- abseil/random/internal/nonsecure_base
|
||||||
|
- abseil/random/internal/pcg_engine
|
||||||
|
- abseil/random/internal/pool_urbg
|
||||||
|
- abseil/random/internal/randen_engine
|
||||||
|
- abseil/random/seed_sequences
|
||||||
|
- abseil/random/seed_gen_exception (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/random/seed_sequences (1.20211102.0):
|
||||||
|
- abseil/container/inlined_vector
|
||||||
|
- abseil/random/internal/nonsecure_base
|
||||||
|
- abseil/random/internal/pool_urbg
|
||||||
|
- abseil/random/internal/salted_seed_seq
|
||||||
|
- abseil/random/internal/seed_material
|
||||||
|
- abseil/random/seed_gen_exception
|
||||||
|
- abseil/types/span
|
||||||
|
- abseil/status/status (1.20211102.0):
|
||||||
|
- abseil/base/atomic_hook
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/container/inlined_vector
|
||||||
|
- abseil/debugging/stacktrace
|
||||||
|
- abseil/debugging/symbolize
|
||||||
|
- abseil/functional/function_ref
|
||||||
|
- abseil/strings/cord
|
||||||
|
- abseil/strings/str_format
|
||||||
|
- abseil/strings/strings
|
||||||
|
- abseil/types/optional
|
||||||
|
- abseil/status/statusor (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/status/status
|
||||||
|
- abseil/strings/strings
|
||||||
|
- abseil/types/variant
|
||||||
|
- abseil/utility/utility
|
||||||
|
- abseil/strings/cord (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/endian
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/container/fixed_array
|
||||||
|
- abseil/container/inlined_vector
|
||||||
|
- abseil/functional/function_ref
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/strings/cord_internal
|
||||||
|
- abseil/strings/cordz_functions
|
||||||
|
- abseil/strings/cordz_info
|
||||||
|
- abseil/strings/cordz_statistics
|
||||||
|
- abseil/strings/cordz_update_scope
|
||||||
|
- abseil/strings/cordz_update_tracker
|
||||||
|
- abseil/strings/internal
|
||||||
|
- abseil/strings/str_format
|
||||||
|
- abseil/strings/strings
|
||||||
|
- abseil/types/optional
|
||||||
|
- abseil/strings/cord_internal (1.20211102.0):
|
||||||
|
- abseil/base/base_internal
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/endian
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/base/throw_delegate
|
||||||
|
- abseil/container/compressed_tuple
|
||||||
|
- abseil/container/inlined_vector
|
||||||
|
- abseil/container/layout
|
||||||
|
- abseil/functional/function_ref
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/strings/strings
|
||||||
|
- abseil/types/span
|
||||||
|
- abseil/strings/cordz_functions (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/profiling/exponential_biased
|
||||||
|
- abseil/strings/cordz_handle (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/synchronization/synchronization
|
||||||
|
- abseil/strings/cordz_info (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/container/inlined_vector
|
||||||
|
- abseil/debugging/stacktrace
|
||||||
|
- abseil/strings/cord_internal
|
||||||
|
- abseil/strings/cordz_functions
|
||||||
|
- abseil/strings/cordz_handle
|
||||||
|
- abseil/strings/cordz_statistics
|
||||||
|
- abseil/strings/cordz_update_tracker
|
||||||
|
- abseil/synchronization/synchronization
|
||||||
|
- abseil/types/span
|
||||||
|
- abseil/strings/cordz_statistics (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/strings/cordz_update_tracker
|
||||||
|
- abseil/strings/cordz_update_scope (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/strings/cord_internal
|
||||||
|
- abseil/strings/cordz_info
|
||||||
|
- abseil/strings/cordz_update_tracker
|
||||||
|
- abseil/strings/cordz_update_tracker (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/strings/internal (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/endian
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/strings/str_format (1.20211102.0):
|
||||||
|
- abseil/strings/str_format_internal
|
||||||
|
- abseil/strings/str_format_internal (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/functional/function_ref
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/numeric/bits
|
||||||
|
- abseil/numeric/int128
|
||||||
|
- abseil/numeric/representation
|
||||||
|
- abseil/strings/strings
|
||||||
|
- abseil/types/optional
|
||||||
|
- abseil/types/span
|
||||||
|
- abseil/strings/strings (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/endian
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/base/throw_delegate
|
||||||
|
- abseil/memory/memory
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/numeric/bits
|
||||||
|
- abseil/numeric/int128
|
||||||
|
- abseil/strings/internal
|
||||||
|
- abseil/synchronization/graphcycles_internal (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/base_internal
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/malloc_internal
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/synchronization/kernel_timeout_internal (1.20211102.0):
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/time/time
|
||||||
|
- abseil/synchronization/synchronization (1.20211102.0):
|
||||||
|
- abseil/base/atomic_hook
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/base_internal
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/dynamic_annotations
|
||||||
|
- abseil/base/malloc_internal
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/debugging/stacktrace
|
||||||
|
- abseil/debugging/symbolize
|
||||||
|
- abseil/synchronization/graphcycles_internal
|
||||||
|
- abseil/synchronization/kernel_timeout_internal
|
||||||
|
- abseil/time/time
|
||||||
|
- abseil/time (1.20211102.0):
|
||||||
|
- abseil/time/internal (= 1.20211102.0)
|
||||||
|
- abseil/time/time (= 1.20211102.0)
|
||||||
|
- abseil/time/internal (1.20211102.0):
|
||||||
|
- abseil/time/internal/cctz (= 1.20211102.0)
|
||||||
|
- abseil/time/internal/cctz (1.20211102.0):
|
||||||
|
- abseil/time/internal/cctz/civil_time (= 1.20211102.0)
|
||||||
|
- abseil/time/internal/cctz/time_zone (= 1.20211102.0)
|
||||||
|
- abseil/time/internal/cctz/civil_time (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/time/internal/cctz/time_zone (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/time/internal/cctz/civil_time
|
||||||
|
- abseil/time/time (1.20211102.0):
|
||||||
|
- abseil/base/base
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/numeric/int128
|
||||||
|
- abseil/strings/strings
|
||||||
|
- abseil/time/internal/cctz/civil_time
|
||||||
|
- abseil/time/internal/cctz/time_zone
|
||||||
|
- abseil/types (1.20211102.0):
|
||||||
|
- abseil/types/any (= 1.20211102.0)
|
||||||
|
- abseil/types/bad_any_cast (= 1.20211102.0)
|
||||||
|
- abseil/types/bad_any_cast_impl (= 1.20211102.0)
|
||||||
|
- abseil/types/bad_optional_access (= 1.20211102.0)
|
||||||
|
- abseil/types/bad_variant_access (= 1.20211102.0)
|
||||||
|
- abseil/types/compare (= 1.20211102.0)
|
||||||
|
- abseil/types/optional (= 1.20211102.0)
|
||||||
|
- abseil/types/span (= 1.20211102.0)
|
||||||
|
- abseil/types/variant (= 1.20211102.0)
|
||||||
|
- abseil/types/any (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/fast_type_id
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/types/bad_any_cast
|
||||||
|
- abseil/utility/utility
|
||||||
|
- abseil/types/bad_any_cast (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/types/bad_any_cast_impl
|
||||||
|
- abseil/types/bad_any_cast_impl (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/types/bad_optional_access (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/types/bad_variant_access (1.20211102.0):
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/raw_logging_internal
|
||||||
|
- abseil/types/compare (1.20211102.0):
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/types/optional (1.20211102.0):
|
||||||
|
- abseil/base/base_internal
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/memory/memory
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/types/bad_optional_access
|
||||||
|
- abseil/utility/utility
|
||||||
|
- abseil/types/span (1.20211102.0):
|
||||||
|
- abseil/algorithm/algorithm
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/base/throw_delegate
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/types/variant (1.20211102.0):
|
||||||
|
- abseil/base/base_internal
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/base/core_headers
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- abseil/types/bad_variant_access
|
||||||
|
- abseil/utility/utility
|
||||||
|
- abseil/utility/utility (1.20211102.0):
|
||||||
|
- abseil/base/base_internal
|
||||||
|
- abseil/base/config
|
||||||
|
- abseil/meta/type_traits
|
||||||
|
- BoringSSL-GRPC (0.0.24):
|
||||||
|
- BoringSSL-GRPC/Implementation (= 0.0.24)
|
||||||
|
- BoringSSL-GRPC/Interface (= 0.0.24)
|
||||||
|
- BoringSSL-GRPC/Implementation (0.0.24):
|
||||||
|
- BoringSSL-GRPC/Interface (= 0.0.24)
|
||||||
|
- BoringSSL-GRPC/Interface (0.0.24)
|
||||||
|
- cloud_firestore (4.0.5):
|
||||||
|
- Firebase/Firestore (= 10.1.0)
|
||||||
|
- firebase_core
|
||||||
|
- Flutter
|
||||||
|
- nanopb (< 2.30910.0, >= 2.30908.0)
|
||||||
|
- Firebase/CoreOnly (10.1.0):
|
||||||
|
- FirebaseCore (= 10.1.0)
|
||||||
|
- Firebase/Firestore (10.1.0):
|
||||||
|
- Firebase/CoreOnly
|
||||||
|
- FirebaseFirestore (~> 10.1.0)
|
||||||
|
- firebase_core (2.2.0):
|
||||||
|
- Firebase/CoreOnly (= 10.1.0)
|
||||||
|
- Flutter
|
||||||
|
- FirebaseCore (10.1.0):
|
||||||
|
- FirebaseCoreInternal (~> 10.0)
|
||||||
|
- GoogleUtilities/Environment (~> 7.8)
|
||||||
|
- GoogleUtilities/Logger (~> 7.8)
|
||||||
|
- FirebaseCoreInternal (10.1.0):
|
||||||
|
- "GoogleUtilities/NSData+zlib (~> 7.8)"
|
||||||
|
- FirebaseFirestore (10.1.0):
|
||||||
|
- abseil/algorithm (~> 1.20211102.0)
|
||||||
|
- abseil/base (~> 1.20211102.0)
|
||||||
|
- abseil/container/flat_hash_map (~> 1.20211102.0)
|
||||||
|
- abseil/memory (~> 1.20211102.0)
|
||||||
|
- abseil/meta (~> 1.20211102.0)
|
||||||
|
- abseil/strings/strings (~> 1.20211102.0)
|
||||||
|
- abseil/time (~> 1.20211102.0)
|
||||||
|
- abseil/types (~> 1.20211102.0)
|
||||||
|
- FirebaseCore (~> 10.0)
|
||||||
|
- "gRPC-C++ (~> 1.44.0)"
|
||||||
|
- leveldb-library (~> 1.22)
|
||||||
|
- nanopb (< 2.30910.0, >= 2.30908.0)
|
||||||
|
- Flutter (1.0.0)
|
||||||
|
- GoogleUtilities/Environment (7.8.0):
|
||||||
|
- PromisesObjC (< 3.0, >= 1.2)
|
||||||
|
- GoogleUtilities/Logger (7.8.0):
|
||||||
|
- GoogleUtilities/Environment
|
||||||
|
- "GoogleUtilities/NSData+zlib (7.8.0)"
|
||||||
|
- "gRPC-C++ (1.44.0)":
|
||||||
|
- "gRPC-C++/Implementation (= 1.44.0)"
|
||||||
|
- "gRPC-C++/Interface (= 1.44.0)"
|
||||||
|
- "gRPC-C++/Implementation (1.44.0)":
|
||||||
|
- abseil/base/base (= 1.20211102.0)
|
||||||
|
- abseil/base/core_headers (= 1.20211102.0)
|
||||||
|
- abseil/container/flat_hash_map (= 1.20211102.0)
|
||||||
|
- abseil/container/inlined_vector (= 1.20211102.0)
|
||||||
|
- abseil/functional/bind_front (= 1.20211102.0)
|
||||||
|
- abseil/hash/hash (= 1.20211102.0)
|
||||||
|
- abseil/memory/memory (= 1.20211102.0)
|
||||||
|
- abseil/random/random (= 1.20211102.0)
|
||||||
|
- abseil/status/status (= 1.20211102.0)
|
||||||
|
- abseil/status/statusor (= 1.20211102.0)
|
||||||
|
- abseil/strings/cord (= 1.20211102.0)
|
||||||
|
- abseil/strings/str_format (= 1.20211102.0)
|
||||||
|
- abseil/strings/strings (= 1.20211102.0)
|
||||||
|
- abseil/synchronization/synchronization (= 1.20211102.0)
|
||||||
|
- abseil/time/time (= 1.20211102.0)
|
||||||
|
- abseil/types/optional (= 1.20211102.0)
|
||||||
|
- abseil/types/variant (= 1.20211102.0)
|
||||||
|
- abseil/utility/utility (= 1.20211102.0)
|
||||||
|
- "gRPC-C++/Interface (= 1.44.0)"
|
||||||
|
- gRPC-Core (= 1.44.0)
|
||||||
|
- "gRPC-C++/Interface (1.44.0)"
|
||||||
|
- gRPC-Core (1.44.0):
|
||||||
|
- gRPC-Core/Implementation (= 1.44.0)
|
||||||
|
- gRPC-Core/Interface (= 1.44.0)
|
||||||
|
- gRPC-Core/Implementation (1.44.0):
|
||||||
|
- abseil/base/base (= 1.20211102.0)
|
||||||
|
- abseil/base/core_headers (= 1.20211102.0)
|
||||||
|
- abseil/container/flat_hash_map (= 1.20211102.0)
|
||||||
|
- abseil/container/inlined_vector (= 1.20211102.0)
|
||||||
|
- abseil/functional/bind_front (= 1.20211102.0)
|
||||||
|
- abseil/hash/hash (= 1.20211102.0)
|
||||||
|
- abseil/memory/memory (= 1.20211102.0)
|
||||||
|
- abseil/random/random (= 1.20211102.0)
|
||||||
|
- abseil/status/status (= 1.20211102.0)
|
||||||
|
- abseil/status/statusor (= 1.20211102.0)
|
||||||
|
- abseil/strings/cord (= 1.20211102.0)
|
||||||
|
- abseil/strings/str_format (= 1.20211102.0)
|
||||||
|
- abseil/strings/strings (= 1.20211102.0)
|
||||||
|
- abseil/synchronization/synchronization (= 1.20211102.0)
|
||||||
|
- abseil/time/time (= 1.20211102.0)
|
||||||
|
- abseil/types/optional (= 1.20211102.0)
|
||||||
|
- abseil/types/variant (= 1.20211102.0)
|
||||||
|
- abseil/utility/utility (= 1.20211102.0)
|
||||||
|
- BoringSSL-GRPC (= 0.0.24)
|
||||||
|
- gRPC-Core/Interface (= 1.44.0)
|
||||||
|
- Libuv-gRPC (= 0.0.10)
|
||||||
|
- gRPC-Core/Interface (1.44.0)
|
||||||
|
- leveldb-library (1.22.1)
|
||||||
|
- Libuv-gRPC (0.0.10):
|
||||||
|
- Libuv-gRPC/Implementation (= 0.0.10)
|
||||||
|
- Libuv-gRPC/Interface (= 0.0.10)
|
||||||
|
- Libuv-gRPC/Implementation (0.0.10):
|
||||||
|
- Libuv-gRPC/Interface (= 0.0.10)
|
||||||
|
- Libuv-gRPC/Interface (0.0.10)
|
||||||
|
- nanopb (2.30909.0):
|
||||||
|
- nanopb/decode (= 2.30909.0)
|
||||||
|
- nanopb/encode (= 2.30909.0)
|
||||||
|
- nanopb/decode (2.30909.0)
|
||||||
|
- nanopb/encode (2.30909.0)
|
||||||
|
- PromisesObjC (2.1.1)
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`)
|
||||||
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
|
- Flutter (from `Flutter`)
|
||||||
|
|
||||||
|
SPEC REPOS:
|
||||||
|
trunk:
|
||||||
|
- abseil
|
||||||
|
- BoringSSL-GRPC
|
||||||
|
- Firebase
|
||||||
|
- FirebaseCore
|
||||||
|
- FirebaseCoreInternal
|
||||||
|
- FirebaseFirestore
|
||||||
|
- GoogleUtilities
|
||||||
|
- "gRPC-C++"
|
||||||
|
- gRPC-Core
|
||||||
|
- leveldb-library
|
||||||
|
- Libuv-gRPC
|
||||||
|
- nanopb
|
||||||
|
- PromisesObjC
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
cloud_firestore:
|
||||||
|
:path: ".symlinks/plugins/cloud_firestore/ios"
|
||||||
|
firebase_core:
|
||||||
|
:path: ".symlinks/plugins/firebase_core/ios"
|
||||||
|
Flutter:
|
||||||
|
:path: Flutter
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
abseil: ebe5b5529fb05d93a8bdb7951607be08b7fa71bc
|
||||||
|
BoringSSL-GRPC: 3175b25143e648463a56daeaaa499c6cb86dad33
|
||||||
|
cloud_firestore: 345ab5f423db6ae492abb648372156bdccb9df42
|
||||||
|
Firebase: 444b35a9c568a516666213c2f6cccd10cb12559f
|
||||||
|
firebase_core: d2242c6f318db1d0dcecfbfa491e943337b0d755
|
||||||
|
FirebaseCore: 55e7ae35991ccca4db03ff8d8df6ed5f17a3e4c7
|
||||||
|
FirebaseCoreInternal: 96d75228e10fd369564da51bd898414eb0f54df5
|
||||||
|
FirebaseFirestore: d482e5e0f95dba8ef5d499a7efa87ba2f56ef0c0
|
||||||
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
|
GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7
|
||||||
|
"gRPC-C++": 9675f953ace2b3de7c506039d77be1f2e77a8db2
|
||||||
|
gRPC-Core: 943e491cb0d45598b0b0eb9e910c88080369290b
|
||||||
|
leveldb-library: 50c7b45cbd7bf543c81a468fe557a16ae3db8729
|
||||||
|
Libuv-gRPC: 55e51798e14ef436ad9bc45d12d43b77b49df378
|
||||||
|
nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431
|
||||||
|
PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
||||||
|
|
||||||
|
COCOAPODS: 1.11.3
|
||||||
@@ -0,0 +1,552 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 50;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
2D3BF4E90FD3D66E6379652C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9668B7DCB1C084B61D13F13B /* Pods_Runner.framework */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
04335B06475E8C2B1917681C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
0A79DE27E9F9DDADA3D915D2 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
8D3EFC6842916D850020C95C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
9668B7DCB1C084B61D13F13B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
2D3BF4E90FD3D66E6379652C /* Pods_Runner.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
32816CF134A5ECA965EE4EF3 /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0A79DE27E9F9DDADA3D915D2 /* Pods-Runner.debug.xcconfig */,
|
||||||
|
04335B06475E8C2B1917681C /* Pods-Runner.release.xcconfig */,
|
||||||
|
8D3EFC6842916D850020C95C /* Pods-Runner.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Pods;
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
32816CF134A5ECA965EE4EF3 /* Pods */,
|
||||||
|
DF1EDFCBAB38011323A8D81B /* Frameworks */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
DF1EDFCBAB38011323A8D81B /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9668B7DCB1C084B61D13F13B /* Pods_Runner.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
C454F828EEBC86C10B4E163A /* [CP] Check Pods Manifest.lock */,
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
897D29AE5A72F625B86D7F66 /* [CP] Embed Pods Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
LastUpgradeCheck = 1300;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
897D29AE5A72F625B86D7F66 /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
C454F828EEBC86C10B4E163A /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = 6Z5P8GG96U;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = 6Z5P8GG96U;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = 6Z5P8GG96U;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1300"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import UIKit
|
||||||
|
import Flutter
|
||||||
|
|
||||||
|
@UIApplicationMain
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "Icon-App-1024x1024@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 564 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |