Compare commits

...
Author SHA1 Message Date
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
hugo 0a4a9c2feb chore(release): publish packages
- wyatt_form_bloc@0.1.0
 - wyatt_authentication_bloc@0.2.1+5
2022-11-10 13:43:55 -05:00
hugo 92f8b2916c Merge pull request 'auth/wyatt_arch_migration' (#25) from auth/wyatt_arch_migration into master
Reviewed-on: #25
2022-11-10 18:40:27 +00:00
hugo 3588c3241b chore(auth): update pubspec 2022-11-10 13:39:07 -05:00
hugo 9b87eb7ed9 chore(form): update pubspec 2022-11-10 13:38:55 -05:00
hugo 6802a56bfc doc(auth): update example router 2022-11-10 13:31:15 -05:00
hugo b2a9dac7c6 refactor(auth): rename local to cache auth data source 2022-11-10 13:31:00 -05:00
hugo c0a91d6437 feat!(auth): use form repository 2022-11-10 13:30:13 -05:00
hugo 59bd3edb0d feat!(auth): use new form features 2022-11-10 13:29:30 -05:00
hugo 0c920e8245 feat(form): clean and add toString and equality 2022-11-10 13:24:29 -05:00
hugo ae3711a136 fix!(form): fix states with required form 2022-11-10 13:24:00 -05:00
hugo f031b3cbf5 fix: make cubit custom implementation simpler 2022-11-10 01:28:30 -05:00
hugo 68a582c3ad refactor(form): refactor simple example 2022-11-10 01:06:25 -05:00
hugo 7a056ac38e feat!(form): add repository feature, and controller for inputs 2022-11-10 01:05:12 -05:00
hugo b89ef3de8a feat!(form): migrate to wyatt architecture 2022-11-09 22:35:41 -05:00
hugo 79fdd3c837 doc(auth): update example 2022-11-09 01:48:32 -05:00
hugo 0d00a67b7c fix: rework presentation features 2022-11-09 01:47:57 -05:00
hugo d2d8acdafb fix: rework core and entities 2022-11-09 01:47:37 -05:00
hugo 2f572db4a4 feat: add data sources 2022-11-09 01:47:09 -05:00
hugo 3972c56e55 feat: rewrite authentication repository 2022-11-09 01:46:28 -05:00
hugo 0bc2aa3512 feat: update dependencies 2022-11-09 01:44:52 -05:00
194 changed files with 7042 additions and 2736 deletions
+89
View File
@@ -3,6 +3,95 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## 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
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`wyatt_form_bloc` - `v0.1.0`](#wyatt_form_bloc---v010)
- [`wyatt_authentication_bloc` - `v0.2.1+5`](#wyatt_authentication_bloc---v0215)
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+5`
---
#### `wyatt_form_bloc` - `v0.1.0`
- **REFACTOR**: refactor simple example.
- **FIX**: make cubit custom implementation simpler.
- **FEAT**: clean and add toString and equality.
## 2022-11-08
### 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.
@@ -1,3 +1,23 @@
## 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.
## 0.2.1+4
- 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
@@ -38,7 +38,7 @@ dependencies:
wyatt_form_bloc:
git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_form_bloc-v0.0.6
ref: wyatt_form_bloc-v0.1.0+1
path: packages/wyatt_form_bloc
# The following adds the Cupertino Icons font to your application.
@@ -4,7 +4,7 @@
# This file should be version controlled.
version:
revision: f1875d570e39de09040c8f79aa13cc56baab8db1
revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
channel: stable
project_type: app
@@ -13,11 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
- platform: android
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
create_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
base_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
- platform: ios
create_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
base_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
# User provided section
@@ -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,142 @@
PODS:
- AppAuth (1.6.0):
- AppAuth/Core (= 1.6.0)
- AppAuth/ExternalUserAgent (= 1.6.0)
- AppAuth/Core (1.6.0)
- AppAuth/ExternalUserAgent (1.6.0):
- AppAuth/Core
- FBAEMKit (14.1.0):
- FBSDKCoreKit_Basics (= 14.1.0)
- FBSDKCoreKit (14.1.0):
- FBAEMKit (= 14.1.0)
- FBSDKCoreKit_Basics (= 14.1.0)
- FBSDKCoreKit_Basics (14.1.0)
- FBSDKLoginKit (14.1.0):
- FBSDKCoreKit (= 14.1.0)
- Firebase/Auth (10.0.0):
- Firebase/CoreOnly
- FirebaseAuth (~> 10.0.0)
- Firebase/CoreOnly (10.0.0):
- FirebaseCore (= 10.0.0)
- firebase_auth (4.1.1):
- Firebase/Auth (= 10.0.0)
- firebase_core
- Flutter
- firebase_core (2.1.1):
- Firebase/CoreOnly (= 10.0.0)
- Flutter
- FirebaseAuth (10.0.0):
- FirebaseCore (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.8)
- GoogleUtilities/Environment (~> 7.8)
- GTMSessionFetcher/Core (~> 2.1)
- FirebaseCore (10.0.0):
- FirebaseCoreInternal (~> 10.0)
- GoogleUtilities/Environment (~> 7.8)
- GoogleUtilities/Logger (~> 7.8)
- FirebaseCoreInternal (10.1.0):
- "GoogleUtilities/NSData+zlib (~> 7.8)"
- Flutter (1.0.0)
- flutter_facebook_auth (4.4.1):
- FBSDKLoginKit (= 14.1.0)
- Flutter
- google_sign_in_ios (0.0.1):
- Flutter
- GoogleSignIn (~> 6.2)
- GoogleSignIn (6.2.4):
- AppAuth (~> 1.5)
- GTMAppAuth (~> 1.3)
- GTMSessionFetcher/Core (< 3.0, >= 1.1)
- GoogleUtilities/AppDelegateSwizzler (7.8.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (7.8.0):
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/Logger (7.8.0):
- GoogleUtilities/Environment
- GoogleUtilities/Network (7.8.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (7.8.0)"
- GoogleUtilities/Reachability (7.8.0):
- GoogleUtilities/Logger
- GTMAppAuth (1.3.1):
- AppAuth/Core (~> 1.6)
- GTMSessionFetcher/Core (< 3.0, >= 1.5)
- GTMSessionFetcher/Core (2.1.0)
- PromisesObjC (2.1.1)
- sign_in_with_apple (0.0.1):
- Flutter
- twitter_login (0.0.1):
- Flutter
DEPENDENCIES:
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- Flutter (from `Flutter`)
- flutter_facebook_auth (from `.symlinks/plugins/flutter_facebook_auth/ios`)
- google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/ios`)
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
- twitter_login (from `.symlinks/plugins/twitter_login/ios`)
SPEC REPOS:
trunk:
- AppAuth
- FBAEMKit
- FBSDKCoreKit
- FBSDKCoreKit_Basics
- FBSDKLoginKit
- Firebase
- FirebaseAuth
- FirebaseCore
- FirebaseCoreInternal
- GoogleSignIn
- GoogleUtilities
- GTMAppAuth
- GTMSessionFetcher
- PromisesObjC
EXTERNAL SOURCES:
firebase_auth:
:path: ".symlinks/plugins/firebase_auth/ios"
firebase_core:
:path: ".symlinks/plugins/firebase_core/ios"
Flutter:
:path: Flutter
flutter_facebook_auth:
:path: ".symlinks/plugins/flutter_facebook_auth/ios"
google_sign_in_ios:
:path: ".symlinks/plugins/google_sign_in_ios/ios"
sign_in_with_apple:
:path: ".symlinks/plugins/sign_in_with_apple/ios"
twitter_login:
:path: ".symlinks/plugins/twitter_login/ios"
SPEC CHECKSUMS:
AppAuth: 8fca6b5563a5baef2c04bee27538025e4ceb2add
FBAEMKit: a899515e45476027f73aef377b5cffadcd56ca3a
FBSDKCoreKit: 24f8bc8d3b5b2a8c5c656a1329492a12e8efa792
FBSDKCoreKit_Basics: 6e578c9bdc7aa1365dbbbde633c9ebb536bcaa98
FBSDKLoginKit: 787de205d524c3a4b17d527916f1d066e4361660
Firebase: 1b810f3d0c0532e27a48f1961f8c0400a668a2cf
firebase_auth: dd33e93fce72a1c72040f7380dacf06e89db5705
firebase_core: 5c0bb0ca7d0e70480a68a6e9ad9bf55d1edd5305
FirebaseAuth: 493382cf533cc45e2862b00e9aa4cfe4c98daf71
FirebaseCore: 97f48a3a567a72b8d4daa0f03c3aadb78df4e995
FirebaseCoreInternal: 96d75228e10fd369564da51bd898414eb0f54df5
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_facebook_auth: 361ac7a57263ebf327f26089507ead0d66558ee8
google_sign_in_ios: 4f85eb9f937450765c8573bb85fd8cd6a5af675c
GoogleSignIn: 5651ce3a61e56ca864160e79b484cd9ed3f49b7a
GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7
GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd
GTMSessionFetcher: ffbb25ec00ebcb5201adab0a56d808f6f1902d9f
PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb
sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440
twitter_login: 2794db69b7640681171b17b3c2c84ad9dfb4a57f
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 */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
69F3BBCD5DEB05A456F6B74F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B0A061B2E527F311149C3581 /* Pods_Runner.framework */; };
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 */
07E343497E760D6A90A105C5 /* 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>"; };
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>"; };
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>"; };
B0A061B2E527F311149C3581 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F0D7945BAE0BEA457137ED73 /* 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>"; };
F9340E3A859C31E59380BD0F /* 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>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
69F3BBCD5DEB05A456F6B74F /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
66B357379C2757D2844F12BB /* Pods */ = {
isa = PBXGroup;
children = (
F0D7945BAE0BEA457137ED73 /* Pods-Runner.debug.xcconfig */,
F9340E3A859C31E59380BD0F /* Pods-Runner.release.xcconfig */,
07E343497E760D6A90A105C5 /* 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 */,
66B357379C2757D2844F12BB /* Pods */,
BC1E25CE0DADDF7B7201CCF8 /* 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>";
};
BC1E25CE0DADDF7B7201CCF8 /* Frameworks */ = {
isa = PBXGroup;
children = (
B0A061B2E527F311149C3581 /* 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 = (
92D20EC4971D81AAB7475BDB /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
DEBBE8DDE939288865FFA279 /* [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";
};
92D20EC4971D81AAB7475BDB /* [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;
};
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";
};
DEBBE8DDE939288865FFA279 /* [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;
};
/* 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.exampleRouter;
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.exampleRouter;
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.exampleRouter;
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"
}
}
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
@@ -0,0 +1,51 @@
<?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>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Example Router</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>example_router</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"
@@ -0,0 +1,7 @@
{
"file_generated_by": "FlutterFire CLI",
"purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory",
"GOOGLE_APP_ID": "1:136771801992:ios:bcdca68d2b7d227097203d",
"FIREBASE_PROJECT_ID": "tchat-beta",
"GCM_SENDER_ID": "136771801992"
}
@@ -3,23 +3,24 @@
// -----
// File: bootstrap.dart
// Created Date: 19/08/2022 15:05:17
// Last Modified: 19/08/2022 15:21:47
// Last Modified: Wed Nov 09 2022
// -----
// Copyright (c) 2022
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';
import 'package:go_router/go_router.dart';
Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
await runZonedGuarded(
() async {
WidgetsFlutterBinding.ensureInitialized();
Bloc.observer = AppBlocObserver();
FlutterError.onError = (details) {
debugPrint(details.toString());
@@ -29,9 +30,7 @@ Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
options: DefaultFirebaseOptions.currentPlatform,
);
GoRouter.setUrlPathStrategy(UrlPathStrategy.path);
Bloc.observer = AppBlocObserver();
await GetItInitializer.init();
runApp(await builder());
},
@@ -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: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;
abstract class GetItInitializer {
static Future<void> init() async {
getIt
..registerLazySingleton<AuthenticationRemoteDataSource>(
() => 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,21 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: forms.dart
// Created Date: 19/08/2022 12:00:31
// Last Modified: 19/08/2022 16:35:52
// -----
// Copyright (c) 2022
import 'package:example_router/core/constants/form_field.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class Forms {
static FormData getNormalData() => const FormData([
FormInput(
AppFormField.confirmedPassword,
ConfirmedPassword.pure(),
metadata: FormInputMetadata<void>(export: false),
),
]);
}
@@ -24,12 +24,12 @@ class DefaultFirebaseOptions {
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for ios - '
'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 - '
@@ -52,12 +52,15 @@ class DefaultFirebaseOptions {
}
}
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk',
appId: '1:136771801992:android:ac3cfeb99fb0763e97203d',
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,22 +3,58 @@
// -----
// File: app.dart
// Created Date: 19/08/2022 12:05:38
// Last Modified: Fri Aug 26 2022
// Last Modified: Thu Nov 10 2022
// -----
// Copyright (c) 2022
import 'dart:async';
import 'dart:math';
import 'package:example_router/core/constants/form_field.dart';
import 'package:example_router/core/dependency_injection/get_it.dart';
import 'package:example_router/core/routes/router.dart';
import 'package:example_router/core/utils/forms.dart';
import 'package:example_router/presentation/features/home/home_page.dart';
import 'package:example_router/presentation/features/welcome/welcome_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.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';
import 'package:wyatt_architecture/wyatt_architecture.dart';
FutureResult<int?> onSignUpSuccess(
Account? account,
WyattForm form,
) async {
const id = -1;
final confirmedPassword =
form.valueOf<String?>(AppFormField.confirmedPassword);
debugPrint(
'onSignUpSuccess: $account, generatedId: $id, extraFormData: $confirmedPassword');
return const Ok<int, AppException>(id);
}
FutureResult<int?> onAccountChanges(Account? account) async {
final id = Random().nextInt(1000);
debugPrint('onAccountChanges: $account, generatedId: $id');
return Ok<int, AppException>(id);
}
class App extends StatelessWidget {
final AuthenticationRepository authenticationRepository =
AuthenticationRepositoryFirebase();
final AuthenticationRepository<int> authenticationRepository =
AuthenticationRepositoryImpl(
authenticationCacheDataSource: getIt<AuthenticationCacheDataSource<int>>(),
authenticationRemoteDataSource: getIt<AuthenticationRemoteDataSource>(),
onSignUpSuccess: onSignUpSuccess,
onAuthChange: onAccountChanges,
extraSignUpInputs: [
FormInput(
AppFormField.confirmedPassword,
const ConfirmedPassword.pure(),
metadata: const FormInputMetadata<void>(export: false),
),
],
);
App({Key? key}) : super(key: key);
@@ -26,13 +62,8 @@ class App extends StatelessWidget {
Widget build(BuildContext context) {
AuthenticationState? previous;
final AuthenticationCubit authenticationCubit = AuthenticationCubit(
authenticationRepository: authenticationRepository,
onAuthSuccess: (user) async {
debugPrint(user.toString());
return {};
},
);
final AuthenticationCubit<int> authenticationCubit =
AuthenticationCubit(authenticationRepository: authenticationRepository);
final GoRouter router = GoRouter(
initialLocation: '/',
@@ -42,7 +73,7 @@ class App extends StatelessWidget {
color: Colors.red,
),
refreshListenable: GoRouterRefreshStream(authenticationCubit.stream),
redirect: (state) {
redirect: (context, state) {
final authState = authenticationCubit.state;
if (authState != previous) {
@@ -59,13 +90,12 @@ class App extends StatelessWidget {
if (isOnboarding) {
return null;
} else {
return state.namedLocation(WelcomePage.pageName);
return '/';
}
} else {
final email = authState.user?.email;
debugPrint('Logged as: $email');
debugPrint('Logged');
if (isOnboarding) {
return state.namedLocation(HomePage.pageName);
return '/home';
} else {
return null;
}
@@ -83,20 +113,15 @@ class App extends StatelessWidget {
],
child: MultiBlocProvider(
providers: [
BlocProvider<AuthenticationCubit>.value(
value: authenticationCubit..init(),
BlocProvider<AuthenticationCubit<int>>.value(
value: authenticationCubit,
),
BlocProvider<SignUpCubit>(
BlocProvider<SignUpCubit<int>>(
create: (_) => SignUpCubit(
authenticationRepository: authenticationRepository,
formData: Forms.getNormalData(),
onSignUpSuccess: (state, uid) async {
debugPrint(state.toString());
debugPrint(uid);
},
),
),
BlocProvider<SignInCubit>(
BlocProvider<SignInCubit<int>>(
create: (_) => SignInCubit(
authenticationRepository: authenticationRepository,
),
@@ -105,11 +130,26 @@ class App extends StatelessWidget {
child: MaterialApp.router(
title: 'Demo Authentication',
debugShowCheckedModeBanner: false,
routerDelegate: router.routerDelegate,
routeInformationParser: router.routeInformationParser,
routeInformationProvider: router.routeInformationProvider,
routerConfig: router,
),
),
);
}
}
class GoRouterRefreshStream extends ChangeNotifier {
GoRouterRefreshStream(Stream<dynamic> stream) {
notifyListeners();
_subscription = stream.asBroadcastStream().listen(
(dynamic _) => notifyListeners(),
);
}
late final StreamSubscription<dynamic> _subscription;
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
}
@@ -3,7 +3,7 @@
// -----
// File: home_page.dart
// Created Date: 19/08/2022 14:38:24
// Last Modified: 19/08/2022 16:12:22
// Last Modified: Wed Nov 09 2022
// -----
// Copyright (c) 2022
@@ -25,7 +25,7 @@ class HomePage extends StatelessWidget {
title: const Text('Home'),
actions: [
IconButton(
onPressed: () => context.read<AuthenticationCubit>().logOut(),
onPressed: () => context.read<AuthenticationCubit<int>>().signOut(),
icon: const Icon(Icons.logout_rounded))
],
),
@@ -34,11 +34,12 @@ class HomePage extends StatelessWidget {
child: SingleChildScrollView(
child: Column(
children: [
BlocBuilder<AuthenticationCubit, AuthenticationState>(
builder: (context, state) {
final email = state.user?.email;
return Text('Logged as $email');
},
AuthenticationBuilder<int>(
authenticated: (context, accountWrapper) =>
Text('Logged as ${accountWrapper.account?.email} | GeneratedId is ${accountWrapper.data}'),
unauthenticated: (context) =>
const Text('Not logged (unauthenticated)'),
unknown: (context) => const Text('Not logged (unknown)'),
),
const SizedBox(
height: 8,
@@ -3,30 +3,30 @@
// -----
// File: sign_in_form.dart
// Created Date: 19/08/2022 15:24:37
// Last Modified: 19/08/2022 16:35:01
// 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';
class _EmailInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
buildWhen: (previous, current) => previous.email != current.email,
builder: (context, state) {
return InputBuilder<SignInCubit<int>>(
field: AuthFormField.email,
builder: ((context, cubit, state, field, inputValid) {
return TextField(
onChanged: (email) => context.read<SignInCubit>().emailChanged(email),
onChanged: (email) => cubit.emailChanged(email),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email',
helperText: '',
errorText: state.email.invalid ? 'Invalid email' : null,
errorText: !inputValid ? 'Invalid email' : null,
),
);
},
}),
);
}
}
@@ -34,21 +34,19 @@ class _EmailInput extends StatelessWidget {
class _PasswordInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
buildWhen: (previous, current) => previous.password != current.password,
builder: (context, state) {
return InputBuilder<SignInCubit<int>>(
field: AuthFormField.password,
builder: ((context, cubit, state, field, inputValid) {
return TextField(
onChanged: (password) {
context.read<SignInCubit>().passwordChanged(password);
},
onChanged: (pwd) => cubit.passwordChanged(pwd),
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
helperText: '',
errorText: state.password.invalid ? 'Invalid password' : null,
errorText: !inputValid ? 'Invalid password' : null,
),
);
},
}),
);
}
}
@@ -56,18 +54,15 @@ class _PasswordInput extends StatelessWidget {
class _SignInButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
builder: (context, state) {
return state.status.isSubmissionInProgress
return SubmitBuilder<SignInCubit<int>>(
builder: ((context, cubit, status) {
return status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: state.status.isValidated
? () =>
context.read<SignInCubit>().signInWithEmailAndPassword()
: null,
onPressed: status.isValidated ? () => cubit.submit() : null,
child: const Text('Sign in'),
);
},
}),
);
}
}
@@ -77,18 +72,12 @@ class SignInForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<SignInCubit, SignInState>(
listener: (context, state) {
if (state.status.isSubmissionSuccess) {
Navigator.of(context).pop();
} else if (state.status.isSubmissionFailure) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'Sign 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,32 +3,31 @@
// -----
// File: sign_up_form.dart
// Created Date: 19/08/2022 14:41:08
// Last Modified: Fri Aug 26 2022
// Last Modified: Fri Nov 11 2022
// -----
// Copyright (c) 2022
import 'package:example_router/core/constants/form_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart' hide FormField;
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class _EmailInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
buildWhen: (previous, current) => previous.email != current.email,
builder: (context, state) {
return InputBuilder<SignUpCubit<int>>(
field: AuthFormField.email,
builder: ((context, cubit, state, field, inputValid) {
return TextField(
onChanged: (email) => context.read<SignUpCubit>().emailChanged(email),
onChanged: (email) => cubit.emailChanged(email),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email',
helperText: '',
errorText: state.email.invalid ? 'Invalid email' : null,
errorText: !inputValid ? 'Invalid email' : null,
),
);
},
}),
);
}
}
@@ -36,33 +35,27 @@ class _EmailInput extends StatelessWidget {
class _PasswordInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
buildWhen: (previous, current) => previous.password != current.password,
builder: (context, state) {
return InputBuilder<SignUpCubit<int>>(
field: AuthFormField.password,
builder: ((context, cubit, state, field, inputValid) {
return TextField(
onChanged: (password) {
context.read<SignUpCubit>().passwordChanged(password);
context.read<SignUpCubit>().dataChanged(
AppFormField.confirmedPassword,
ConfirmedPassword.dirty(
password: password,
value: context
.read<SignUpCubit>()
.state
.data
.valueOf<String>(
AppFormField.confirmedPassword),
),
);
onChanged: (pwd) {
cubit.passwordChanged(pwd);
cubit.dataChanged(
AppFormField.confirmedPassword,
ConfirmedPassword.dirty(
password: pwd,
value: state.form
.valueOf<String?>(AppFormField.confirmedPassword)));
},
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
helperText: '',
errorText: state.password.invalid ? 'Invalid password' : null,
errorText: !inputValid ? 'Invalid password' : null,
),
);
},
}),
);
}
}
@@ -70,28 +63,27 @@ class _PasswordInput extends StatelessWidget {
class _ConfirmPasswordInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return InputBuilder<SignUpCubit<int>>(
field: AppFormField.confirmedPassword,
builder: ((context, cubit, state, field, inputValid) {
return TextField(
onChanged: (confirmPassword) => context
.read<SignUpCubit>()
.dataChanged(
AppFormField.confirmedPassword,
ConfirmedPassword.dirty(
password: context.read<SignUpCubit>().state.password.value,
value: confirmPassword,
),
),
onChanged: (pwd) {
cubit.dataChanged(
field,
ConfirmedPassword.dirty(
password:
state.form.valueOf<String?>(AuthFormField.password) ?? '',
value: pwd),
);
},
obscureText: true,
decoration: InputDecoration(
labelText: 'Confirm password',
helperText: '',
errorText: state.data.isNotValid(AppFormField.confirmedPassword)
? 'Passwords do not match'
: null,
errorText: !inputValid ? 'Passwords do not match' : null,
),
);
},
}),
);
}
}
@@ -99,17 +91,15 @@ class _ConfirmPasswordInput extends StatelessWidget {
class _SignUpButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return state.status.isSubmissionInProgress
return SubmitBuilder<SignUpCubit<int>>(
builder: ((context, cubit, status) {
return status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: state.status.isValidated
? () => context.read<SignUpCubit>().signUpFormSubmitted()
: null,
onPressed: status.isValidated ? () => cubit.submit() : null,
child: const Text('Sign up'),
);
},
}),
);
}
}
@@ -119,31 +109,25 @@ class SignUpForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<SignUpCubit, SignUpState>(
listener: (context, state) {
if (state.status.isSubmissionSuccess) {
Navigator.of(context).pop();
} else if (state.status.isSubmissionFailure) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'Sign Up Failure')),
);
}
},
child: 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(),
],
),
));
}
}
@@ -3,7 +3,7 @@
// -----
// File: sub_page.dart
// Created Date: 19/08/2022 16:10:05
// Last Modified: 19/08/2022 16:10:44
// Last Modified: Wed Nov 09 2022
// -----
// Copyright (c) 2022
@@ -23,7 +23,7 @@ class SubPage extends StatelessWidget {
title: const Text('Sub'),
actions: [
IconButton(
onPressed: () => context.read<AuthenticationCubit>().logOut(),
onPressed: () => context.read<AuthenticationCubit<int>>().signOut(),
icon: const Icon(Icons.logout_rounded))
],
),
@@ -3,16 +3,14 @@
// -----
// File: welcome_page.dart
// Created Date: 19/08/2022 12:33:21
// Last Modified: 19/08/2022 15:56:05
// Last Modified: Wed Nov 09 2022
// -----
// Copyright (c) 2022
import 'package:example_router/presentation/features/sign_in/sign_in_page.dart';
import 'package:example_router/presentation/features/sign_up/sign_up_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
class WelcomePage extends StatelessWidget {
const WelcomePage({Key? key}) : super(key: key);
@@ -24,12 +22,6 @@ class WelcomePage extends StatelessWidget {
return Scaffold(
appBar: AppBar(
title: const Text('Welcome'),
actions: [
IconButton(
onPressed: () => context.read<AuthenticationCubit>().changeStatus(
context.read<AuthenticationCubit>().state.user!),
icon: const Icon(Icons.refresh_rounded))
],
),
body: SingleChildScrollView(
child: Column(
@@ -30,21 +30,29 @@ dependencies:
flutter:
sdk: flutter
go_router: ^4.2.8
firebase_core: ^1.21.0
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.0.6
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:
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.2
cupertino_icons: ^1.0.5
dev_dependencies:
flutter_test:
@@ -14,16 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
enum SetOperation {
/// Replace entire set with new set.
replace,
/// Keep common elements between sets.
intersection,
/// Remove common elements between sets.
difference,
/// Add new elements to set.
union
abstract class AuthFormField {
static const email = 'wyattEmailField';
static const password = 'wyattPasswordField';
}
@@ -0,0 +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/>.
abstract class AuthFormName {
static const String signUpForm = 'wyattSignUpForm';
static const String signInForm = 'wyattSignInForm';
static const String passwordResetForm = 'wyattPasswordResetForm';
}
@@ -0,0 +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 'constants/form_field.dart';
export 'constants/form_name.dart';
export 'enums/enums.dart';
export 'exceptions/exceptions.dart';
export 'utils/utils.dart';
@@ -0,0 +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/>.
enum AuthenticationStatus {
unknown,
authenticated,
unauthenticated,
}
@@ -0,0 +1,17 @@
// 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_status.dart';
@@ -14,13 +14,21 @@
// 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 AuthenticationFailureInterface implements Exception {
String code;
String message;
import 'package:wyatt_architecture/wyatt_architecture.dart';
AuthenticationFailureInterface(this.code, this.message);
part 'exceptions_firebase.dart';
abstract class AuthenticationFailureInterface extends AppException
implements Exception {
String code;
String msg;
@override
String get message => msg;
AuthenticationFailureInterface(this.code, this.msg);
AuthenticationFailureInterface.fromCode(this.code)
: message = 'An unknown error occurred.';
: msg = 'An unknown error occurred.';
}
/// {@template apply_action_code_failure}
@@ -29,7 +37,7 @@ abstract class AuthenticationFailureInterface implements Exception {
abstract class ApplyActionCodeFailureInterface
extends AuthenticationFailureInterface {
/// {@macro apply_action_code_failure}
ApplyActionCodeFailureInterface(super.code, super.message);
ApplyActionCodeFailureInterface(super.code, super.msg);
/// {@macro apply_action_code_failure}
ApplyActionCodeFailureInterface.fromCode(super.code) : super.fromCode();
@@ -41,7 +49,7 @@ abstract class ApplyActionCodeFailureInterface
abstract class SignUpWithEmailAndPasswordFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_up_with_email_and_password_failure}
SignUpWithEmailAndPasswordFailureInterface(super.code, super.message);
SignUpWithEmailAndPasswordFailureInterface(super.code, super.msg);
/// {@macro sign_up_with_email_and_password_failure}
SignUpWithEmailAndPasswordFailureInterface.fromCode(super.code)
@@ -54,7 +62,7 @@ abstract class SignUpWithEmailAndPasswordFailureInterface
abstract class FetchSignInMethodsForEmailFailureInterface
extends AuthenticationFailureInterface {
/// {@macro fetch_sign_in_methods_failure}
FetchSignInMethodsForEmailFailureInterface(super.code, super.message);
FetchSignInMethodsForEmailFailureInterface(super.code, super.msg);
/// {@macro fetch_sign_in_methods_failure}
FetchSignInMethodsForEmailFailureInterface.fromCode(super.code)
@@ -67,7 +75,7 @@ abstract class FetchSignInMethodsForEmailFailureInterface
abstract class SignInWithCredentialFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_in_with_credential_failure}
SignInWithCredentialFailureInterface(super.code, super.message);
SignInWithCredentialFailureInterface(super.code, super.msg);
/// {@macro sign_in_with_credential_failure}
SignInWithCredentialFailureInterface.fromCode(super.code) : super.fromCode();
@@ -79,7 +87,7 @@ abstract class SignInWithCredentialFailureInterface
abstract class SignInAnonymouslyFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_in_anonymously_failure}
SignInAnonymouslyFailureInterface(super.code, super.message);
SignInAnonymouslyFailureInterface(super.code, super.msg);
/// {@macro sign_in_anonymously_failure}
SignInAnonymouslyFailureInterface.fromCode(super.code) : super.fromCode();
@@ -91,7 +99,7 @@ abstract class SignInAnonymouslyFailureInterface
abstract class SignInWithGoogleFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_in_with_google_failure}
SignInWithGoogleFailureInterface(super.code, super.message);
SignInWithGoogleFailureInterface(super.code, super.msg);
/// {@macro sign_in_with_google_failure}
SignInWithGoogleFailureInterface.fromCode(super.code) : super.fromCode();
@@ -103,7 +111,7 @@ abstract class SignInWithGoogleFailureInterface
abstract class SignInWithFacebookFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_in_with_facebook_failure}
SignInWithFacebookFailureInterface(super.code, super.message);
SignInWithFacebookFailureInterface(super.code, super.msg);
/// {@macro sign_in_with_facebook_failure}
SignInWithFacebookFailureInterface.fromCode(super.code) : super.fromCode();
@@ -115,7 +123,7 @@ abstract class SignInWithFacebookFailureInterface
abstract class SignInWithAppleFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_in_with_apple_failure}
SignInWithAppleFailureInterface(super.code, super.message);
SignInWithAppleFailureInterface(super.code, super.msg);
/// {@macro sign_in_with_apple_failure}
SignInWithAppleFailureInterface.fromCode(super.code) : super.fromCode();
@@ -127,7 +135,7 @@ abstract class SignInWithAppleFailureInterface
abstract class SignInWithTwitterFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_in_with_twitter_failure}
SignInWithTwitterFailureInterface(super.code, super.message);
SignInWithTwitterFailureInterface(super.code, super.msg);
/// {@macro sign_in_with_twitter_failure}
SignInWithTwitterFailureInterface.fromCode(super.code) : super.fromCode();
@@ -139,7 +147,7 @@ abstract class SignInWithTwitterFailureInterface
abstract class SignInWithEmailLinkFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_in_with_email_link_failure}
SignInWithEmailLinkFailureInterface(super.code, super.message);
SignInWithEmailLinkFailureInterface(super.code, super.msg);
/// {@macro sign_in_with_email_link_failure}
SignInWithEmailLinkFailureInterface.fromCode(super.code) : super.fromCode();
@@ -151,7 +159,7 @@ abstract class SignInWithEmailLinkFailureInterface
abstract class SignInWithEmailAndPasswordFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_in_with_email_and_password_failure}
SignInWithEmailAndPasswordFailureInterface(super.code, super.message);
SignInWithEmailAndPasswordFailureInterface(super.code, super.msg);
/// {@macro sign_in_with_email_and_password_failure}
SignInWithEmailAndPasswordFailureInterface.fromCode(super.code)
@@ -164,7 +172,7 @@ abstract class SignInWithEmailAndPasswordFailureInterface
abstract class SendEmailVerificationFailureInterface
extends AuthenticationFailureInterface {
/// {@macro send_email_verification_failure}
SendEmailVerificationFailureInterface(super.code, super.message);
SendEmailVerificationFailureInterface(super.code, super.msg);
/// {@macro send_email_verification_failure}
SendEmailVerificationFailureInterface.fromCode(super.code) : super.fromCode();
@@ -176,7 +184,7 @@ abstract class SendEmailVerificationFailureInterface
abstract class SendPasswordResetEmailFailureInterface
extends AuthenticationFailureInterface {
/// {@macro send_password_reset_email_failure}
SendPasswordResetEmailFailureInterface(super.code, super.message);
SendPasswordResetEmailFailureInterface(super.code, super.msg);
/// {@macro send_password_reset_email_failure}
SendPasswordResetEmailFailureInterface.fromCode(super.code)
@@ -189,7 +197,7 @@ abstract class SendPasswordResetEmailFailureInterface
abstract class SendSignInLinkEmailFailureInterface
extends AuthenticationFailureInterface {
/// {@macro send_sign_in_link_email_failure}
SendSignInLinkEmailFailureInterface(super.code, super.message);
SendSignInLinkEmailFailureInterface(super.code, super.msg);
/// {@macro send_sign_in_link_email_failure}
SendSignInLinkEmailFailureInterface.fromCode(super.code) : super.fromCode();
@@ -201,7 +209,7 @@ abstract class SendSignInLinkEmailFailureInterface
abstract class ConfirmPasswordResetFailureInterface
extends AuthenticationFailureInterface {
/// {@macro confirm_password_reset_failure}
ConfirmPasswordResetFailureInterface(super.code, super.message);
ConfirmPasswordResetFailureInterface(super.code, super.msg);
/// {@macro confirm_password_reset_failure}
ConfirmPasswordResetFailureInterface.fromCode(super.code) : super.fromCode();
@@ -213,7 +221,7 @@ abstract class ConfirmPasswordResetFailureInterface
abstract class VerifyPasswordResetCodeFailureInterface
extends AuthenticationFailureInterface {
/// {@macro verify_password_reset_code_failure}
VerifyPasswordResetCodeFailureInterface(super.code, super.message);
VerifyPasswordResetCodeFailureInterface(super.code, super.msg);
/// {@macro verify_password_reset_code_failure}
VerifyPasswordResetCodeFailureInterface.fromCode(super.code)
@@ -225,7 +233,7 @@ abstract class VerifyPasswordResetCodeFailureInterface
/// {@endtemplate}
abstract class RefreshFailureInterface extends AuthenticationFailureInterface {
/// {@macro refresh_failure}
RefreshFailureInterface(super.code, super.message);
RefreshFailureInterface(super.code, super.msg);
/// {@macro refresh_failure}
RefreshFailureInterface.fromCode(super.code) : super.fromCode();
@@ -236,8 +244,15 @@ abstract class RefreshFailureInterface extends AuthenticationFailureInterface {
/// {@endtemplate}
abstract class SignOutFailureInterface extends AuthenticationFailureInterface {
/// {@macro sign_out_failure}
SignOutFailureInterface(super.code, super.message);
SignOutFailureInterface(super.code, super.msg);
/// {@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();
}
@@ -14,133 +14,130 @@
// 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:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart';
part of 'exceptions.dart';
class ApplyActionCodeFailureFirebase extends ApplyActionCodeFailureInterface {
ApplyActionCodeFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
ApplyActionCodeFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
ApplyActionCodeFailureFirebase.fromCode(String code) : super.fromCode(code) {
switch (code) {
case 'expired-action-code':
message = 'Action code has expired.';
msg = 'Action code has expired.';
break;
case 'invalid-action-code':
message = 'Action code is invalid.';
msg = 'Action code is invalid.';
break;
case 'user-disabled':
message =
'This user has been disabled. Please contact support for help.';
msg = 'This user has been disabled. Please contact support for help.';
break;
case 'user-not-found':
message = 'Email is not found, please create an account.';
msg = 'Email is not found, please create an account.';
break;
default:
this.code = 'unknown';
message = 'An unknown error occurred.';
msg = 'An unknown error occurred.';
}
}
}
class SignUpWithEmailAndPasswordFailureFirebase
extends SignUpWithEmailAndPasswordFailureInterface {
SignUpWithEmailAndPasswordFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SignUpWithEmailAndPasswordFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignUpWithEmailAndPasswordFailureFirebase.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'invalid-email':
message = 'The email address is badly formatted.';
msg = 'The email address is badly formatted.';
break;
case 'user-disabled':
message =
'This user has been disabled. Please contact support for help.';
msg = 'This user has been disabled. Please contact support for help.';
break;
case 'email-already-in-use':
message = 'An account already exists for that email.';
msg = 'An account already exists for that email.';
break;
case 'operation-not-allowed':
message = 'Operation is not allowed. Please contact support.';
msg = 'Operation is not allowed. Please contact support.';
break;
case 'weak-password':
message = 'Please enter a stronger password.';
msg = 'Please enter a stronger password.';
break;
default:
this.code = 'unknown';
message = 'An unknown error occurred.';
msg = 'An unknown error occurred.';
}
}
}
class FetchSignInMethodsForEmailFailureFirebase
extends FetchSignInMethodsForEmailFailureInterface {
FetchSignInMethodsForEmailFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
FetchSignInMethodsForEmailFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
FetchSignInMethodsForEmailFailureFirebase.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'invalid-email':
message = 'The email address is badly formatted.';
msg = 'The email address is badly formatted.';
break;
default:
this.code = 'unknown';
message = 'An unknown error occurred.';
msg = 'An unknown error occurred.';
}
}
}
class SignInAnonymouslyFailureFirebase
extends SignInAnonymouslyFailureInterface {
SignInAnonymouslyFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SignInAnonymouslyFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignInAnonymouslyFailureFirebase.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'operation-not-allowed':
message = 'Operation is not allowed. Please contact support.';
msg = 'Operation is not allowed. Please contact support.';
break;
default:
this.code = 'unknown';
message = 'An unknown error occurred.';
msg = 'An unknown error occurred.';
}
}
}
class SignInWithCredentialFailureFirebase
extends SignInWithCredentialFailureInterface {
SignInWithCredentialFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SignInWithCredentialFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignInWithCredentialFailureFirebase.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'account-exists-with-different-credential':
message = 'Account exists with different credentials.';
msg = 'Account exists with different credentials.';
break;
case 'invalid-credential':
message = 'The credential received is malformed or has expired.';
msg = 'The credential received is malformed or has expired.';
break;
case 'operation-not-allowed':
message = 'Operation is not allowed. Please contact support.';
msg = 'Operation is not allowed. Please contact support.';
break;
case 'user-disabled':
message =
'This user has been disabled. Please contact support for help.';
msg = 'This user has been disabled. Please contact support for help.';
break;
case 'user-not-found':
message = 'Email is not found, please create an account.';
msg = 'Email is not found, please create an account.';
break;
case 'wrong-password':
message = 'Incorrect password, please try again.';
msg = 'Incorrect password, please try again.';
break;
case 'invalid-verification-code':
message = 'The credential verification code received is invalid.';
msg = 'The credential verification code received is invalid.';
break;
case 'invalid-verification-id':
message = 'The credential verification ID received is invalid.';
msg = 'The credential verification ID received is invalid.';
break;
default:
this.code = 'unknown';
message = 'An unknown error occurred.';
msg = 'An unknown error occurred.';
}
}
}
@@ -148,130 +145,135 @@ class SignInWithCredentialFailureFirebase
class SignInWithGoogleFailureFirebase
extends SignInWithCredentialFailureFirebase
implements SignInWithGoogleFailureInterface {
SignInWithGoogleFailureFirebase([super.code, super.message]);
SignInWithGoogleFailureFirebase([super.code, super.msg]);
SignInWithGoogleFailureFirebase.fromCode(super.code) : super.fromCode();
}
class SignInWithFacebookFailureFirebase
extends SignInWithCredentialFailureFirebase
implements SignInWithFacebookFailureInterface {
SignInWithFacebookFailureFirebase([super.code, super.message]);
SignInWithFacebookFailureFirebase([super.code, super.msg]);
SignInWithFacebookFailureFirebase.fromCode(super.code) : super.fromCode();
}
class SignInWithAppleFailureFirebase extends SignInWithCredentialFailureFirebase
implements SignInWithAppleFailureInterface {
SignInWithAppleFailureFirebase([super.code, super.message]);
SignInWithAppleFailureFirebase([super.code, super.msg]);
SignInWithAppleFailureFirebase.fromCode(super.code) : super.fromCode();
}
class SignInWithTwitterFailureFirebase
extends SignInWithCredentialFailureFirebase
implements SignInWithAppleFailureInterface {
SignInWithTwitterFailureFirebase([super.code, super.message]);
SignInWithTwitterFailureFirebase([super.code, super.msg]);
SignInWithTwitterFailureFirebase.fromCode(super.code) : super.fromCode();
}
class SignInWithEmailLinkFailureFirebase
extends SignInWithEmailLinkFailureInterface {
SignInWithEmailLinkFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SignInWithEmailLinkFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignInWithEmailLinkFailureFirebase.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'expired-action-code':
message = 'Action code has expired.';
msg = 'Action code has expired.';
break;
case 'invalid-email':
message = 'Email is not valid or badly formatted.';
msg = 'Email is not valid or badly formatted.';
break;
case 'user-disabled':
message =
'This user has been disabled. Please contact support for help.';
msg = 'This user has been disabled. Please contact support for help.';
break;
default:
this.code = 'unknown';
message = 'An unknown error occurred.';
msg = 'An unknown error occurred.';
}
}
}
class SignInWithEmailAndPasswordFailureFirebase
extends SignInWithEmailAndPasswordFailureInterface {
SignInWithEmailAndPasswordFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SignInWithEmailAndPasswordFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignInWithEmailAndPasswordFailureFirebase.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'invalid-email':
message = 'Email is not valid or badly formatted.';
msg = 'Email is not valid or badly formatted.';
break;
case 'user-disabled':
message =
'This user has been disabled. Please contact support for help.';
msg = 'This user has been disabled. Please contact support for help.';
break;
case 'user-not-found':
message = 'Email is not found, please create an account.';
msg = 'Email is not found, please create an account.';
break;
case 'wrong-password':
message = 'Incorrect password, please try again.';
msg = 'Incorrect password, please try again.';
break;
default:
this.code = 'unknown';
message = 'An unknown error occurred.';
msg = 'An unknown error occurred.';
}
}
}
class SendEmailVerificationFailureFirebase
extends SendEmailVerificationFailureInterface {
SendEmailVerificationFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SendEmailVerificationFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SendEmailVerificationFailureFirebase.fromCode(super.code) : super.fromCode();
}
class SendPasswordResetEmailFailureFirebase
extends SendPasswordResetEmailFailureInterface {
SendPasswordResetEmailFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SendPasswordResetEmailFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SendPasswordResetEmailFailureFirebase.fromCode(super.code) : super.fromCode();
}
class SendSignInLinkEmailFailureFirebase
extends SendSignInLinkEmailFailureInterface {
SendSignInLinkEmailFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SendSignInLinkEmailFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SendSignInLinkEmailFailureFirebase.fromCode(super.code) : super.fromCode();
}
class ConfirmPasswordResetFailureFirebase
extends ConfirmPasswordResetFailureInterface {
ConfirmPasswordResetFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
ConfirmPasswordResetFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
ConfirmPasswordResetFailureFirebase.fromCode(super.code) : super.fromCode();
}
class VerifyPasswordResetCodeFailureFirebase
extends VerifyPasswordResetCodeFailureInterface {
VerifyPasswordResetCodeFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
VerifyPasswordResetCodeFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
VerifyPasswordResetCodeFailureFirebase.fromCode(super.code)
: super.fromCode();
}
class RefreshFailureFirebase extends RefreshFailureInterface {
RefreshFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
RefreshFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
RefreshFailureFirebase.fromCode(super.code) : super.fromCode();
}
class SignOutFailureFirebase extends SignOutFailureInterface {
SignOutFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SignOutFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
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();
}
@@ -0,0 +1,17 @@
// 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 'cryptography.dart';
@@ -0,0 +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 'data_sources/data_sources.dart';
export 'models/models.dart';
export 'repositories/repositories.dart';
@@ -0,0 +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';
@@ -0,0 +1,67 @@
// 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:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class AuthenticationCacheDataSourceImpl<T extends Object>
extends AuthenticationCacheDataSource<T> {
Account? _account;
T? _data;
AuthenticationCacheDataSourceImpl();
@override
Future<void> storeAccount(Account? account) async {
_account = account;
}
@override
Future<void> storeData(T? data) async {
_data = data;
}
@override
Future<Account> loadAccount() async {
if (_account.isNotNull) {
return _account!;
}
throw ClientException('Cached account is invalid');
}
@override
Future<T> loadData() async {
if (_data.isNotNull) {
return _data!;
}
throw ClientException('Cached data is invalid');
}
@override
Future<void> destroy() async {
_data = null;
_account = null;
}
@override
Future<AccountWrapper<T>> load() async {
if (_account.isNull) {
throw ClientException('Cached account is invalid');
}
return AccountWrapperModel(_account, _data);
}
}
@@ -0,0 +1,202 @@
// 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:firebase_auth/firebase_auth.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class AuthenticationFirebaseDataSourceImpl
extends AuthenticationRemoteDataSource {
final FirebaseAuth _firebaseAuth;
AuthenticationFirebaseDataSourceImpl({FirebaseAuth? firebaseAuth})
: _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance;
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
Future<Account> signInWithEmailAndPassword({
required String email,
required String password,
}) async {
try {
final userCredential = await _firebaseAuth.signInWithEmailAndPassword(
email: email,
password: password,
);
final user = userCredential.user;
if (user.isNotNull) {
return _mapper(user!);
} else {
throw Exception(); // Get caught just after.
}
} on FirebaseAuthException catch (e) {
throw SignInWithEmailAndPasswordFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithEmailAndPasswordFailureFirebase();
}
}
@override
/// {@macro signup}
Future<Account> signUp({
required String email,
required String password,
}) async {
try {
final userCredential = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
final user = userCredential.user;
if (user.isNotNull) {
return _mapper(user!);
} else {
throw Exception(); // Get caught just after.
}
} on FirebaseAuthException catch (e) {
throw SignUpWithEmailAndPasswordFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignUpWithEmailAndPasswordFailureFirebase();
}
}
@override
Future<void> signOut() async {
try {
await _firebaseAuth.signOut();
} catch (_) {
throw SignOutFailureFirebase();
}
}
@override
Future<String> getIdentityToken() async {
try {
final token = await _firebaseAuth.currentUser?.getIdToken();
if (token.isNotNull) {
return token!;
} else {
throw Exception(); // Get caught just after.
}
} on FirebaseAuthException catch (e) {
throw GetIdTokenFailureFirebase.fromCode(e.code);
} catch (_) {
throw GetIdTokenFailureFirebase();
}
}
@override
Stream<Account?> streamAccount() =>
_firebaseAuth.userChanges().map<Account?>((user) {
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;
}
}
@@ -0,0 +1,87 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// 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:wyatt_authentication_bloc/src/domain/entities/account.dart';
class AccountModel extends Account {
@override
final String uid;
@override
final String? 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.
//
@@ -14,19 +15,22 @@
// 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:wyatt_form_bloc/src/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/validators/inputs/base/text_validator.dart';
class EnumValidator<E> extends TextValidator<ValidationStandardError> {
/// {@macro text_string}
const EnumValidator.pure() : super.pure();
/// {@macro text_string}
EnumValidator.dirty(E value) : super.dirty(value.toString());
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account_wrapper.dart';
class AccountWrapperModel<T> extends AccountWrapper<T> {
@override
ValidationStandardError get onEmpty => ValidationStandardError.empty;
final Account? account;
@override
ValidationStandardError get onNull => ValidationStandardError.invalid;
final T? data;
AccountWrapperModel(this.account, this.data);
AccountWrapperModel<T> copyWith({
Account? account,
T? data,
}) => AccountWrapperModel<T>(
account ?? this.account,
data ?? this.data,
);
}
@@ -0,0 +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 'account_model.dart';
export 'account_wrapper_model.dart';
@@ -1,82 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart' as wyatt;
class UserFirebase implements wyatt.User {
final User? _user;
const UserFirebase(User user) : _user = user;
User? get inner => _user;
@override
const UserFirebase.empty() : _user = null;
@override
DateTime? get creationTime => _user?.metadata.creationTime;
@override
String? get displayName => _user?.displayName;
@override
String? get email => _user?.email;
@override
bool get emailVerified => _user?.emailVerified ?? false;
@override
bool get isAnonymous => _user?.isAnonymous ?? false;
@override
bool get isEmpty => _user == null;
@override
bool get isNotEmpty => _user != null;
@override
DateTime? get lastSignInTime => _user?.metadata.lastSignInTime;
@override
String? get phoneNumber => _user?.phoneNumber;
@override
String? get photoURL => _user?.photoURL;
@override
String? get refreshToken => _user?.refreshToken;
@override
String get uid => _user?.uid ?? '';
@override
String? get providerId => _user?.providerData.first.providerId;
@override
bool? get isNewUser {
if (_user?.metadata.lastSignInTime == null ||
_user?.metadata.creationTime == null) {
return null;
} else {
return _user?.metadata.lastSignInTime == _user?.metadata.creationTime;
}
}
@override
// ignore: lines_longer_than_80_chars
String toString() => 'UserFirebase(creationTime: $creationTime, displayName: $displayName, email: $email, emailVerified: $emailVerified, isAnonymous: $isAnonymous, lastSignInTime: $lastSignInTime, phoneNumber: $phoneNumber, photoURL: $photoURL, refreshToken: $refreshToken, uid: $uid)';
}
@@ -1,338 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:twitter_login/twitter_login.dart';
import 'package:wyatt_authentication_bloc/src/core/enum/auth_cubit_status.dart';
import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions_firebase.dart';
import 'package:wyatt_authentication_bloc/src/core/extensions/firebase_auth_user_x.dart';
import 'package:wyatt_authentication_bloc/src/core/utils/cryptography.dart';
import 'package:wyatt_authentication_bloc/src/data/models/user_firebase.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart'
as wyatt;
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
class AuthenticationRepositoryFirebase implements AuthenticationRepository {
final _controller = StreamController<AuthCubitStatus>();
final FirebaseAuth _firebaseAuth;
final TwitterLogin? _twitterLogin;
UserFirebase _userCache = const UserFirebase.empty();
AuthenticationRepositoryFirebase({
FirebaseAuth? firebaseAuth,
TwitterLogin? twitterLogin,
}) : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance,
_twitterLogin = twitterLogin {
_controller.sink.add(AuthCubitStatus.stoped);
}
@override
Stream<AuthCubitStatus> get cubitStatus =>
_controller.stream.asBroadcastStream();
@override
void changeCubitStatus(AuthCubitStatus status) =>
_controller.sink.add(status);
@override
Stream<wyatt.User> get user =>
_firebaseAuth.userChanges().map((firebaseUser) {
final UserFirebase user = (firebaseUser == null)
? const UserFirebase.empty()
: firebaseUser.model;
_userCache = user;
return user;
});
@override
wyatt.User get currentUser => _userCache;
@override
Future<void> applyActionCode(String code) async {
try {
await _firebaseAuth.applyActionCode(code);
} on FirebaseAuthException catch (e) {
throw ApplyActionCodeFailureFirebase.fromCode(e.code);
} catch (_) {
throw ApplyActionCodeFailureFirebase();
}
}
@override
Future<String?> signUp({
required String email,
required String password,
}) async {
try {
final creds = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
return creds.user?.uid;
} on FirebaseAuthException catch (e) {
throw SignUpWithEmailAndPasswordFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignUpWithEmailAndPasswordFailureFirebase();
}
}
@override
Future<List<String>> fetchSignInMethodsForEmail({
required String email,
}) async {
try {
return await _firebaseAuth.fetchSignInMethodsForEmail(email);
} on FirebaseAuthException catch (e) {
throw FetchSignInMethodsForEmailFailureFirebase.fromCode(e.code);
} catch (_) {
throw FetchSignInMethodsForEmailFailureFirebase();
}
}
@override
Future<void> signInAnonymously() async {
try {
await _firebaseAuth.signInAnonymously();
} on FirebaseAuthException catch (e) {
throw SignInAnonymouslyFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInAnonymouslyFailureFirebase();
}
}
@override
Future<void> signInWithGoogle() async {
// Trigger the authentication flow
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication? googleAuth =
await googleUser?.authentication;
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
try {
await _firebaseAuth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw SignInWithGoogleFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithGoogleFailureFirebase();
}
}
@override
Future<void> signInWithFacebook() async {
// Trigger the sign-in flow
final LoginResult loginResult = await FacebookAuth.instance.login();
// Create a credential from the access token
final OAuthCredential credential =
FacebookAuthProvider.credential(loginResult.accessToken?.token ?? '');
try {
await _firebaseAuth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw SignInWithFacebookFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithFacebookFailureFirebase();
}
}
@override
Future<void> signInWithApple() async {
// To prevent replay attacks with the credential returned from Apple, we
// include a nonce in the credential request. When signing in with
// Firebase, the nonce in the id token returned by Apple, is expected to
// match the sha256 hash of `rawNonce`.
final rawNonce = Cryptography.generateNonce();
final nonce = Cryptography.sha256ofString(rawNonce);
// Request credential for the currently signed in Apple account.
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
// Create an `OAuthCredential` from the credential returned by Apple.
final credential = OAuthProvider('apple.com').credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
// Sign in the user with Firebase. If the nonce we generated earlier does
// not match the nonce in `appleCredential.identityToken`,
// sign in will fail.
try {
await _firebaseAuth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw SignInWithAppleFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithAppleFailureFirebase();
}
}
@override
Future<void> signInWithTwitter() async {
final twitterLogin = _twitterLogin;
if (twitterLogin == null) {
throw SignInWithTwitterFailureFirebase();
}
// Trigger the sign-in flow
final authResult = await twitterLogin.login();
// Create a credential from the access token
final credential = TwitterAuthProvider.credential(
accessToken: authResult.authToken!,
secret: authResult.authTokenSecret!,
);
try {
await _firebaseAuth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw SignInWithCredentialFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithCredentialFailureFirebase();
}
}
@override
Future<void> signInWithEmailLink(String email, String emailLink) async {
try {
await _firebaseAuth.signInWithEmailLink(
email: email,
emailLink: emailLink,
);
} on FirebaseAuthException catch (e) {
throw SignInWithEmailLinkFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithEmailLinkFailureFirebase();
}
}
@override
Future<void> signInWithEmailAndPassword({
required String email,
required String password,
}) async {
try {
await _firebaseAuth.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
throw SignInWithEmailAndPasswordFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithEmailAndPasswordFailureFirebase();
}
}
@override
Future<void> sendEmailVerification() async {
try {
await _userCache.inner!.sendEmailVerification();
} catch (e) {
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<void> sendSignInLinkEmail({required String email}) async {
try {
// TODO(hpcl): implement sendSignInLinkEmail
} on FirebaseAuthException catch (e) {
throw SendSignInLinkEmailFailureFirebase.fromCode(e.code);
} catch (_) {
throw SendSignInLinkEmailFailureFirebase();
}
}
@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();
}
throw UnimplementedError();
}
@override
Future<void> verifyPasswordResetCode({required String code}) async {
try {
await _firebaseAuth.verifyPasswordResetCode(code);
} on FirebaseAuthException catch (e) {
throw VerifyPasswordResetCodeFailureFirebase.fromCode(e.code);
} catch (_) {
throw VerifyPasswordResetCodeFailureFirebase();
}
}
@override
Future<void> signOut() async {
try {
await Future.wait([
_firebaseAuth.signOut(),
]);
_userCache = const UserFirebase.empty();
} catch (_) {
throw SignOutFailureFirebase();
}
}
@override
Future<void> refresh() async {
try {
await _userCache.inner!.reload();
} on FirebaseAuthException catch (e) {
throw RefreshFailureFirebase.fromCode(e.code);
} catch (_) {
throw RefreshFailureFirebase();
}
}
}
@@ -0,0 +1,277 @@
// 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:wyatt_architecture/wyatt_architecture.dart';
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/data/models/account_wrapper_model.dart';
import 'package:wyatt_authentication_bloc/src/domain/data_sources/local/authentication_cache_data_source.dart';
import 'package:wyatt_authentication_bloc/src/domain/data_sources/remote/authentication_remote_data_source.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account_wrapper.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';
typedef OnSignUpSuccess<T> = FutureResult<T?> Function(
Account? account,
WyattForm form,
);
typedef OnAuthChange<T> = FutureResult<T?> Function(Account? account);
class AuthenticationRepositoryImpl<T extends Object>
extends AuthenticationRepository<T> {
final AuthenticationCacheDataSource<T> _authenticationLocalDataSource;
final AuthenticationRemoteDataSource _authenticationRemoteDataSource;
late FormRepository _formRepository;
final OnSignUpSuccess<T>? _onSignUpSuccess;
final OnAuthChange<T>? _onAccountChanges;
AuthenticationRepositoryImpl({
required AuthenticationCacheDataSource<T> authenticationCacheDataSource,
required AuthenticationRemoteDataSource authenticationRemoteDataSource,
FormRepository? formRepository,
// ignore: strict_raw_type
List<FormInput>? extraSignUpInputs,
OnSignUpSuccess<T>? onSignUpSuccess,
OnAuthChange<T>? onAuthChange,
}) : _authenticationLocalDataSource = authenticationCacheDataSource,
_authenticationRemoteDataSource = authenticationRemoteDataSource,
_onSignUpSuccess = onSignUpSuccess,
_onAccountChanges = onAuthChange {
_formRepository = formRepository ?? FormRepositoryImpl();
if (formRepository != null) {
return;
}
_formRepository
..registerForm(
WyattFormImpl(
[
FormInput(AuthFormField.email, const Email.pure()),
FormInput(AuthFormField.password, const Password.pure())
],
name: AuthFormName.signInForm,
),
)
..registerForm(
WyattFormImpl(
[
FormInput(AuthFormField.email, const Email.pure()),
FormInput(AuthFormField.password, const Password.pure()),
...extraSignUpInputs ?? []
],
name: AuthFormName.signUpForm,
),
);
}
@override
FormRepository get formRepository => _formRepository;
@override
FutureResult<Account> signInWithEmailAndPassword({
required String email,
required String password,
}) =>
Result.tryCatchAsync<Account, AppException, AppException>(
() async {
final account =
await _authenticationRemoteDataSource.signInWithEmailAndPassword(
email: email,
password: password,
);
await _authenticationLocalDataSource.storeAccount(account);
return account;
},
(error) => error,
);
@override
FutureResult<void> signOut() =>
Result.tryCatchAsync<void, AppException, AppException>(
() async {
await _authenticationRemoteDataSource.signOut();
await _authenticationLocalDataSource.destroy();
},
(error) => error,
);
@override
FutureResult<Account> signUp({
required String email,
required String password,
}) =>
Result.tryCatchAsync<Account, AppException, AppException>(
() async {
final account = await _authenticationRemoteDataSource.signUp(
email: email,
password: password,
);
await _authenticationLocalDataSource.storeAccount(account);
if (_onSignUpSuccess.isNotNull) {
final dataResult = await _onSignUpSuccess!.call(
account,
_formRepository.accessForm(AuthFormName.signUpForm).clone(),
);
await dataResult.foldAsync(
_authenticationLocalDataSource.storeData,
(error) async => error,
);
}
return account;
},
(error) => error,
);
@override
FutureResult<void> destroyCache() =>
Result.tryCatchAsync<void, AppException, AppException>(
_authenticationLocalDataSource.destroy,
(error) => error,
);
@override
FutureResult<AccountWrapper<T>> getCache() =>
Result.tryCatchAsync<AccountWrapper<T>, AppException, AppException>(
_authenticationLocalDataSource.load,
(error) => error,
);
@override
FutureResult<Account> getAccount() =>
Result.tryCatchAsync<Account, AppException, AppException>(
_authenticationLocalDataSource.loadAccount,
(error) => error,
);
@override
FutureResult<void> setAccount(
Account account,
) =>
Result.tryCatchAsync<void, AppException, AppException>(
() async {
await _authenticationLocalDataSource.storeAccount(account);
},
(error) => error,
);
@override
FutureResult<T> getData() =>
Result.tryCatchAsync<T, AppException, AppException>(
_authenticationLocalDataSource.loadData,
(error) => error,
);
@override
FutureResult<void> setData(
T? data,
) =>
Result.tryCatchAsync<void, AppException, AppException>(
() async {
await _authenticationLocalDataSource.storeData(data);
},
(error) => error,
);
@override
FutureResult<String> getIdentityToken() =>
Result.tryCatchAsync<String, AppException, AppException>(
_authenticationRemoteDataSource.getIdentityToken,
(error) => error,
);
@override
Stream<FutureResult<AccountWrapper<T>>> streamAccount() =>
_authenticationRemoteDataSource.streamAccount().map((account) async {
if (_onAccountChanges.isNotNull) {
final dataResult = await _onAccountChanges!.call(account);
return dataResult.map((data) => AccountWrapperModel(account, data));
}
return Ok<AccountWrapperModel<T>, AppException>(
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,
);
}
@@ -0,0 +1,17 @@
// 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_repository_impl.dart';
@@ -0,0 +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 'local/authentication_cache_data_source.dart';
export 'remote/authentication_remote_data_source.dart';
@@ -0,0 +1,29 @@
// 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:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account_wrapper.dart';
abstract class AuthenticationCacheDataSource<T extends Object>
extends BaseLocalDataSource {
Future<void> storeAccount(Account? account);
Future<void> storeData(T? data);
Future<Account> loadAccount();
Future<T> loadData();
Future<AccountWrapper<T>> load();
Future<void> destroy();
}
@@ -0,0 +1,51 @@
// 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:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
abstract class AuthenticationRemoteDataSource extends BaseRemoteDataSource {
Future<Account> signUp({
required String email,
required String password,
});
Future<Account> signInWithEmailAndPassword({
required String email,
required String password,
});
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();
}
@@ -0,0 +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 'data_sources/data_sources.dart';
export 'entities/entities.dart';
export 'repositories/repositories.dart';
@@ -14,14 +14,12 @@
// 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 User {
/// The empty user constructor.
const User.empty();
import 'package:equatable/equatable.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
/// The users display name.
///
/// Will be `null` if signing in anonymously or via password authentication.
String? get displayName;
abstract class Account extends Equatable implements Entity {
/// The user's unique ID.
String get uid;
/// The users email address.
///
@@ -31,9 +29,6 @@ abstract class User {
/// Returns whether the users email address has been verified.
///
/// To send a verification email, see `SendEmailVerification`.
///
/// Once verified, call `reload` to ensure the latest user information is
/// retrieved from Firebase.
bool get emailVerified;
/// Returns whether the user is a anonymous.
@@ -45,9 +40,6 @@ abstract class User {
DateTime? get creationTime;
/// When the user last signed in as dictated by the server clock.
///
/// This is only accurate up to a granularity of 2 minutes for consecutive
/// sign-in attempts.
DateTime? get lastSignInTime;
/// Returns the users phone number.
@@ -62,24 +54,30 @@ abstract class User {
/// with a 3rd party OAuth provider (such as Google).
String? get photoURL;
/// Returns a JWT refresh token for the user.
///
/// This property maybe `null` or empty if the underlying platform does not
/// support providing refresh tokens.
String? get refreshToken;
/// The user's unique ID.
String get uid;
/// The provider ID for the user.
String? get providerId;
String get providerId;
/// Whether the user account has been recently created.
bool? get isNewUser;
/// Convenience getter to determine whether the current user is empty.
bool get isEmpty;
@override
List<Object?> get props => [
uid,
email,
emailVerified,
isAnonymous,
creationTime,
lastSignInTime,
phoneNumber,
photoURL,
providerId,
isNewUser,
];
/// Convenience getter to determine whether the current user is not empty.
bool get isNotEmpty;
@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,15 +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 'dart:convert';
import 'package:equatable/equatable.dart';
import 'package:wyatt_form_bloc/src/enums/form_input_status.dart';
import 'package:wyatt_form_bloc/src/enums/form_status.dart';
import 'package:wyatt_form_bloc/src/enums/validation_error.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
part 'form_data.dart';
part 'form_input.dart';
part 'form_input_metadata.dart';
part 'form_input_validator.dart';
part 'form_validator.dart';
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)';
}
@@ -0,0 +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 'account.dart';
export 'account_wrapper.dart';
@@ -14,126 +14,101 @@
// 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:wyatt_authentication_bloc/src/core/enum/auth_cubit_status.dart';
import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account_wrapper.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
/// {@template authentication_repository}
/// Repository which manages user authentication.
/// {@endtemplate}
abstract class AuthenticationRepository {
/// Stream of [AuthCubitStatus] wich will emit the current cubit status.
Stream<AuthCubitStatus> get cubitStatus;
/// Changes cubit status.(Useful to start or stop the engine.)
void changeCubitStatus(AuthCubitStatus status);
/// Stream of [User] which will emit the current user when
/// the authentication state changes.
///
/// Emits [User.empty] if the user is not authenticated.
Stream<User> get user;
/// Returns the current cached account.
/// Defaults to [User.empty] if there is no cached user.
User get currentUser;
/// Applies action code
///
/// [code] - The action code sent to the user's email address.
/// Throw [ApplyActionCodeFailureInterface] if an exception occurs.
Future<void> applyActionCode(String code);
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
/// Throws a SignUpWithEmailAndPasswordFailureInterface if
/// an exception occurs.
Future<String?> signUp({required String email, required String password});
/// Fetches sign in methods for [email].
///
/// Throws a [FetchSignInMethodsForEmailFailureInterface] if
/// an exception occurs.
Future<List<String>> fetchSignInMethodsForEmail({required String email});
/// Sign in anonimously.
///
/// Throws a [SignInAnonymouslyFailureInterface] if an exception occurs.
Future<void> signInAnonymously();
/// Starts the Sign In with Google Flow.
///
/// Throws a [SignInWithGoogleFailureInterface] if an exception occurs.
Future<void> signInWithGoogle();
/// Starts the Sign In with Facebook Flow.
///
/// Throws a [SignInWithFacebookFailureInterface] if an exception occurs.
Future<void> signInWithFacebook();
/// Starts the Sign In with Apple Flow.
///
/// Throws a [SignInWithAppleFailureInterface] if an exception occurs.
Future<void> signInWithApple();
/// Starts the Sign In with Twitter Flow.
///
/// Throws a [SignInWithTwitterFailureInterface] if an exception occurs.
Future<void> signInWithTwitter();
/// Signs in using an email address and email sign-in link.
///
/// Throws a [SignInWithEmailLinkFailureInterface] if an exception occurs.
Future<void> signInWithEmailLink(
String email,
String emailLink,
);
/// Signs in with the provided [email] and [password].
///
/// Throws a [SignInWithEmailAndPasswordFailureInterface] if
/// an exception occurs.
Future<void> signInWithEmailAndPassword({
/// {@endtemplate}
FutureResult<Account> signUp({
required String email,
required String password,
});
/// Sends verification email to the provided [user].
/// {@template send_email_verification}
/// Sends verification email to the account email.
///
/// Throws a [SendEmailVerificationFailureInterface] if an exception occurs.
Future<void> sendEmailVerification();
/// 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.
Future<void> sendPasswordResetEmail({required String email});
/// Sends link to login.
///
/// Throws a [SendSignInLinkEmailFailureInterface] if an exception occurs.
Future<void> sendSignInLinkEmail({required String 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.
Future<void> confirmPasswordReset({
/// 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.
Future<void> verifyPasswordResetCode({required String code});
/// Throws a VerifyPasswordResetCodeFailureInterface if an exception occurs.
/// {@endtemplate}
FutureResult<bool> verifyPasswordResetCode({required String code});
/// Signs out the current user which will emit
/// [User.empty] from the [user] Stream.
Future<void> signOut();
/// Refreshes the current user.
/// {@template signin_anom}
/// Sign in anonymously.
///
/// Throws a [RefreshFailureInterface] if an exception occurs.
Future<void> refresh();
/// 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();
FutureResult<Account> getAccount();
FutureResult<void> setAccount(Account account);
FutureResult<T> getData();
FutureResult<void> setData(T? data);
FutureResult<AccountWrapper<T>> getCache();
FutureResult<void> destroyCache();
}
@@ -0,0 +1,17 @@
// 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_repository.dart';
@@ -16,7 +16,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart';
import 'package:wyatt_authentication_bloc/src/core/enums/authentication_status.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account_wrapper.dart';
import 'package:wyatt_authentication_bloc/src/features/authentication/cubit/authentication_cubit.dart';
class AuthenticationBuilder<Extra> extends StatelessWidget {
@@ -29,8 +30,7 @@ class AuthenticationBuilder<Extra> extends StatelessWidget {
final Widget Function(
BuildContext context,
User user,
Extra? extra,
AccountWrapper<Extra> accountWrapper,
) authenticated;
final Widget Function(BuildContext context) unauthenticated;
final Widget Function(BuildContext context) unknown;
@@ -40,8 +40,8 @@ class AuthenticationBuilder<Extra> extends StatelessWidget {
BlocBuilder<AuthenticationCubit<Extra>, AuthenticationState<Extra>>(
builder: (context, state) {
if (state.status == AuthenticationStatus.authenticated) {
if (state.user != null) {
return authenticated(context, state.user!, state.extra);
if (state.accountWrapper != null) {
return authenticated(context, state.accountWrapper!);
} else {
return unauthenticated(context);
}
@@ -18,83 +18,46 @@ import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/core/enum/auth_cubit_status.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart';
import 'package:wyatt_authentication_bloc/src/core/enums/authentication_status.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account_wrapper.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'authentication_state.dart';
class AuthenticationCubit<Extra> extends Cubit<AuthenticationState<Extra>> {
final AuthenticationRepository _authenticationRepository;
late final StreamSubscription<AuthCubitStatus> _statusSubscription;
StreamSubscription<User>? _userSubscription;
final Future<Extra?> Function(User user)? _onAuthSuccess;
final AuthenticationRepository<Extra> _authenticationRepository;
AuthenticationCubit({
required AuthenticationRepository authenticationRepository,
Future<Extra?> Function(User user)? onAuthSuccess,
required AuthenticationRepository<Extra> authenticationRepository,
}) : _authenticationRepository = authenticationRepository,
_onAuthSuccess = onAuthSuccess,
super(const AuthenticationState.unknown()) {
_subscribeStatus();
_listenForAuthenticationChanges();
}
Future<AuthCubitStatus> get status async =>
_authenticationRepository.cubitStatus.last;
void _subscribeStatus() {
try {
_statusSubscription = _authenticationRepository.cubitStatus.listen(
(status) {
switch (status) {
case AuthCubitStatus.started:
start();
break;
case AuthCubitStatus.stoped:
stop();
break;
void _listenForAuthenticationChanges() {
_authenticationRepository.streamAccount().listen((accountFutureResult) {
accountFutureResult.fold(
(value) {
if (value.account.isNotNull) {
emit(AuthenticationState<Extra>.authenticated(value));
return;
}
_authenticationRepository.destroyCache();
emit(AuthenticationState<Extra>.unauthenticated());
return;
},
(error) {
_authenticationRepository.destroyCache();
emit(AuthenticationState<Extra>.unauthenticated());
return;
},
);
} catch (_) {}
});
}
Future<void> init() async {
final firstUser = await _authenticationRepository.user.first;
_authenticationRepository.changeCubitStatus(AuthCubitStatus.started);
return changeStatus(firstUser);
}
bool start() {
_userSubscription = _authenticationRepository.user.listen(changeStatus);
return true;
}
bool stop() {
_userSubscription?.cancel();
return true;
}
Future<void> changeStatus(User user) async {
if (user.isNotEmpty) {
final Extra? extra = await _onAuthSuccess?.call(user);
emit(AuthenticationState.authenticated(user, extra));
} else {
_authenticationRepository.changeCubitStatus(AuthCubitStatus.stoped);
emit(const AuthenticationState.unauthenticated());
}
}
void logOut() {
unawaited(_authenticationRepository.signOut());
}
@override
Future<void> close() {
_userSubscription?.cancel();
_statusSubscription.cancel();
return super.close();
FutureOr<void> signOut() {
// TODO(hpcl): maybe force unauthenticated by emitting an event
_authenticationRepository.signOut();
}
}
@@ -16,42 +16,28 @@
part of 'authentication_cubit.dart';
enum AuthenticationStatus {
unknown,
authenticated,
unauthenticated,
}
class AuthenticationState<Extra> extends Equatable {
final AuthenticationStatus status;
final User? user;
final Extra? extra;
final AccountWrapper<Extra>? accountWrapper;
const AuthenticationState._({
required this.status,
this.user,
this.extra,
});
const AuthenticationState._({required this.status, this.accountWrapper});
const AuthenticationState.unknown()
: this._(status: AuthenticationStatus.unknown);
const AuthenticationState.authenticated(
User user,
Extra? extra,
) : this._(
const AuthenticationState.authenticated(AccountWrapper<Extra> accountWrapper)
: this._(
status: AuthenticationStatus.authenticated,
user: user,
extra: extra,
accountWrapper: accountWrapper,
);
const AuthenticationState.unauthenticated()
: this._(status: AuthenticationStatus.unauthenticated);
@override
List<Object?> get props => [status, user, extra];
List<Object?> get props => [status, accountWrapper];
@override
String toString() =>
'AuthenticationState(status: $status, user: $user, extra: $extra)';
'AuthenticationState(status: $status, accountWrapper: $accountWrapper)';
}
@@ -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);
},
);
}

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