Compare commits

..
Author SHA1 Message Date
hugo 7345e415a7 chore(release): publish packages
- wyatt_crud_bloc@0.1.0
2022-11-13 20:29:19 -05:00
hugo ff680614aa typo(crud): fix line length 2022-11-13 20:21:20 -05:00
hugo 3e110e2eb5 fix(crud): update example 2022-11-13 20:19:21 -05:00
hugo 1ccb0a540e refactor(crud)!: migrate to wyatt architecture 2022-11-13 20:19:10 -05:00
hugo f9da719b91 refactor(crud): add vscode config 2022-11-13 20:18:10 -05:00
hugo 95f41025f4 chore(release): publish packages
- wyatt_authentication_bloc@0.3.0+1
2022-11-11 19:09:01 -05:00
hugo eceda684f0 fix(auth): fix some bugs to validate publishing 2022-11-11 19:08:28 -05:00
hugo 291c195ceb chore(release): publish packages
- wyatt_authentication_bloc@0.3.0
2022-11-11 19:01:49 -05:00
hugo 1896497c1a refactor(auth): use correct deps 2022-11-11 19:01:00 -05:00
hugo 87254ef547 feat(auth): add fully functionnal mock data source 2022-11-11 18:48:30 -05:00
hugo da34acd35b fix(auth): return exception and not throwing it anymore 2022-11-11 18:47:59 -05:00
hugo d9d0625c67 fix(auth): exception returns msg as message 2022-11-11 18:47:16 -05:00
hugo 08f789725b feat(auth): add toString and equality on entities and models 2022-11-11 18:46:37 -05:00
hugo 33ac5c6280 doc(auth): update pubspec and readme 2022-11-11 16:54:58 -05:00
hugo 762b9bcd11 refactor(auth): update example with new listeners 2022-11-11 16:54:40 -05:00
hugo 38480d84f4 test(auth): add tests for all features 2022-11-11 16:54:08 -05:00
hugo 03a51b97ad feat(auth)!: add email verification, password reset support 2022-11-11 16:53:36 -05:00
hugo 1dd49fa080 chore(release): publish packages
- wyatt_architecture@0.0.2
 - wyatt_authentication_bloc@0.2.1+6
 - wyatt_form_bloc@0.1.0+1
2022-11-10 22:59:51 -05:00
hugo 0219e3d557 doc(arch): update readme 2022-11-10 22:58:35 -05:00
hugo 3de7d01173 feat(arch): add new typedef for stream and simple result 2022-11-10 22:58:19 -05:00
hugo 4183da6259 feat(arch): make base class const 2022-11-10 22:57:51 -05:00
hugo 1f24a76717 chore(form): add hosted dependecy 2022-11-10 13:51:37 -05:00
159 changed files with 6732 additions and 1722 deletions
+103
View File
@@ -3,6 +3,109 @@
All notable changes to this project will be documented in this file.
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
### Changes
+4
View File
@@ -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
- **FEAT**: add exceptions, datasources, repositories, and usecases.
+142 -14
View File
@@ -7,7 +7,7 @@
* 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,
* 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.
@@ -16,31 +16,159 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
# Flutter - Wyatt Architecture
<p align="left">
<a href="https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis">
<img src="https://img.shields.io/badge/Style-Wyatt%20Analysis-blue.svg?style=flat-square" alt="Style: Wyatt Analysis" />
</a>
<img src="https://img.shields.io/badge/SDK-Flutter-blue?style=flat-square" alt="SDK: Flutter" />
</p>
Architecture for Flutter.
Following: <https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html>
The Wyatt Architecture for Flutter.
## Features
- Usecase
- Repository
- DataSource
- Entity
## Getting started
<!-- TODO -->
* Usecase
* Repository
* DataSource
* Entity
## 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
// 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';
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';
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
// 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';
typedef FutureResult<T> = Future<Result<T, AppException>>;
typedef StreamResult<T> = Stream<Result<T, AppException>>;
typedef Res<T> = Result<T, AppException>;
// ignore: one_member_abstracts
abstract class UseCase<Parameters, ReturnType> {
@@ -14,4 +14,5 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'no_param.dart';
export 'usecase.dart';
+1 -1
View File
@@ -1,7 +1,7 @@
name: wyatt_architecture
description: A new Wyatt package
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
@@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License
// 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
- Update a dependency to the latest release.
+20 -258
View File
@@ -29,43 +29,28 @@ Authentication Bloc for Flutter.
## Features
- UserInterface
* UserFirebase : FirebaseAuth user implementation
- AuthenticationRepositoryInterface
* AuthenticationRepositoryFirebase : FirebaseAuth implementation
- ExceptionsInterface
* ExceptionsFirebase : FirebaseAuth Exception parsing implementation
- AuthenticationBloc
* Tracks every user changes
- Right after the listener has been registered.
- When a user is signed in.
- When the current user is signed out.
- When there is a change in the current user's token.
- On `refresh()`
* Start/Stop listening on demand
- `start()` to listen to user changes
- `stop()` to cancel listener
- SignUpCubit
* Handles email/password validation and password confirmation
* Handles register with email/password
* 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
- Wyatt Architecture
- Entities:
- Account : AccountModel -> Contains account information from provider
- AccountWrapper : AccountWrapperModel -> Contains account and extra data.
- Data Sources:
- Local:
- Cached Authentication Data : AuthenticationCacheDataSourceImpl -> Provides a cache implementation
- Remote:
- Remote Authentication Data : AuthenticationFirebaseDataSourceImpl -> Provides a proxy to FirebaseAuth
- Repositories:
- AuthenticationRepository : AuthenticationRepositoryImpl -> Provides all authentication methods
- Features:
- Authentication:
- AuthenticationBuilder : widget to build reactive view from authentication state
- AuthenticationCubit : tracks every auth changes, have sign out capability.
- SignUp:
- SignUpCubit: implementation of a FormDataCubit from `wyatt_form_bloc` for the sign up
- SignIn:
- SignUpCubit: implementation of a FormDataCubit from `wyatt_form_bloc` for the sign in
- Consistent
* Every class have same naming convention
- Tested
* Partially tested with *bloc_test*
## Getting started
@@ -77,227 +62,4 @@ import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
## Usage
Create an authentication repository:
```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.
// TODO
@@ -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:
git:
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
# The following adds the Cupertino Icons font to your application.
@@ -45,3 +45,5 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
firebase_options.dart
@@ -3,7 +3,7 @@
// -----
// File: bootstrap.dart
// Created Date: 19/08/2022 15:05:17
// Last Modified: Wed Nov 09 2022
// Last Modified: Fri Nov 11 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/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_bloc/flutter_bloc.dart';
@@ -25,11 +23,6 @@ Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
FlutterError.onError = (details) {
debugPrint(details.toString());
};
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await GetItInitializer.init();
runApp(await builder());
@@ -16,6 +16,7 @@
import 'package:get_it/get_it.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
final getIt = GetIt.I;
@@ -23,12 +24,33 @@ abstract class GetItInitializer {
static Future<void> init() async {
getIt
..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>>(
() => AuthenticationCacheDataSourceImpl<int>(),
);
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
// Created Date: 19/08/2022 15:24:37
// Last Modified: Thu Nov 10 2022
// Last Modified: Fri Nov 11 2022
// -----
// Copyright (c) 2022
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
@@ -73,16 +72,12 @@ class SignInForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<SignInCubit<int>, SignInState>(
listener: (context, state) {
if (state.status.isSubmissionFailure) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'Sign In Failure')),
);
}
},
return SignInListener<int>(
onError: (context, status, errorMessage) => ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(content: Text(errorMessage ?? 'Sign In Failure')),
),
child: SingleChildScrollView(
child: Column(
children: [
@@ -3,13 +3,12 @@
// -----
// File: sign_up_form.dart
// Created Date: 19/08/2022 14:41:08
// Last Modified: Thu Nov 10 2022
// Last Modified: Fri Nov 11 2022
// -----
// Copyright (c) 2022
import 'package:example_router/core/constants/form_field.dart';
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_form_bloc/wyatt_form_bloc.dart';
@@ -110,29 +109,25 @@ class SignUpForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<SignUpCubit<int>, SignUpState>(
listener: (context, state) {
if (state.status.isSubmissionFailure) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'Sign Up Failure')),
);
}
},
child: SingleChildScrollView(
child: Column(
children: [
_EmailInput(),
const SizedBox(height: 8),
_PasswordInput(),
const SizedBox(height: 8),
_ConfirmPasswordInput(),
const SizedBox(height: 16),
_SignUpButton(),
],
),
),
);
return SignUpListener<int>(
onError: (context, status, errorMessage) =>
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(content: Text(errorMessage ?? 'Sign Up Failure')),
),
child: SingleChildScrollView(
child: Column(
children: [
_EmailInput(),
const SizedBox(height: 8),
_PasswordInput(),
const SizedBox(height: 8),
_ConfirmPasswordInput(),
const SizedBox(height: 16),
_SignUpButton(),
],
),
));
}
}
@@ -33,25 +33,26 @@ dependencies:
go_router: ^5.1.5
firebase_core: ^2.1.1
flutter_bloc: ^8.1.1
get_it: ^7.2.0
wyatt_authentication_bloc:
path: "../"
wyatt_form_bloc:
git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_form_bloc-v0.1.0
path: packages/wyatt_form_bloc
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.1.0+1
wyatt_architecture:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.0.2
wyatt_type_utils:
git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_type_utils-v0.0.3+1
path: packages/wyatt_type_utils
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.0.3+1
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.5
get_it: ^7.2.0
dev_dependencies:
flutter_test:
@@ -17,4 +17,5 @@
abstract class AuthFormName {
static const String signUpForm = 'wyattSignUpForm';
static const String signInForm = 'wyattSignInForm';
static const String passwordResetForm = 'wyattPasswordResetForm';
}
@@ -23,6 +23,9 @@ abstract class AuthenticationFailureInterface extends AppException
String code;
String msg;
@override
String get message => msg;
AuthenticationFailureInterface(this.code, this.msg);
AuthenticationFailureInterface.fromCode(this.code)
: msg = 'An unknown error occurred.';
@@ -246,3 +249,10 @@ abstract class SignOutFailureInterface extends AuthenticationFailureInterface {
/// {@macro sign_out_failure}
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();
}
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
// 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/>.
export 'local/authentication_cache_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/>.
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_type_utils/wyatt_type_utils.dart';
@@ -28,7 +27,18 @@ class AuthenticationFirebaseDataSourceImpl
Account _mapper(User user) => AccountModel(
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,
phoneNumber: user.phoneNumber,
photoURL: user.photoURL,
);
@override
@@ -55,6 +65,8 @@ class AuthenticationFirebaseDataSourceImpl
}
@override
/// {@macro signup}
Future<Account> signUp({
required String email,
required String password,
@@ -95,9 +107,10 @@ class AuthenticationFirebaseDataSourceImpl
} else {
throw Exception(); // Get caught just after.
}
} on FirebaseAuthException catch (e) {
throw GetIdTokenFailureFirebase.fromCode(e.code);
} catch (_) {
// TODO(hpcl): implement a non ambiguous exception for this case
throw ServerException();
throw GetIdTokenFailureFirebase();
}
}
@@ -107,4 +120,83 @@ class AuthenticationFirebaseDataSourceImpl
final Account? account = (user.isNotNull) ? _mapper(user!) : null;
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
// Please see the AUTHORS file for details.
//
@@ -16,12 +17,71 @@
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
class AccountModel implements Account {
class AccountModel extends Account {
@override
final String uid;
@override
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
// Please see the AUTHORS file for details.
//
@@ -24,4 +25,12 @@ class AccountWrapperModel<T> extends AccountWrapper<T> {
final T? 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(
_authenticationLocalDataSource.storeData,
(error) => throw error,
(error) async => error,
);
}
return account;
@@ -208,4 +208,70 @@ class AuthenticationRepositoryImpl<T extends Object>
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> refresh();
Stream<Account?> streamAccount();
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
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:equatable/equatable.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.
String get uid;
@@ -24,4 +25,59 @@ abstract class Account extends Entity {
///
/// Will be `null` if signing in anonymously.
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
// 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_authentication_bloc/src/domain/entities/account.dart';
abstract class AccountWrapper<T> extends Entity {
abstract class AccountWrapper<T> extends Equatable implements Entity {
Account? get account;
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 {
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({
required String email,
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({
required String email,
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> 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();
FutureResult<String> getIdentityToken();
@@ -40,16 +40,16 @@ class AuthenticationCubit<Extra> extends Cubit<AuthenticationState<Extra>> {
accountFutureResult.fold(
(value) {
if (value.account.isNotNull) {
emit(AuthenticationState.authenticated(value));
emit(AuthenticationState<Extra>.authenticated(value));
return;
}
_authenticationRepository.destroyCache();
emit(const AuthenticationState.unauthenticated());
emit(AuthenticationState<Extra>.unauthenticated());
return;
},
(error) {
_authenticationRepository.destroyCache();
emit(const AuthenticationState.unauthenticated());
emit(AuthenticationState<Extra>.unauthenticated());
return;
},
);
@@ -35,7 +35,7 @@ class AuthenticationState<Extra> extends Equatable {
: this._(status: AuthenticationStatus.unauthenticated);
@override
List<Object?> get props => [status];
List<Object?> get props => [status, accountWrapper];
@override
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
// Please see the AUTHORS file for details.
//
@@ -14,47 +15,35 @@
// 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 'crud_cubit.dart';
part of 'email_verification_cubit.dart';
enum CrudStatus {
idle,
loading,
success,
failure,
}
class CrudState<T> extends Equatable {
final CrudStatus status;
final List<T?> data;
final Stream<List<T?>>? stream;
class EmailVerificationState extends Equatable {
final FormStatus status;
final bool isVerified;
final String? errorMessage;
const CrudState({
this.status = CrudStatus.idle,
this.data = const [],
this.stream,
const EmailVerificationState({
this.isVerified = false,
this.status = FormStatus.pure,
this.errorMessage,
});
@override
List<Object?> get props => [status, data];
CrudState<T> copyWith({
CrudStatus? status,
List<T?>? data,
Stream<List<T?>>? stream,
EmailVerificationState copyWith({
FormStatus? status,
bool? isVerified,
String? errorMessage,
}) {
return CrudState<T>(
status: status ?? this.status,
data: data ?? this.data,
stream: stream ?? this.stream,
errorMessage: errorMessage ?? this.errorMessage,
);
}
}) =>
EmailVerificationState(
status: status ?? this.status,
isVerified: isVerified ?? this.isVerified,
errorMessage: errorMessage ?? this.errorMessage,
);
@override
String toString() =>
// ignore: lines_longer_than_80_chars
'CrudState(status: $status, data: $data, stream: ${stream != null ? 'listening' : 'null'}, errorMessage: $errorMessage)';
List<Object?> get props => [status, isVerified, 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
// along with this program. If not, see <https://www.gnu.org/licenses/>.
abstract class Model<O, T> {
String? get id;
Map<String, Object> toMap();
T? from(O? object);
}
export 'builder/email_verification_builder.dart';
export 'cubit/email_verification_cubit.dart';
@@ -1,19 +1,21 @@
// 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/>.
export 'authentication/authentication.dart';
export 'email_verification/email_verification.dart';
export 'password_reset/password_reset.dart';
export 'sign_in/sign_in.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
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'queries_firestore.dart';
export 'queries_interface.dart';
export 'cubit/password_reset_cubit.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
// 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/>.
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
// 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/>.
export 'cubit/sign_up_cubit.dart';
export 'listener/sign_up_listener.dart';
+10 -15
View File
@@ -1,7 +1,7 @@
name: wyatt_authentication_bloc
description: Authentication BLoC for Flutter
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
@@ -12,7 +12,7 @@ environment:
dependencies:
flutter:
sdk: flutter
crypto: ^3.0.2
flutter_bloc: ^8.1.1
equatable: ^2.0.5
@@ -23,28 +23,23 @@ dependencies:
twitter_login: ^4.2.3
wyatt_form_bloc:
git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_form_bloc-v0.1.0
path: packages/wyatt_form_bloc
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: ^0.1.0+1
wyatt_architecture:
git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_architecture-v0.0.2-dev.0
path: packages/wyatt_architecture
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: ^0.0.2
wyatt_type_utils:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.0.3+1
version: ^0.0.3+1
dev_dependencies:
flutter_test:
sdk: flutter
bloc_test: ^9.1.0
mocktail: ^0.3.0
wyatt_analysis:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 2.2.2
@@ -17,100 +17,114 @@
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_type_utils/wyatt_type_utils.dart';
class MockAuthenticationRepository extends Mock
implements AuthenticationRepository {}
implements AuthenticationRepository<int> {}
class MockUser extends Mock implements User {}
class MockAccount extends Mock implements Account {}
void main() {
group('AuthenticationCubit<T>', () {
final MockUser user = MockUser();
late AuthenticationRepository authenticationRepository;
final MockAccount account = MockAccount();
final AccountWrapper<int> wrapper = AccountWrapperModel(account, 10);
late AuthenticationRepository<int> authenticationRepository;
setUp(() {
authenticationRepository = MockAuthenticationRepository();
when(() => authenticationRepository.user).thenAnswer(
when(() => authenticationRepository.streamAccount()).thenAnswer(
(_) => const Stream.empty(),
);
when(() => authenticationRepository.cubitStatus).thenAnswer(
(_) => Stream.fromIterable([AuthCubitStatus.stoped]),
);
when(
() => authenticationRepository.currentUser,
).thenReturn(user);
() => authenticationRepository.getAccount(),
).thenAnswer((_) async => Ok(account));
});
test('initial auth state is `unknown`', () {
expect(
AuthenticationCubit<void>(
AuthenticationCubit<int>(
authenticationRepository: authenticationRepository,
).state,
const AuthenticationState<Never>.unknown(),
);
});
test('initial cubit status is `stoped`', () async {
expect(
await AuthenticationCubit<void>(
authenticationRepository: authenticationRepository,
).status,
AuthCubitStatus.stoped,
);
});
group('UserChanged', () {
blocTest<AuthenticationCubit<void>, AuthenticationState<void>>(
'emits authenticated when user is not empty',
group('ListenForAuthenticationChanges', () {
blocTest<AuthenticationCubit<int>, AuthenticationState<int>>(
'emits authenticated when stream contains account',
setUp: () {
when(() => user.isNotEmpty).thenReturn(true);
when(() => authenticationRepository.user).thenAnswer(
(_) => Stream.value(user),
when(() => authenticationRepository.streamAccount()).thenAnswer(
(_) => Stream.fromIterable([
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(
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(() => authenticationRepository.signOut()).called(1);
},
@@ -18,7 +18,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
class MockUser extends Mock implements User {}
class MockAccount extends Mock implements Account {}
void main() {
group('AuthenticationState', () {
@@ -27,29 +27,33 @@ void main() {
const AuthenticationState<void> state =
AuthenticationState.unauthenticated();
expect(state.status, AuthenticationStatus.unauthenticated);
expect(state.user, null);
expect(state.accountWrapper, null);
});
});
group('authenticated', () {
test('has correct status', () {
final MockUser user = MockUser();
final MockAccount account = MockAccount();
final AuthenticationState<void> state =
AuthenticationState.authenticated(user, null);
AuthenticationState.authenticated(
AccountWrapperModel<void>(account, null),
);
expect(state.status, AuthenticationStatus.authenticated);
expect(state.user, user);
expect(state.accountWrapper?.account, account);
});
});
group('authenticated with extra data', () {
test('has correct status', () {
final MockUser user = MockUser();
final MockAccount account = MockAccount();
const String extra = 'AwesomeExtraData';
final AuthenticationState<String> state =
AuthenticationState.authenticated(user, extra);
AuthenticationState.authenticated(
AccountWrapperModel(account, extra),
);
expect(state.status, AuthenticationStatus.authenticated);
expect(state.user, user);
expect(state.extra, extra);
expect(state.accountWrapper?.account, account);
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: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 {}
implements AuthenticationRepository<int> {}
class MockAuthenticationCubit extends Mock
implements AuthenticationCubit<void> {}
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 Email invalidEmail = Email.dirty(invalidEmailString);
const String validEmailString = 'test@gmail.com';
const Email validEmail = Email.dirty(validEmailString);
const String invalidPasswordString = 'invalid';
const Password invalidPassword = Password.dirty(invalidPasswordString);
const String validPasswordString = 't0pS3cret1234';
const Password validPassword = Password.dirty(validPasswordString);
group('SignInCubit', () {
late AuthenticationRepository authenticationRepository;
late AuthenticationCubit<void> authenticationCubit;
final MockAccount account = MockAccount();
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(() {
authenticationRepository = MockAuthenticationRepository();
authenticationCubit = MockAuthenticationCubit();
formRepository = MockFormRepository();
when(
() => authenticationRepository.signInWithEmailAndPassword(
email: any(named: 'email'),
password: any(named: 'password'),
),
).thenAnswer((_) async {});
).thenAnswer((_) async => Ok(account));
when(
() => authenticationCubit.start(),
).thenReturn(true);
() => authenticationRepository.formRepository,
).thenAnswer((_) => formRepository);
when(
() => formRepository.accessForm(AuthFormName.signInForm),
).thenAnswer((_) => form);
});
test('initial state is SignInState', () {
@@ -64,33 +79,90 @@ void main() {
SignInCubit(
authenticationRepository: authenticationRepository,
).state,
const SignInState(),
SignInState(form: form),
);
});
group('emailChanged', () {
blocTest<SignInCubit, SignInState>(
blocTest<SignInCubit<int>, SignInState>(
'emits [invalid] when email/password are invalid',
build: () => SignInCubit(
authenticationRepository: authenticationRepository,
),
act: (cubit) => cubit.emailChanged(invalidEmailString),
expect: () => const <SignInState>[
SignInState(email: invalidEmail, status: FormStatus.invalid),
expect: () => <SignInState>[
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',
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(
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),
expect: () => const <SignInState>[
expect: () => <SignInState>[
SignInState(
email: validEmail,
password: validPassword,
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.valid,
),
],
@@ -98,58 +170,145 @@ void main() {
});
group('passwordChanged', () {
blocTest<SignInCubit, SignInState>(
blocTest<SignInCubit<int>, SignInState>(
'emits [invalid] when email/password are invalid',
build: () => SignInCubit(
authenticationRepository: authenticationRepository,
),
act: (cubit) => cubit.passwordChanged(invalidPasswordString),
expect: () => const <SignInState>[
expect: () => <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,
),
],
);
blocTest<SignInCubit, SignInState>(
blocTest<SignInCubit<int>, SignInState>(
'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(
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),
expect: () => const <SignInState>[
expect: () => <SignInState>[
SignInState(
email: validEmail,
password: validPassword,
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.valid,
),
],
);
});
group('logInWithCredentials', () {
blocTest<SignInCubit, SignInState>(
group('submit', () {
blocTest<SignInCubit<int>, SignInState>(
'does nothing when status is not validated',
build: () => SignInCubit(
authenticationRepository: authenticationRepository,
),
act: (cubit) => cubit.signInWithEmailAndPassword(),
act: (cubit) => cubit.submit(),
expect: () => const <SignInState>[],
);
blocTest<SignInCubit, SignInState>(
blocTest<SignInCubit<int>, SignInState>(
'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(
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,
email: validEmail,
password: validPassword,
),
act: (cubit) => cubit.signInWithEmailAndPassword(),
act: (cubit) => cubit.submit(),
verify: (_) {
verify(
() => authenticationRepository.signInWithEmailAndPassword(
@@ -160,33 +319,85 @@ void main() {
},
);
blocTest<SignInCubit, SignInState>(
blocTest<SignInCubit<int>, SignInState>(
'emits [submissionInProgress, submissionSuccess] '
'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(
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,
email: validEmail,
password: validPassword,
),
act: (cubit) => cubit.signInWithEmailAndPassword(),
expect: () => const <SignInState>[
act: (cubit) => cubit.submit(),
expect: () => <SignInState>[
SignInState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.submissionInProgress,
email: validEmail,
password: validPassword,
),
SignInState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.submissionSuccess,
email: validEmail,
password: validPassword,
)
],
);
blocTest<SignInCubit, SignInState>(
blocTest<SignInCubit<int>, SignInState>(
'emits [submissionInProgress, submissionFailure] '
'when signInWithEmailAndPassword fails',
setUp: () {
@@ -195,27 +406,77 @@ void main() {
email: any(named: 'email'),
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(
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,
email: validEmail,
password: validPassword,
),
act: (cubit) => cubit.signInWithEmailAndPassword(),
expect: () => const <SignInState>[
act: (cubit) => cubit.submit(),
expect: () => <SignInState>[
SignInState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.submissionInProgress,
email: validEmail,
password: validPassword,
),
SignInState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
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';
void main() {
const Email email = Email.dirty('email');
const Password password = Password.dirty('password');
final WyattForm form = WyattFormImpl(
[
FormInput(AuthFormField.email, const Email.pure()),
FormInput(AuthFormField.password, const Password.pure())
],
name: AuthFormName.signInForm,
);
group('SignInState', () {
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', () {
expect(const SignInState().copyWith(), const SignInState());
expect(
SignInState(form: form).copyWith(),
SignInState(form: form),
);
});
test('returns object with updated status when status is passed', () {
expect(
const SignInState().copyWith(status: FormStatus.pure),
const SignInState(),
);
});
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),
SignInState(form: form).copyWith(status: FormStatus.invalid),
SignInState(
form: form,
status: FormStatus.invalid,
),
);
});
});
@@ -17,163 +17,298 @@
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 {}
implements AuthenticationRepository<int> {}
class MockAuthenticationCubit extends Mock
implements AuthenticationCubit<void> {}
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 Email invalidEmail = Email.dirty(invalidEmailString);
const String validEmailString = 'test@gmail.com';
const Email validEmail = Email.dirty(validEmailString);
const String invalidPasswordString = 'invalid';
const Password invalidPassword = Password.dirty(invalidPasswordString);
const String validPasswordString = 't0pS3cret1234';
const Password validPassword = Password.dirty(validPasswordString);
group('SignUpCubit', () {
late AuthenticationRepository authenticationRepository;
late AuthenticationCubit<void> authenticationCubit;
final MockAccount account = MockAccount();
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(() {
authenticationRepository = MockAuthenticationRepository();
authenticationCubit = MockAuthenticationCubit();
formRepository = MockFormRepository();
when(
() => authenticationRepository.signUp(
email: any(named: 'email'),
password: any(named: 'password'),
),
).thenAnswer((_) async => 'uid');
).thenAnswer((_) async => Ok(account));
when(
() => authenticationCubit.start(),
).thenReturn(true);
() => authenticationRepository.formRepository,
).thenAnswer((_) => formRepository);
when(
() => authenticationCubit.stop(),
).thenReturn(true);
() => formRepository.accessForm(AuthFormName.signUpForm),
).thenAnswer((_) => form);
});
test('initial state is SignUpState', () {
expect(
SignUpCubit(
authenticationRepository: authenticationRepository,
formData: const FormData.empty(),
).state,
const SignUpState(data: FormData.empty()),
SignUpState(form: form),
);
});
group('emailChanged', () {
blocTest<SignUpCubit, SignUpState>(
blocTest<SignUpCubit<int>, SignUpState>(
'emits [invalid] when email/password are invalid',
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
formData: const FormData.empty(),
),
act: (cubit) => cubit.emailChanged(invalidEmailString),
expect: () => <SignUpState>[
const SignUpState(
email: invalidEmail,
SignUpState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(invalidEmailString),
),
FormInput(AuthFormField.password, const Password.pure())
],
name: AuthFormName.signUpForm,
),
status: FormStatus.invalid,
data: FormData.empty(),
),
],
);
blocTest<SignUpCubit, SignUpState>(
blocTest<SignUpCubit<int>, SignUpState>(
'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(
authenticationRepository: authenticationRepository,
formData: const FormData.empty(),
),
seed: () => const SignUpState(
password: validPassword,
data: FormData.empty(),
seed: () => SignUpState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.pure(),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signUpForm,
),
status: FormStatus.invalid,
),
act: (cubit) => cubit.emailChanged(validEmailString),
expect: () => <SignUpState>[
const SignUpState(
email: validEmail,
password: validPassword,
SignUpState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signUpForm,
),
status: FormStatus.valid,
data: FormData.empty(),
),
],
);
});
group('passwordChanged', () {
blocTest<SignUpCubit, SignUpState>(
blocTest<SignUpCubit<int>, SignUpState>(
'emits [invalid] when email/password are invalid',
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
formData: const FormData.empty(),
),
act: (cubit) => cubit.passwordChanged(invalidPasswordString),
expect: () => <SignUpState>[
const SignUpState(
password: invalidPassword,
SignUpState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.pure(),
),
FormInput(
AuthFormField.password,
const Password.dirty(invalidPasswordString),
)
],
name: AuthFormName.signUpForm,
),
status: FormStatus.invalid,
data: FormData.empty(),
),
],
);
blocTest<SignUpCubit, SignUpState>(
blocTest<SignUpCubit<int>, SignUpState>(
'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(
authenticationRepository: authenticationRepository,
formData: const FormData.empty(),
),
seed: () => const SignUpState(
email: validEmail,
data: FormData.empty(),
seed: () => SignUpState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.pure(),
)
],
name: AuthFormName.signUpForm,
),
status: FormStatus.invalid,
),
act: (cubit) => cubit.passwordChanged(validPasswordString),
expect: () => <SignUpState>[
const SignUpState(
email: validEmail,
password: validPassword,
SignUpState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signUpForm,
),
status: FormStatus.valid,
data: FormData.empty(),
),
],
);
});
group('signUpFormSubmitted', () {
blocTest<SignUpCubit, SignUpState>(
group('submit', () {
blocTest<SignUpCubit<int>, SignUpState>(
'does nothing when status is not validated',
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
formData: const FormData.empty(),
),
act: (cubit) => cubit.signUpFormSubmitted(),
act: (cubit) => cubit.submit(),
expect: () => const <SignUpState>[],
);
blocTest<SignUpCubit, SignUpState>(
'calls signUp with correct email/password/confirmedPassword',
blocTest<SignUpCubit<int>, SignUpState>(
'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(
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,
email: validEmail,
password: validPassword,
data: FormData.empty(),
),
act: (cubit) => cubit.signUpFormSubmitted(),
act: (cubit) => cubit.submit(),
verify: (_) {
verify(
() => authenticationRepository.signUp(
@@ -184,37 +319,85 @@ void main() {
},
);
blocTest<SignUpCubit, SignUpState>(
blocTest<SignUpCubit<int>, SignUpState>(
'emits [submissionInProgress, submissionSuccess] '
'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(
authenticationRepository: authenticationRepository,
formData: const FormData.empty(),
),
seed: () => const SignUpState(
status: FormStatus.valid,
email: validEmail,
password: validPassword,
data: FormData.empty(),
),
act: (cubit) => cubit.signUpFormSubmitted(),
expect: () => <SignUpState>[
const SignUpState(
status: FormStatus.submissionInProgress,
email: validEmail,
password: validPassword,
data: FormData.empty(),
seed: () => SignUpState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
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,
email: validEmail,
password: validPassword,
data: FormData.empty(),
)
],
);
blocTest<SignUpCubit, SignUpState>(
blocTest<SignUpCubit<int>, SignUpState>(
'emits [submissionInProgress, submissionFailure] '
'when signUp fails',
setUp: () {
@@ -223,31 +406,77 @@ void main() {
email: any(named: 'email'),
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(
authenticationRepository: authenticationRepository,
formData: const FormData.empty(),
),
seed: () => const SignUpState(
status: FormStatus.valid,
email: validEmail,
password: validPassword,
data: FormData.empty(),
),
act: (cubit) => cubit.signUpFormSubmitted(),
expect: () => <SignUpState>[
const SignUpState(
status: FormStatus.submissionInProgress,
email: validEmail,
password: validPassword,
data: FormData.empty(),
seed: () => SignUpState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
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,
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';
void main() {
const Email email = Email.dirty('email');
const String passwordString = 'password';
const Password password = Password.dirty(passwordString);
final WyattForm form = WyattFormImpl(
[
FormInput(AuthFormField.email, const Email.pure()),
FormInput(AuthFormField.password, const Password.pure())
],
name: AuthFormName.signInForm,
);
group('SignUpState', () {
test('supports value comparisons', () {
expect(
const SignUpState(
data: FormData.empty(),
),
const SignUpState(
data: FormData.empty(),
SignUpState(
form: form,
),
SignUpState(form: form),
);
});
test('returns same object when no properties are passed', () {
expect(
const SignUpState(
data: FormData.empty(),
).copyWith(),
const SignUpState(
data: FormData.empty(),
),
SignUpState(form: form).copyWith(),
SignUpState(form: form),
);
});
test('returns object with updated status when status is passed', () {
expect(
const SignUpState(
data: FormData.empty(),
).copyWith(status: FormStatus.pure),
const SignUpState(
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(),
),
],
),
SignUpState(form: form).copyWith(status: FormStatus.invalid),
SignUpState(
form: form,
status: FormStatus.invalid,
),
);
});
+24
View File
@@ -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"
]
}
+71
View File
@@ -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,
}
+8
View File
@@ -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
- Migrate crud_package in monorepo
@@ -1 +1,4 @@
include: package:wyatt_analysis/analysis_options.flutter.yaml
analyzer:
exclude: "!example/**"
+22 -2
View File
@@ -1,10 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
# This file should be version controlled.
version:
revision: 5464c5bac742001448fe4fc0597be939379f88ea
revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
channel: stable
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"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

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"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Some files were not shown because too many files have changed in this diff Show More