Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a66db23ace
|
||
|
|
3d7af98ac9
|
||
|
|
b033b97917 | ||
|
|
14b7f31b53
|
||
|
|
403bcafe93
|
||
|
|
21ab6f6021
|
||
|
|
198ecac5da
|
||
|
|
c945cd435e
|
||
|
|
852d29ef0a
|
||
|
|
08383ec03b
|
||
|
|
ee8ce76839
|
||
|
|
f05f2230e9
|
||
|
|
54002beaab
|
||
|
|
d5698fbffc
|
||
|
|
31ad460757
|
||
|
|
4fd2a2a16c
|
||
|
|
6eb40de72c
|
||
|
|
cecbc94a95
|
||
|
|
3820fc4764
|
||
|
|
1b893a5b84
|
||
|
|
72661a343f
|
||
|
|
9ebe8e21dc
|
||
|
|
141f15b444
|
||
|
|
3a8a604a48
|
||
|
|
41493207b3
|
||
|
|
cbc4a2e149
|
||
|
|
7345e415a7
|
||
|
|
ff680614aa
|
||
|
|
3e110e2eb5
|
||
|
|
1ccb0a540e
|
||
|
|
f9da719b91
|
||
|
|
95f41025f4
|
||
|
|
eceda684f0
|
||
|
|
291c195ceb
|
||
|
|
1896497c1a
|
||
|
|
87254ef547
|
||
|
|
da34acd35b
|
||
|
|
d9d0625c67
|
||
|
|
08f789725b
|
||
|
|
33ac5c6280
|
||
|
|
762b9bcd11
|
||
|
|
38480d84f4
|
||
|
|
03a51b97ad
|
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
// Dart
|
||||||
|
"dart.runPubGetOnPubspecChanges": "never",
|
||||||
|
// Gitea
|
||||||
|
"gitea.owner": "Wyatt-FOSS",
|
||||||
|
"gitea.repo": "wyatt-packages",
|
||||||
|
// Header
|
||||||
"psi-header.changes-tracking": {
|
"psi-header.changes-tracking": {
|
||||||
"isActive": true
|
"isActive": true
|
||||||
},
|
},
|
||||||
@@ -67,5 +73,4 @@
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dart.runPubGetOnPubspecChanges": false,
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,6 +3,131 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## 2022-11-16
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`wyatt_authentication_bloc` - `v0.4.0+1`](#wyatt_authentication_bloc---v0401)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_authentication_bloc` - `v0.4.0+1`
|
||||||
|
|
||||||
|
- **FIX**: add sort of mutex on onAuthChanges callback trigger.
|
||||||
|
|
||||||
|
|
||||||
|
## 2022-11-16
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- [`wyatt_authentication_bloc` - `v0.4.0`](#wyatt_authentication_bloc---v040)
|
||||||
|
- [`wyatt_form_bloc` - `v0.2.0`](#wyatt_form_bloc---v020)
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- There are no other changes in this release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_authentication_bloc` - `v0.4.0`
|
||||||
|
|
||||||
|
- **REFACTOR**: update example with new input builders.
|
||||||
|
- **FIX**: add initial null account event in stream in the mock authentication.
|
||||||
|
- **BREAKING** **FIX**: add signInWithEmailAndPassword and signInAnonymously methods in SignInCubit #26.
|
||||||
|
|
||||||
|
#### `wyatt_form_bloc` - `v0.2.0`
|
||||||
|
|
||||||
|
- **FIX**: disable status selection and rebuild SubmitBuilder on every state changes #28.
|
||||||
|
- **FIX**: fix set operations behaviors.
|
||||||
|
- **FIX**: add nullable validator on boolean value.
|
||||||
|
- **FEAT**: add metadata tag to bypass validation #29.
|
||||||
|
- **FEAT**: add shorthands to access validator status.
|
||||||
|
- **BREAKING** **REFACTOR**: input builders provides FormInput.
|
||||||
|
|
||||||
|
|
||||||
|
## 2022-11-13
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- [`wyatt_crud_bloc` - `v0.1.0`](#wyatt_crud_bloc---v010)
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- There are no other changes in this release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_crud_bloc` - `v0.1.0`
|
||||||
|
|
||||||
|
- **REFACTOR**: add vscode config.
|
||||||
|
- **FIX**: update example.
|
||||||
|
- **BREAKING** **REFACTOR**: migrate to wyatt architecture.
|
||||||
|
|
||||||
|
|
||||||
|
## 2022-11-11
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`wyatt_authentication_bloc` - `v0.3.0+1`](#wyatt_authentication_bloc---v0301)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_authentication_bloc` - `v0.3.0+1`
|
||||||
|
|
||||||
|
- **FIX**: fix some bugs to validate publishing.
|
||||||
|
|
||||||
|
|
||||||
|
## 2022-11-11
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- [`wyatt_authentication_bloc` - `v0.3.0`](#wyatt_authentication_bloc---v030)
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- There are no other changes in this release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_authentication_bloc` - `v0.3.0`
|
||||||
|
|
||||||
|
- **REFACTOR**: use correct deps.
|
||||||
|
- **REFACTOR**: update example with new listeners.
|
||||||
|
- **FIX**: return exception and not throwing it anymore.
|
||||||
|
- **FIX**: exception returns msg as message.
|
||||||
|
- **FEAT**: add fully functionnal mock data source.
|
||||||
|
- **FEAT**: add toString and equality on entities and models.
|
||||||
|
- **BREAKING** **FEAT**: add email verification, password reset support.
|
||||||
|
|
||||||
|
|
||||||
## 2022-11-10
|
## 2022-11-10
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
firebase_options.dart
|
||||||
|
.vscode
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
// Dart
|
||||||
|
"dart.runPubGetOnPubspecChanges": "never",
|
||||||
|
// Gitea
|
||||||
|
"gitea.owner": "Wyatt-FOSS",
|
||||||
|
"gitea.repo": "wyatt-packages",
|
||||||
|
// Header
|
||||||
"psi-header.changes-tracking": {
|
"psi-header.changes-tracking": {
|
||||||
"isActive": true
|
"isActive": true
|
||||||
},
|
},
|
||||||
@@ -67,5 +73,4 @@
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dart.runPubGetOnPubspecChanges": false,
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,31 @@
|
|||||||
|
## 0.4.0+1
|
||||||
|
|
||||||
|
- **FIX**: add sort of mutex on onAuthChanges callback trigger.
|
||||||
|
|
||||||
|
## 0.4.0
|
||||||
|
|
||||||
|
> Note: This release has breaking changes.
|
||||||
|
|
||||||
|
- **REFACTOR**: update example with new input builders.
|
||||||
|
- **FIX**: add initial null account event in stream in the mock authentication.
|
||||||
|
- **BREAKING** **FIX**: add signInWithEmailAndPassword and signInAnonymously methods in SignInCubit #26.
|
||||||
|
|
||||||
|
## 0.3.0+1
|
||||||
|
|
||||||
|
- **FIX**: fix some bugs to validate publishing.
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
> Note: This release has breaking changes.
|
||||||
|
|
||||||
|
- **REFACTOR**: use correct deps.
|
||||||
|
- **REFACTOR**: update example with new listeners.
|
||||||
|
- **FIX**: return exception and not throwing it anymore.
|
||||||
|
- **FIX**: exception returns msg as message.
|
||||||
|
- **FEAT**: add fully functionnal mock data source.
|
||||||
|
- **FEAT**: add toString and equality on entities and models.
|
||||||
|
- **BREAKING** **FEAT**: add email verification, password reset support.
|
||||||
|
|
||||||
## 0.2.1+6
|
## 0.2.1+6
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
- Update a dependency to the latest release.
|
||||||
|
|||||||
@@ -29,43 +29,28 @@ Authentication Bloc for Flutter.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- UserInterface
|
- Wyatt Architecture
|
||||||
* UserFirebase : FirebaseAuth user implementation
|
- Entities:
|
||||||
- AuthenticationRepositoryInterface
|
- Account : AccountModel -> Contains account information from provider
|
||||||
* AuthenticationRepositoryFirebase : FirebaseAuth implementation
|
- AccountWrapper : AccountWrapperModel -> Contains account and extra data.
|
||||||
- ExceptionsInterface
|
- Data Sources:
|
||||||
* ExceptionsFirebase : FirebaseAuth Exception parsing implementation
|
- Local:
|
||||||
- AuthenticationBloc
|
- Cached Authentication Data : AuthenticationCacheDataSourceImpl -> Provides a cache implementation
|
||||||
* Tracks every user changes
|
- Remote:
|
||||||
- Right after the listener has been registered.
|
- Remote Authentication Data : AuthenticationFirebaseDataSourceImpl -> Provides a proxy to FirebaseAuth
|
||||||
- When a user is signed in.
|
- Repositories:
|
||||||
- When the current user is signed out.
|
- AuthenticationRepository : AuthenticationRepositoryImpl -> Provides all authentication methods
|
||||||
- When there is a change in the current user's token.
|
- Features:
|
||||||
- On `refresh()`
|
- Authentication:
|
||||||
* Start/Stop listening on demand
|
- AuthenticationBuilder : widget to build reactive view from authentication state
|
||||||
- `start()` to listen to user changes
|
- AuthenticationCubit : tracks every auth changes, have sign out capability.
|
||||||
- `stop()` to cancel listener
|
- SignUp:
|
||||||
- SignUpCubit
|
- SignUpCubit: implementation of a FormDataCubit from `wyatt_form_bloc` for the sign up
|
||||||
* Handles email/password validation and password confirmation
|
- SignIn:
|
||||||
* Handles register with email/password
|
- SignUpCubit: implementation of a FormDataCubit from `wyatt_form_bloc` for the sign in
|
||||||
* Handles custom form fields thanks `wyatt_form_bloc`
|
|
||||||
- Use `entries` to pass a `FormData` object
|
|
||||||
- You can use several pre configured `FormInput` for validation
|
|
||||||
- You can use `updateFormData()` to change FormData and validators during runtime (intersection, union, difference or replace)
|
|
||||||
- SignInCubit
|
|
||||||
* Handles email/password validation
|
|
||||||
* Handles login with email/password
|
|
||||||
- EmailVerificationCubit
|
|
||||||
* Handles send email verification process
|
|
||||||
* Handles email verification check
|
|
||||||
- PasswordResetCubit
|
|
||||||
* Handles send password reset email process
|
|
||||||
- Builders
|
|
||||||
* AuthenticationBuilder to build widgets on user state changes
|
|
||||||
- Consistent
|
- Consistent
|
||||||
* Every class have same naming convention
|
* Every class have same naming convention
|
||||||
- Tested
|
- Tested
|
||||||
* Partially tested with *bloc_test*
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
@@ -77,227 +62,4 @@ import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Create an authentication repository:
|
// TODO
|
||||||
|
|
||||||
```dart
|
|
||||||
final AuthenticationRepositoryInterface _authenticationRepository = AuthenticationRepositoryFirebase();
|
|
||||||
```
|
|
||||||
|
|
||||||
Create an authentication cubit:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final AuthenticationCubit _authenticationCubit = AuthenticationCubit(
|
|
||||||
authenticationRepository: _authenticationRepository,
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a sign up cubit:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final SignUpCubit _signUpCubit = SignUpCubit(
|
|
||||||
authenticationRepository: _authenticationRepository,
|
|
||||||
authenticationCubit: _authenticationCubit,
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
You can use `AuthenticationBloc` to route your app.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
return MultiRepositoryProvider(
|
|
||||||
providers: [
|
|
||||||
RepositoryProvider<AuthenticationRepositoryInterface>(
|
|
||||||
create: (context) => _authenticationRepository,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: MultiBlocProvider(
|
|
||||||
providers: [
|
|
||||||
BlocProvider<AuthenticationCubit>(
|
|
||||||
create: (context) => _authenticationCubit..init(),
|
|
||||||
),
|
|
||||||
BlocProvider<SignUpCubit>(
|
|
||||||
create: (context) => _signUpCubit,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: const AppView(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
```
|
|
||||||
> Don't forget to call `init()` on authentication cubit.
|
|
||||||
|
|
||||||
And in `AppView` use an `AuthenticationBuilder`:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
AuthenticationBuilder(
|
|
||||||
unknown: (context) => const LoadingPage(),
|
|
||||||
unauthenticated: (context) => const LoginPage(),
|
|
||||||
authenticated: (context, user, userData) => const HomePage(),
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
To create a `SignInCubit` you'll need the same `AuthenticationRepository`, you can use the `context`:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
BlocProvider(
|
|
||||||
create: (_) => SignInCubit(context.read<AuthenticationRepositoryInterface>()),
|
|
||||||
child: const LoginForm(),
|
|
||||||
),
|
|
||||||
```
|
|
||||||
|
|
||||||
> In practice it's better to create it in the main `MultiBlocProvider` because the LoginPage can be destroyed, and cubit closed, before login flow ends
|
|
||||||
|
|
||||||
## Recipes
|
|
||||||
|
|
||||||
### Password confirmation
|
|
||||||
|
|
||||||
In this recipe we'll se how to create a custom `FormEntry` to confirm password.
|
|
||||||
|
|
||||||
First, create an entry at the SignUpCubit creation:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
SignUpCubit _signUpCubit = SignUpCubit(
|
|
||||||
authenticationRepository: _authenticationRepository,
|
|
||||||
authenticationCubit: _authenticationCubit,
|
|
||||||
entries: const FormData([
|
|
||||||
FormEntry('form_field_confirmPassword', ConfirmedPassword.pure()),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, in the sign up form, create an input for password confirmation:
|
|
||||||
|
|
||||||
- `ConfirmedPassword` validator need password value and confirm password value to compare.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (confirmPassword) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged(
|
|
||||||
'form_field_confirmPassword',
|
|
||||||
ConfirmedPassword.dirty(
|
|
||||||
password: context.read<SignUpCubit>().state.password.value,
|
|
||||||
value: confirmPassword,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
obscureText: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'confirm password',
|
|
||||||
errorText: state.data!.input('form_field_confirmPassword').invalid
|
|
||||||
? 'passwords do not match'
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
> `form_field_confirmPassword` is the field identifier used in all application to retrieve data. You can use a constant to avoid typos.
|
|
||||||
|
|
||||||
You'll need to update password input to update confirm state on password update !
|
|
||||||
|
|
||||||
```dart
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (password) {
|
|
||||||
context.read<SignUpCubit>().passwordChanged(password);
|
|
||||||
context.read<SignUpCubit>().dataChanged(
|
|
||||||
'form_field_confirmPassword',
|
|
||||||
ConfirmedPassword.dirty(
|
|
||||||
password: password,
|
|
||||||
value: context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.state
|
|
||||||
.data!
|
|
||||||
.input('form_field_confirmPassword')
|
|
||||||
.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
obscureText: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'password',
|
|
||||||
errorText: state.password.invalid ? 'invalid password' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
> Here you call standard `passwordChanged()` AND `dataChanged()`.
|
|
||||||
|
|
||||||
And voilà !
|
|
||||||
|
|
||||||
### Create Firestore Document on Sign Up
|
|
||||||
|
|
||||||
In this recipe we'll se how to create a Firestore Document on sign up success.
|
|
||||||
|
|
||||||
First create a callback function:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
Future<void> onSignUpSuccess(SignUpState state, String? uid) async {
|
|
||||||
if (uid != null) {
|
|
||||||
final user = {
|
|
||||||
'uid': uid,
|
|
||||||
'email': state.email.value,
|
|
||||||
...state.data.toMap(),
|
|
||||||
};
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).set(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then create SignUpCubit with custom entries and register callback:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
SignUpCubit _signUpCubit = SignUpCubit(
|
|
||||||
authenticationRepository: _authenticationRepository,
|
|
||||||
authenticationCubit: _authenticationCubit,
|
|
||||||
entries: const FormData([
|
|
||||||
FormEntry('form_field_name', Name.pure(), fieldName: 'name'),
|
|
||||||
FormEntry('form_field_phone', Phone.pure(), fieldName: 'phone'),
|
|
||||||
FormEntry('form_field_confirmPassword', ConfirmedPassword.pure(), export: false),
|
|
||||||
]),
|
|
||||||
onSignUpSuccess: onSignUpSuccess,
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
> Use `fieldName` and `export` to control `.toMap()` result on FormData ! Useful to disable exportation of sensible data like passwords.
|
|
||||||
|
|
||||||
Create widgets for each inputs:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
class _PhoneInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (phone) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged('form_field_phone', Phone.dirty(phone)),
|
|
||||||
keyboardType: TextInputType.phone,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'phone',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.data!.input('form_field_phone').invalid
|
|
||||||
? 'invalid phone'
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> Create widgets for Name and ConfirmedPassword too.
|
|
||||||
|
|
||||||
Then add a sign up button with:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
context.read<SignUpCubit>().signUpFormSubmitted()
|
|
||||||
```
|
|
||||||
|
|
||||||
And voilà, a document with `uid` as id, and fields `email`, `name`, `phone`, `uid` will be create in `users` collection.
|
|
||||||
@@ -1 +1,4 @@
|
|||||||
include: package:wyatt_analysis/analysis_options.flutter.yaml
|
include: package:wyatt_analysis/analysis_options.flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude: "!example/**"
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
.buildlog/
|
.buildlog/
|
||||||
.history
|
.history
|
||||||
.svn/
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
# IntelliJ related
|
# IntelliJ related
|
||||||
*.iml
|
*.iml
|
||||||
@@ -45,4 +46,4 @@ app.*.map.json
|
|||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
lib/firebase_options.dart
|
firebase_options.dart
|
||||||
@@ -1,10 +1,30 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
# This file tracks properties of this Flutter project.
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
#
|
#
|
||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: 5464c5bac742001448fe4fc0597be939379f88ea
|
revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
||||||
|
base_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
||||||
|
- platform: ios
|
||||||
|
create_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
||||||
|
base_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
## 1.0.1
|
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# authentication_bloc_example
|
# example_router
|
||||||
|
|
||||||
A new Flutter project.
|
A new Flutter project.
|
||||||
|
|
||||||
@@ -8,9 +8,9 @@ This project is a starting point for a Flutter application.
|
|||||||
|
|
||||||
A few resources to get you started if this is your first Flutter project:
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
|
||||||
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
|
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||||
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
|
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||||
|
|
||||||
For help getting started with Flutter, view our
|
For help getting started with Flutter development, view the
|
||||||
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||||
samples, guidance on mobile development, and a full API reference.
|
samples, guidance on mobile development, and a full API reference.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion flutter.compileSdkVersion
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
@@ -46,7 +47,9 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "com.example.authentication_bloc_example"
|
applicationId "com.example.example_router"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion flutter.targetSdkVersion
|
targetSdkVersion flutter.targetSdkVersion
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
{
|
|
||||||
"project_info": {
|
|
||||||
"project_number": "136771801992",
|
|
||||||
"firebase_url": "https://tchat-beta.firebaseio.com",
|
|
||||||
"project_id": "tchat-beta",
|
|
||||||
"storage_bucket": "tchat-beta.appspot.com"
|
|
||||||
},
|
|
||||||
"client": [
|
|
||||||
{
|
|
||||||
"client_info": {
|
|
||||||
"mobilesdk_app_id": "1:136771801992:android:4ff419f07afdad6097203d",
|
|
||||||
"android_client_info": {
|
|
||||||
"package_name": "com.example.authentication_bloc_example"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-nps7ic22shstvgdv0c2ojvbkj3dkntum.apps.googleusercontent.com",
|
|
||||||
"client_type": 1,
|
|
||||||
"android_info": {
|
|
||||||
"package_name": "com.example.authentication_bloc_example",
|
|
||||||
"certificate_hash": "5d8790309e13b68c35e5d4d8437c35c6d15e6131"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"api_key": [
|
|
||||||
{
|
|
||||||
"current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"services": {
|
|
||||||
"appinvite_service": {
|
|
||||||
"other_platform_oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
|
|
||||||
"client_type": 2,
|
|
||||||
"ios_info": {
|
|
||||||
"bundle_id": "com.example.example"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_info": {
|
|
||||||
"mobilesdk_app_id": "1:136771801992:android:8482c9b90bc29de697203d",
|
|
||||||
"android_client_info": {
|
|
||||||
"package_name": "com.example.crud_bloc_example"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"api_key": [
|
|
||||||
{
|
|
||||||
"current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"services": {
|
|
||||||
"appinvite_service": {
|
|
||||||
"other_platform_oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
|
|
||||||
"client_type": 2,
|
|
||||||
"ios_info": {
|
|
||||||
"bundle_id": "com.example.example"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"configuration_version": "1"
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.example.authentication_bloc_example">
|
package="com.example.example_router">
|
||||||
<!-- Flutter needs it to communicate with the running application
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.example.authentication_bloc_example">
|
package="com.example.example_router">
|
||||||
<application
|
<application
|
||||||
android:label="authentication_bloc_example"
|
android:label="example_router"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
Flutter draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
Flutter draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.example.authentication_bloc_example">
|
package="com.example.example_router">
|
||||||
<!-- Flutter needs it to communicate with the running application
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||||
// START: FlutterFire Configuration
|
// START: FlutterFire Configuration
|
||||||
classpath 'com.google.gms:google-services:4.3.10'
|
classpath 'com.google.gms:google-services:4.3.10'
|
||||||
// END: FlutterFire Configuration
|
// END: FlutterFire Configuration
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 564 B After Width: | Height: | Size: 564 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |
@@ -1,182 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/constants.dart';
|
|
||||||
import 'package:authentication_bloc_example/home/home_page.dart';
|
|
||||||
import 'package:authentication_bloc_example/login/login_page.dart';
|
|
||||||
import 'package:authentication_bloc_example/model.dart';
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
|
||||||
|
|
||||||
class App extends StatelessWidget {
|
|
||||||
const App({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
static FormData getNormalFormData() {
|
|
||||||
return const FormData([
|
|
||||||
FormInput(formFieldName, Name.pure()),
|
|
||||||
FormInput(formFieldPhone, Phone.pure()),
|
|
||||||
FormInput(formFieldPro, Boolean.pure()),
|
|
||||||
FormInput(
|
|
||||||
formFieldConfirmedPassword,
|
|
||||||
ConfirmedPassword.pure(),
|
|
||||||
metadata: FormInputMetadata(export: false),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static FormData getProFormData() {
|
|
||||||
return const FormData([
|
|
||||||
FormInput(formFieldName, Name.pure()),
|
|
||||||
FormInput(formFieldPhone, Phone.pure()),
|
|
||||||
FormInput(formFieldPro, Boolean.pure()),
|
|
||||||
FormInput(formFieldSiren, Siren.pure()),
|
|
||||||
FormInput(formFieldIban, Iban.pure()),
|
|
||||||
FormInput(
|
|
||||||
formFieldConfirmedPassword,
|
|
||||||
ConfirmedPassword.pure(),
|
|
||||||
metadata: FormInputMetadata(export: false),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// On Authentication Success. (Authenticated or Anonymous)
|
|
||||||
// User callback.
|
|
||||||
Future<Map<String, dynamic>> onAuthSuccess(UserInterface user) async {
|
|
||||||
if (user.isNotEmpty && !user.isAnonymous) {
|
|
||||||
// Check if user is register in Firesore.
|
|
||||||
DocumentSnapshot firestoreUser = await FirebaseFirestore.instance
|
|
||||||
.collection(firestoreCollectionUsers)
|
|
||||||
.doc(user.uid)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (!firestoreUser.exists) {
|
|
||||||
// Register user in Firestore when sign in with social account.
|
|
||||||
final uid = user.uid;
|
|
||||||
final u = {'uid': uid, 'email': user.email};
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection(firestoreCollectionUsers)
|
|
||||||
.doc(uid)
|
|
||||||
.set(u);
|
|
||||||
return {
|
|
||||||
'user': UserFirestore(
|
|
||||||
uid: uid,
|
|
||||||
email: user.email ?? '',
|
|
||||||
name: user.displayName ?? '',
|
|
||||||
phone: user.phoneNumber ?? ''),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'user': UserFirestore.fromMap(
|
|
||||||
firestoreUser.data() as Map<String, dynamic>),
|
|
||||||
...firestoreUser.data() as Map<String, dynamic>? ?? {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// On Sign Up Success.
|
|
||||||
Future<void> onSignUpSuccess(SignUpState state, String? uid) async {
|
|
||||||
if (uid != null) {
|
|
||||||
final data = state.data.toMap();
|
|
||||||
final user = {'uid': uid, 'email': state.email.value, ...data};
|
|
||||||
log('onSignUpSuccess: $user');
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection(firestoreCollectionUsers)
|
|
||||||
.doc(uid)
|
|
||||||
.set(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
AuthenticationRepositoryInterface authenticationRepository =
|
|
||||||
AuthenticationRepositoryFirebase();
|
|
||||||
|
|
||||||
AuthenticationCubit authenticationCubit = AuthenticationCubit(
|
|
||||||
authenticationRepository: authenticationRepository,
|
|
||||||
onAuthSuccess: onAuthSuccess,
|
|
||||||
);
|
|
||||||
|
|
||||||
SignUpCubit signUpCubit = SignUpCubit(
|
|
||||||
authenticationRepository: authenticationRepository,
|
|
||||||
authenticationCubit: authenticationCubit,
|
|
||||||
entries: getNormalFormData(),
|
|
||||||
onSignUpSuccess: onSignUpSuccess,
|
|
||||||
);
|
|
||||||
|
|
||||||
SignInCubit signInCubit = SignInCubit(
|
|
||||||
authenticationRepository: authenticationRepository,
|
|
||||||
authenticationCubit: authenticationCubit,
|
|
||||||
);
|
|
||||||
|
|
||||||
return MultiRepositoryProvider(
|
|
||||||
providers: [
|
|
||||||
RepositoryProvider<AuthenticationRepositoryInterface>(
|
|
||||||
create: (context) => authenticationRepository,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: MultiBlocProvider(
|
|
||||||
providers: [
|
|
||||||
BlocProvider<AuthenticationCubit>(
|
|
||||||
create: (context) => authenticationCubit..init(),
|
|
||||||
),
|
|
||||||
BlocProvider<SignUpCubit>(
|
|
||||||
create: (context) => signUpCubit,
|
|
||||||
),
|
|
||||||
BlocProvider<SignInCubit>(
|
|
||||||
create: (context) => signInCubit,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: const AppView(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppView extends StatelessWidget {
|
|
||||||
const AppView({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return MaterialApp(
|
|
||||||
theme: ThemeData(
|
|
||||||
primarySwatch: Colors.blue,
|
|
||||||
buttonTheme: const ButtonThemeData(
|
|
||||||
buttonColor: Colors.blue,
|
|
||||||
textTheme: ButtonTextTheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
home: AuthenticationBuilder(
|
|
||||||
unknown: (context) {
|
|
||||||
return const Scaffold(
|
|
||||||
body: Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
authenticated: (context, user, userData) => const HomePage(),
|
|
||||||
unauthenticated: (context) => const LoginPage(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// -----
|
// -----
|
||||||
// File: bootstrap.dart
|
// File: bootstrap.dart
|
||||||
// Created Date: 19/08/2022 15:05:17
|
// Created Date: 19/08/2022 15:05:17
|
||||||
// Last Modified: Wed Nov 09 2022
|
// Last Modified: Fri Nov 11 2022
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2022
|
// Copyright (c) 2022
|
||||||
|
|
||||||
@@ -11,8 +11,6 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:example_router/core/dependency_injection/get_it.dart';
|
import 'package:example_router/core/dependency_injection/get_it.dart';
|
||||||
import 'package:example_router/core/utils/app_bloc_observer.dart';
|
import 'package:example_router/core/utils/app_bloc_observer.dart';
|
||||||
import 'package:example_router/firebase_options.dart';
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
@@ -25,11 +23,6 @@ Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
|
|||||||
FlutterError.onError = (details) {
|
FlutterError.onError = (details) {
|
||||||
debugPrint(details.toString());
|
debugPrint(details.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
await Firebase.initializeApp(
|
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
);
|
|
||||||
|
|
||||||
await GetItInitializer.init();
|
await GetItInitializer.init();
|
||||||
|
|
||||||
runApp(await builder());
|
runApp(await builder());
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
|
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
||||||
|
|
||||||
final getIt = GetIt.I;
|
final getIt = GetIt.I;
|
||||||
|
|
||||||
@@ -23,12 +24,33 @@ abstract class GetItInitializer {
|
|||||||
static Future<void> init() async {
|
static Future<void> init() async {
|
||||||
getIt
|
getIt
|
||||||
..registerLazySingleton<AuthenticationRemoteDataSource>(
|
..registerLazySingleton<AuthenticationRemoteDataSource>(
|
||||||
() => AuthenticationFirebaseDataSourceImpl(),
|
() => AuthenticationMockDataSourceImpl(registeredAccounts: [
|
||||||
|
Pair(
|
||||||
|
AccountModel(
|
||||||
|
uid: '1',
|
||||||
|
emailVerified: true,
|
||||||
|
isAnonymous: false,
|
||||||
|
providerId: 'wyatt',
|
||||||
|
email: 'toto@test.fr',
|
||||||
|
),
|
||||||
|
'toto1234',
|
||||||
|
),
|
||||||
|
Pair(
|
||||||
|
AccountModel(
|
||||||
|
uid: '2',
|
||||||
|
emailVerified: false,
|
||||||
|
isAnonymous: false,
|
||||||
|
providerId: 'wyatt',
|
||||||
|
email: 'tata@test.fr',
|
||||||
|
),
|
||||||
|
'tata1234',
|
||||||
|
),
|
||||||
|
]),
|
||||||
)
|
)
|
||||||
..registerLazySingleton<AuthenticationCacheDataSource<int>>(
|
..registerLazySingleton<AuthenticationCacheDataSource<int>>(
|
||||||
() => AuthenticationCacheDataSourceImpl<int>(),
|
() => AuthenticationCacheDataSourceImpl<int>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
await getIt.allReady();
|
await getIt.allReady();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/forgot_password/widgets/forgot_password_form.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
class ForgotPasswordPage extends StatelessWidget {
|
|
||||||
const ForgotPasswordPage({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
static Route route() {
|
|
||||||
return MaterialPageRoute<void>(builder: (_) => const ForgotPasswordPage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(title: const Text('Forgot Password')),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: BlocProvider(
|
|
||||||
create: (_) => PasswordResetCubit(
|
|
||||||
context.read<AuthenticationRepositoryInterface>(),
|
|
||||||
),
|
|
||||||
child: const ForgotPasswordForm(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
|
||||||
|
|
||||||
class _EmailInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<PasswordResetCubit, PasswordResetState>(
|
|
||||||
buildWhen: (previous, current) => previous.email != current.email,
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (email) =>
|
|
||||||
context.read<PasswordResetCubit>().emailChanged(email),
|
|
||||||
keyboardType: TextInputType.emailAddress,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'email',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.email.invalid ? 'invalid email' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ResetButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<PasswordResetCubit, PasswordResetState>(
|
|
||||||
buildWhen: (previous, current) => previous.status != current.status,
|
|
||||||
builder: (context, state) {
|
|
||||||
return state.status.isSubmissionInProgress
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: ElevatedButton(
|
|
||||||
onPressed: state.status.isValidated
|
|
||||||
? () => context
|
|
||||||
.read<PasswordResetCubit>()
|
|
||||||
.sendPasswordResetEmail()
|
|
||||||
: null,
|
|
||||||
child: const Text('SEND EMAIL'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ForgotPasswordForm extends StatelessWidget {
|
|
||||||
const ForgotPasswordForm({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocListener<PasswordResetCubit, PasswordResetState>(
|
|
||||||
listener: (context, state) {
|
|
||||||
if (state.status.isSubmissionSuccess) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
} else if (state.status.isSubmissionFailure) {
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
..hideCurrentSnackBar()
|
|
||||||
..showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(state.errorMessage ?? 'Password reset Failure'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Align(
|
|
||||||
alignment: const Alignment(0, -1 / 3),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [_EmailInput(), const SizedBox(height: 8), _ResetButton()],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/home/widgets/infos.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/home/widgets/email_verification.dart';
|
|
||||||
|
|
||||||
class HomePage extends StatelessWidget {
|
|
||||||
const HomePage({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
static Route route() {
|
|
||||||
return MaterialPageRoute<void>(builder: (_) => const HomePage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final user = context.select((AuthenticationCubit cubit) => cubit.state.user);
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Home'),
|
|
||||||
actions: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.exit_to_app),
|
|
||||||
onPressed: () => context
|
|
||||||
.read<AuthenticationCubit>()
|
|
||||||
.logOut(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: BlocProvider(
|
|
||||||
create: (_) => EmailVerificationCubit(
|
|
||||||
context.read<AuthenticationRepositoryInterface>(),
|
|
||||||
)..checkEmailVerification(),
|
|
||||||
child: BlocBuilder<EmailVerificationCubit, EmailVerificationState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state.isVerified || user!.isAnonymous) {
|
|
||||||
return const UserInfo();
|
|
||||||
} else {
|
|
||||||
return const EmailVerification();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
class EmailVerification extends StatelessWidget {
|
|
||||||
const EmailVerification({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final user = context.select((AuthenticationCubit cubit) => cubit.state.user);
|
|
||||||
final userData = context.select((AuthenticationCubit cubit) => cubit.state.userData);
|
|
||||||
return Align(
|
|
||||||
alignment: const Alignment(0, -1 / 3),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
Text("Hello ${userData!['name'] ?? 'null'}!"),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("Email '${user?.email ?? 'null'}' is not verified"),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.read<EmailVerificationCubit>().sendEmailVerification();
|
|
||||||
},
|
|
||||||
child: const Text('(Re)send email'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.read<EmailVerificationCubit>().checkEmailVerification();
|
|
||||||
},
|
|
||||||
child: const Text('Refresh'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/home/widgets/avatar.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:authentication_bloc_example/constants.dart';
|
|
||||||
|
|
||||||
class UserInfo extends StatelessWidget {
|
|
||||||
const UserInfo({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final textTheme = Theme.of(context).textTheme;
|
|
||||||
final user = context.select((AuthenticationCubit cubit) => cubit.state.user);
|
|
||||||
final userData = context.select((AuthenticationCubit cubit) => cubit.state.userData);
|
|
||||||
return Align(
|
|
||||||
alignment: const Alignment(0, -1 / 3),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
Avatar(photo: user?.photoURL),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("Email: ${user?.email ?? 'null'}", style: textTheme.headline6),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("Name: ${userData![formFieldName] ?? 'null'}",
|
|
||||||
style: textTheme.headline6),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("Phone: ${userData[formFieldPhone] ?? 'null'}",
|
|
||||||
style: textTheme.headline6),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("IsPro: ${userData[formFieldPro] ?? 'null'}",
|
|
||||||
style: textTheme.headline6),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("IsAnonymous: ${user?.isAnonymous.toString() ?? 'null'}",
|
|
||||||
style: textTheme.headline6),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text("IsEmailVerified: ${user?.emailVerified.toString() ?? 'null'}",
|
|
||||||
style: textTheme.headline6),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/app/app.dart';
|
|
||||||
import 'package:authentication_bloc_example/constants.dart';
|
|
||||||
import 'package:authentication_bloc_example/forgot_password/forgot_password_page.dart';
|
|
||||||
import 'package:authentication_bloc_example/sign_up/sign_up_page.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
|
||||||
|
|
||||||
class _EmailInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignInCubit, SignInState>(
|
|
||||||
buildWhen: (previous, current) => previous.email != current.email,
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (email) => context.read<SignInCubit>().emailChanged(email),
|
|
||||||
keyboardType: TextInputType.emailAddress,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'email',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.email.invalid ? 'invalid email' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PasswordInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignInCubit, SignInState>(
|
|
||||||
buildWhen: (previous, current) => previous.password != current.password,
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (password) =>
|
|
||||||
context.read<SignInCubit>().passwordChanged(password),
|
|
||||||
obscureText: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'password',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.password.invalid ? 'invalid password' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LoginAnonButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignInCubit, SignInState>(
|
|
||||||
buildWhen: (previous, current) => previous.status != current.status,
|
|
||||||
builder: (context, state) {
|
|
||||||
return state.status.isSubmissionInProgress
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: ElevatedButton(
|
|
||||||
onPressed: () =>
|
|
||||||
context.read<SignInCubit>().signInAnonymously(),
|
|
||||||
child: const Text('LOGIN ANONYMOUSLY'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LoginWithPasswordButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignInCubit, SignInState>(
|
|
||||||
buildWhen: (previous, current) => previous.status != current.status,
|
|
||||||
builder: (context, state) {
|
|
||||||
return state.status.isSubmissionInProgress
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: ElevatedButton(
|
|
||||||
onPressed: state.status.isValidated
|
|
||||||
? () =>
|
|
||||||
context.read<SignInCubit>().signInWithEmailAndPassword()
|
|
||||||
: null,
|
|
||||||
child: const Text('LOGIN WITH PASSWORD'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LoginWithGoogleButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignInCubit, SignInState>(
|
|
||||||
buildWhen: (previous, current) => previous.status != current.status,
|
|
||||||
builder: (context, state) {
|
|
||||||
return state.status.isSubmissionInProgress
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: ElevatedButton(
|
|
||||||
onPressed: () =>
|
|
||||||
context.read<SignInCubit>().signInWithGoogle(),
|
|
||||||
child: const Text('LOGIN GOOGLE'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SignUpButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
return TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.read<SignUpCubit>().updateFormData(App.getNormalFormData());
|
|
||||||
Navigator.of(context).push<void>(SignUpPage.route());
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'CREATE ACCOUNT',
|
|
||||||
style: TextStyle(color: theme.primaryColor),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SignUpAsProButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
return TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.read<SignUpCubit>().updateFormData(App.getProFormData());
|
|
||||||
context.read<SignUpCubit>().dataChanged(
|
|
||||||
formFieldPro,
|
|
||||||
const Boolean.dirty(value: true),
|
|
||||||
);
|
|
||||||
Navigator.of(context).push<void>(SignUpPage.route());
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'CREATE PRO ACCOUNT',
|
|
||||||
style: TextStyle(color: theme.primaryColor),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoginForm extends StatelessWidget {
|
|
||||||
const LoginForm({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocListener<SignInCubit, SignInState>(
|
|
||||||
listener: (context, state) {
|
|
||||||
if (state.status.isSubmissionFailure) {
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
..hideCurrentSnackBar()
|
|
||||||
..showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(state.errorMessage ?? 'Authentication Failure'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Align(
|
|
||||||
alignment: const Alignment(0, -1 / 3),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 120),
|
|
||||||
_EmailInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_PasswordInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).push(ForgotPasswordPage.route());
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'Forgot password ?',
|
|
||||||
style: Theme.of(context).textTheme.bodyText2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_LoginWithPasswordButton(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_LoginAnonButton(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_LoginWithGoogleButton(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_SignUpButton(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_SignUpAsProButton(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +1,6 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
import 'package:example_router/bootstrap.dart';
|
||||||
// Please see the AUTHORS file for details.
|
import 'package:example_router/presentation/features/app/app.dart';
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/app/app.dart';
|
void main() {
|
||||||
import 'package:authentication_bloc_example/app/bloc_observer.dart';
|
bootstrap(App.new);
|
||||||
import 'package:authentication_bloc_example/firebase_options.dart';
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
Future<void> main() async {
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
await Firebase.initializeApp(
|
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
);
|
|
||||||
BlocOverrides.runZoned(
|
|
||||||
() => runApp(
|
|
||||||
const App(),
|
|
||||||
),
|
|
||||||
blocObserver: AppBlocObserver(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,11 @@
|
|||||||
// -----
|
// -----
|
||||||
// File: sign_in_form.dart
|
// File: sign_in_form.dart
|
||||||
// Created Date: 19/08/2022 15:24:37
|
// Created Date: 19/08/2022 15:24:37
|
||||||
// Last Modified: Thu Nov 10 2022
|
// Last Modified: Wed Nov 16 2022
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2022
|
// Copyright (c) 2022
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
@@ -17,14 +16,14 @@ class _EmailInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InputBuilder<SignInCubit<int>>(
|
return InputBuilder<SignInCubit<int>>(
|
||||||
field: AuthFormField.email,
|
field: AuthFormField.email,
|
||||||
builder: ((context, cubit, state, field, inputValid) {
|
builder: ((context, cubit, state, field, input) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (email) => cubit.emailChanged(email),
|
onChanged: (email) => cubit.emailChanged(email),
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Email',
|
labelText: 'Email',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: !inputValid ? 'Invalid email' : null,
|
errorText: input.validator.invalid ? 'Invalid email' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -37,14 +36,14 @@ class _PasswordInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InputBuilder<SignInCubit<int>>(
|
return InputBuilder<SignInCubit<int>>(
|
||||||
field: AuthFormField.password,
|
field: AuthFormField.password,
|
||||||
builder: ((context, cubit, state, field, inputValid) {
|
builder: ((context, cubit, state, field, input) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (pwd) => cubit.passwordChanged(pwd),
|
onChanged: (pwd) => cubit.passwordChanged(pwd),
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Password',
|
labelText: 'Password',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: !inputValid ? 'Invalid password' : null,
|
errorText: input.validator.invalid ? 'Invalid password' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -60,8 +59,24 @@ class _SignInButton extends StatelessWidget {
|
|||||||
return status.isSubmissionInProgress
|
return status.isSubmissionInProgress
|
||||||
? const CircularProgressIndicator()
|
? const CircularProgressIndicator()
|
||||||
: ElevatedButton(
|
: ElevatedButton(
|
||||||
onPressed: status.isValidated ? () => cubit.submit() : null,
|
onPressed: status.isValidated ? () => cubit.signInWithEmailAndPassword() : null,
|
||||||
child: const Text('Sign in'),
|
child: const Text('Sign in with credentials'),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SignInAnonymouslyButton extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SubmitBuilder<SignInCubit<int>>(
|
||||||
|
builder: ((context, cubit, status) {
|
||||||
|
return status.isSubmissionInProgress
|
||||||
|
? const CircularProgressIndicator()
|
||||||
|
: ElevatedButton(
|
||||||
|
onPressed: () => cubit.signInAnonymously(),
|
||||||
|
child: const Text('Sign in anonymously'),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -73,24 +88,23 @@ class SignInForm extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocListener<SignInCubit<int>, SignInState>(
|
return SignInListener<int>(
|
||||||
listener: (context, state) {
|
onError: (context, status, errorMessage) => ScaffoldMessenger.of(context)
|
||||||
if (state.status.isSubmissionFailure) {
|
..hideCurrentSnackBar()
|
||||||
ScaffoldMessenger.of(context)
|
..showSnackBar(
|
||||||
..hideCurrentSnackBar()
|
SnackBar(content: Text(errorMessage ?? 'Sign In Failure')),
|
||||||
..showSnackBar(
|
),
|
||||||
SnackBar(content: Text(state.errorMessage ?? 'Sign In Failure')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
_EmailInput(),
|
_EmailInput(),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_PasswordInput(),
|
_PasswordInput(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_SignInButton(),
|
_SignInButton(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_SignInAnonymouslyButton(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -3,13 +3,12 @@
|
|||||||
// -----
|
// -----
|
||||||
// File: sign_up_form.dart
|
// File: sign_up_form.dart
|
||||||
// Created Date: 19/08/2022 14:41:08
|
// Created Date: 19/08/2022 14:41:08
|
||||||
// Last Modified: Thu Nov 10 2022
|
// Last Modified: Wed Nov 16 2022
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2022
|
// Copyright (c) 2022
|
||||||
|
|
||||||
import 'package:example_router/core/constants/form_field.dart';
|
import 'package:example_router/core/constants/form_field.dart';
|
||||||
import 'package:flutter/material.dart' hide FormField;
|
import 'package:flutter/material.dart' hide FormField;
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
@@ -18,14 +17,14 @@ class _EmailInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InputBuilder<SignUpCubit<int>>(
|
return InputBuilder<SignUpCubit<int>>(
|
||||||
field: AuthFormField.email,
|
field: AuthFormField.email,
|
||||||
builder: ((context, cubit, state, field, inputValid) {
|
builder: ((context, cubit, state, field, input) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (email) => cubit.emailChanged(email),
|
onChanged: (email) => cubit.emailChanged(email),
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Email',
|
labelText: 'Email',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: !inputValid ? 'Invalid email' : null,
|
errorText: input.validator.invalid ? 'Invalid email' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -38,7 +37,7 @@ class _PasswordInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InputBuilder<SignUpCubit<int>>(
|
return InputBuilder<SignUpCubit<int>>(
|
||||||
field: AuthFormField.password,
|
field: AuthFormField.password,
|
||||||
builder: ((context, cubit, state, field, inputValid) {
|
builder: ((context, cubit, state, field, input) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (pwd) {
|
onChanged: (pwd) {
|
||||||
cubit.passwordChanged(pwd);
|
cubit.passwordChanged(pwd);
|
||||||
@@ -53,7 +52,7 @@ class _PasswordInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Password',
|
labelText: 'Password',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: !inputValid ? 'Invalid password' : null,
|
errorText: input.validator.invalid ? 'Invalid password' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -66,7 +65,7 @@ class _ConfirmPasswordInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InputBuilder<SignUpCubit<int>>(
|
return InputBuilder<SignUpCubit<int>>(
|
||||||
field: AppFormField.confirmedPassword,
|
field: AppFormField.confirmedPassword,
|
||||||
builder: ((context, cubit, state, field, inputValid) {
|
builder: ((context, cubit, state, field, input) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (pwd) {
|
onChanged: (pwd) {
|
||||||
cubit.dataChanged(
|
cubit.dataChanged(
|
||||||
@@ -81,7 +80,8 @@ class _ConfirmPasswordInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Confirm password',
|
labelText: 'Confirm password',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: !inputValid ? 'Passwords do not match' : null,
|
errorText:
|
||||||
|
input.validator.invalid ? 'Passwords do not match' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -110,29 +110,25 @@ class SignUpForm extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocListener<SignUpCubit<int>, SignUpState>(
|
return SignUpListener<int>(
|
||||||
listener: (context, state) {
|
onError: (context, status, errorMessage) =>
|
||||||
if (state.status.isSubmissionFailure) {
|
ScaffoldMessenger.of(context)
|
||||||
ScaffoldMessenger.of(context)
|
..hideCurrentSnackBar()
|
||||||
..hideCurrentSnackBar()
|
..showSnackBar(
|
||||||
..showSnackBar(
|
SnackBar(content: Text(errorMessage ?? 'Sign Up Failure')),
|
||||||
SnackBar(content: Text(state.errorMessage ?? 'Sign Up Failure')),
|
),
|
||||||
);
|
child: SingleChildScrollView(
|
||||||
}
|
child: Column(
|
||||||
},
|
children: [
|
||||||
child: SingleChildScrollView(
|
_EmailInput(),
|
||||||
child: Column(
|
const SizedBox(height: 8),
|
||||||
children: [
|
_PasswordInput(),
|
||||||
_EmailInput(),
|
const SizedBox(height: 8),
|
||||||
const SizedBox(height: 8),
|
_ConfirmPasswordInput(),
|
||||||
_PasswordInput(),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 8),
|
_SignUpButton(),
|
||||||
_ConfirmPasswordInput(),
|
],
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
_SignUpButton(),
|
));
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:authentication_bloc_example/app/app.dart';
|
|
||||||
import 'package:authentication_bloc_example/constants.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
|
||||||
|
|
||||||
class _NameInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (name) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged(formFieldName, Name.dirty(name)),
|
|
||||||
keyboardType: TextInputType.name,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'name',
|
|
||||||
helperText: '',
|
|
||||||
errorText:
|
|
||||||
state.data.isNotValid(formFieldName) ? 'invalid name' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PhoneInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (phone) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged(formFieldPhone, Phone.dirty(phone)),
|
|
||||||
keyboardType: TextInputType.phone,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'phone',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.data.isNotValid(formFieldPhone)
|
|
||||||
? 'invalid phone'
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SirenInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (siren) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged(formFieldSiren, Siren.dirty(siren)),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'siren',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.data.isNotValid(formFieldSiren)
|
|
||||||
? 'invalid SIREN'
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _IbanInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (iban) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged(formFieldIban, Iban.dirty(iban)),
|
|
||||||
keyboardType: TextInputType.text,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'iban',
|
|
||||||
helperText: '',
|
|
||||||
errorText:
|
|
||||||
state.data.isNotValid(formFieldIban) ? 'invalid IBAN' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _EmailInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
buildWhen: (previous, current) => previous.email != current.email,
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (email) => context.read<SignUpCubit>().emailChanged(email),
|
|
||||||
keyboardType: TextInputType.emailAddress,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'email',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.email.invalid ? 'invalid email' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PasswordInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (password) {
|
|
||||||
context.read<SignUpCubit>().passwordChanged(password);
|
|
||||||
context.read<SignUpCubit>().dataChanged(
|
|
||||||
formFieldConfirmedPassword,
|
|
||||||
ConfirmedPassword.dirty(
|
|
||||||
password: password,
|
|
||||||
value: context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.state
|
|
||||||
.data
|
|
||||||
.valueOf<String>(formFieldConfirmedPassword),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
obscureText: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'password',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.password.invalid ? 'invalid password' : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ConfirmPasswordInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return TextField(
|
|
||||||
onChanged: (confirmPassword) => context
|
|
||||||
.read<SignUpCubit>()
|
|
||||||
.dataChanged(
|
|
||||||
formFieldConfirmedPassword,
|
|
||||||
ConfirmedPassword.dirty(
|
|
||||||
password: context.read<SignUpCubit>().state.password.value,
|
|
||||||
value: confirmPassword,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
obscureText: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'confirm password',
|
|
||||||
helperText: '',
|
|
||||||
errorText: state.data.isNotValid(formFieldConfirmedPassword)
|
|
||||||
? 'passwords do not match'
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CheckIsProInput extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ListTile(
|
|
||||||
title: const Text('Are you a pro?'),
|
|
||||||
trailing: BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return Checkbox(
|
|
||||||
value: state.data.valueOf<bool>(formFieldPro),
|
|
||||||
onChanged: (isPro) {
|
|
||||||
final value =
|
|
||||||
isPro!; // tristate is false, so value can't be null
|
|
||||||
|
|
||||||
context.read<SignUpCubit>().dataChanged(
|
|
||||||
formFieldPro,
|
|
||||||
Boolean.dirty(value: value),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
context.read<SignUpCubit>().updateFormData(
|
|
||||||
App.getProFormData(),
|
|
||||||
operation: SetOperation.union);
|
|
||||||
} else {
|
|
||||||
context.read<SignUpCubit>().updateFormData(
|
|
||||||
App.getNormalFormData(),
|
|
||||||
operation: SetOperation.intersection);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SignUpButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
buildWhen: (previous, current) => previous.status != current.status,
|
|
||||||
builder: (context, state) {
|
|
||||||
return state.status.isSubmissionInProgress
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: ElevatedButton(
|
|
||||||
onPressed: state.status.isValidated
|
|
||||||
? () => context.read<SignUpCubit>().signUpFormSubmitted()
|
|
||||||
: null,
|
|
||||||
child: const Text('SIGN UP'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DebugButton extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// log(state.toString());
|
|
||||||
log(state.data.toMap().toString());
|
|
||||||
},
|
|
||||||
child: const Text('DEBUG'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SignUpForm extends StatelessWidget {
|
|
||||||
const SignUpForm({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocListener<SignUpCubit, SignUpState>(
|
|
||||||
listener: (context, state) {
|
|
||||||
if (state.status.isSubmissionSuccess) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
} else if (state.status.isSubmissionFailure) {
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
..hideCurrentSnackBar()
|
|
||||||
..showSnackBar(
|
|
||||||
SnackBar(content: Text(state.errorMessage ?? 'Sign Up Failure')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height,
|
|
||||||
child: Align(
|
|
||||||
alignment: const Alignment(0, -1 / 3),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
_NameInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_PhoneInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_CheckIsProInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
BlocBuilder<SignUpCubit, SignUpState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state.data.valueOf<bool>(formFieldPro)) {
|
|
||||||
return Column(children: [
|
|
||||||
_SirenInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_IbanInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_EmailInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_PasswordInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_ConfirmPasswordInput(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_SignUpButton(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_DebugButton(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: authentication_bloc_example
|
name: example_router
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
|
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
@@ -15,10 +15,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.0.1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.16.2 <3.0.0"
|
sdk: ">=2.17.6 <3.0.0"
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
@@ -29,22 +29,30 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
go_router: ^5.1.5
|
||||||
|
firebase_core: ^2.1.1
|
||||||
|
flutter_bloc: ^8.1.1
|
||||||
|
get_it: ^7.2.0
|
||||||
|
|
||||||
firebase_core: ^1.14.1
|
|
||||||
flutter_bloc: ^8.0.1
|
|
||||||
wyatt_authentication_bloc:
|
wyatt_authentication_bloc:
|
||||||
path: "../"
|
path: "../"
|
||||||
|
|
||||||
wyatt_form_bloc:
|
wyatt_form_bloc:
|
||||||
git:
|
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
version: 0.2.0
|
||||||
ref: wyatt_form_bloc-v0.1.0+1
|
|
||||||
path: packages/wyatt_form_bloc
|
wyatt_architecture:
|
||||||
|
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
||||||
|
version: 0.0.2
|
||||||
|
|
||||||
|
wyatt_type_utils:
|
||||||
|
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
|
||||||
|
version: 0.0.3+1
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.4
|
cupertino_icons: ^1.0.5
|
||||||
cloud_firestore: ^3.1.12
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -60,7 +68,7 @@ dev_dependencies:
|
|||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
# The following section is specific to Flutter.
|
# The following section is specific to Flutter packages.
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
# The following line ensures that the Material Icons font is
|
# The following line ensures that the Material Icons font is
|
||||||
@@ -74,7 +82,7 @@ flutter:
|
|||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|
||||||
# For details regarding adding assets from package dependencies, see
|
# For details regarding adding assets from package dependencies, see
|
||||||
# https://flutter.dev/assets-and-images/#from-packages
|
# https://flutter.dev/assets-and-images/#from-packages
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
// File generated by FlutterFire CLI.
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
|
|
||||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
|
||||||
import 'package:flutter/foundation.dart'
|
|
||||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
|
||||||
|
|
||||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```dart
|
|
||||||
/// import 'firebase_options.dart';
|
|
||||||
/// // ...
|
|
||||||
/// await Firebase.initializeApp(
|
|
||||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
class DefaultFirebaseOptions {
|
|
||||||
static FirebaseOptions get currentPlatform {
|
|
||||||
if (kIsWeb) {
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for web - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
switch (defaultTargetPlatform) {
|
|
||||||
case TargetPlatform.android:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for android - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
case TargetPlatform.iOS:
|
|
||||||
return ios;
|
|
||||||
case TargetPlatform.macOS:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for macos - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
case TargetPlatform.windows:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for windows - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
case TargetPlatform.linux:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for linux - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions are not supported for this platform.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const FirebaseOptions ios = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyCDbbhjbFrQwLXuIANdJzjkDk8uOETnn7w',
|
|
||||||
appId: '1:136771801992:ios:bcdca68d2b7d227097203d',
|
|
||||||
messagingSenderId: '136771801992',
|
|
||||||
projectId: 'tchat-beta',
|
|
||||||
databaseURL: 'https://tchat-beta.firebaseio.com',
|
|
||||||
storageBucket: 'tchat-beta.appspot.com',
|
|
||||||
androidClientId: '136771801992-n2pq8oqutvrqj58e05hbavvc7n1jdfjb.apps.googleusercontent.com',
|
|
||||||
iosClientId: '136771801992-p629tpo9bk3hcm2955s5ahivdla57ln9.apps.googleusercontent.com',
|
|
||||||
iosBundleId: 'com.example.exampleRouter',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import 'package:example_router/bootstrap.dart';
|
|
||||||
import 'package:example_router/presentation/features/app/app.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
bootstrap(App.new);
|
|
||||||
}
|
|
||||||
@@ -17,4 +17,5 @@
|
|||||||
abstract class AuthFormName {
|
abstract class AuthFormName {
|
||||||
static const String signUpForm = 'wyattSignUpForm';
|
static const String signUpForm = 'wyattSignUpForm';
|
||||||
static const String signInForm = 'wyattSignInForm';
|
static const String signInForm = 'wyattSignInForm';
|
||||||
|
static const String passwordResetForm = 'wyattPasswordResetForm';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ abstract class AuthenticationFailureInterface extends AppException
|
|||||||
String code;
|
String code;
|
||||||
String msg;
|
String msg;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get message => msg;
|
||||||
|
|
||||||
AuthenticationFailureInterface(this.code, this.msg);
|
AuthenticationFailureInterface(this.code, this.msg);
|
||||||
AuthenticationFailureInterface.fromCode(this.code)
|
AuthenticationFailureInterface.fromCode(this.code)
|
||||||
: msg = 'An unknown error occurred.';
|
: msg = 'An unknown error occurred.';
|
||||||
@@ -246,3 +249,10 @@ abstract class SignOutFailureInterface extends AuthenticationFailureInterface {
|
|||||||
/// {@macro sign_out_failure}
|
/// {@macro sign_out_failure}
|
||||||
SignOutFailureInterface.fromCode(super.code) : super.fromCode();
|
SignOutFailureInterface.fromCode(super.code) : super.fromCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class GetIdTokenFailureInterface
|
||||||
|
extends AuthenticationFailureInterface {
|
||||||
|
GetIdTokenFailureInterface(super.code, super.msg);
|
||||||
|
|
||||||
|
GetIdTokenFailureInterface.fromCode(super.code) : super.fromCode();
|
||||||
|
}
|
||||||
|
|||||||
@@ -270,3 +270,10 @@ class SignOutFailureFirebase extends SignOutFailureInterface {
|
|||||||
|
|
||||||
SignOutFailureFirebase.fromCode(super.code) : super.fromCode();
|
SignOutFailureFirebase.fromCode(super.code) : super.fromCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GetIdTokenFailureFirebase extends GetIdTokenFailureInterface {
|
||||||
|
GetIdTokenFailureFirebase([String? code, String? msg])
|
||||||
|
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
|
||||||
|
|
||||||
|
GetIdTokenFailureFirebase.fromCode(super.code) : super.fromCode();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// any later version.
|
// any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export 'local/authentication_cache_data_source_impl.dart';
|
export 'local/authentication_cache_data_source_impl.dart';
|
||||||
export 'remote/authentication_firebase_data_source_impl.dart';
|
export 'remote/authentication_firebase_data_source_impl.dart';
|
||||||
|
export 'remote/authentication_mock_data_source_impl.dart';
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import 'package:wyatt_architecture/wyatt_architecture.dart';
|
|
||||||
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
|
||||||
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
||||||
|
|
||||||
@@ -28,7 +27,18 @@ class AuthenticationFirebaseDataSourceImpl
|
|||||||
|
|
||||||
Account _mapper(User user) => AccountModel(
|
Account _mapper(User user) => AccountModel(
|
||||||
uid: user.uid,
|
uid: user.uid,
|
||||||
|
emailVerified: user.emailVerified,
|
||||||
|
isAnonymous: user.isAnonymous,
|
||||||
|
providerId: user.providerData.first.providerId,
|
||||||
|
creationTime: user.metadata.creationTime,
|
||||||
|
lastSignInTime: user.metadata.lastSignInTime,
|
||||||
|
isNewUser: (user.metadata.creationTime != null &&
|
||||||
|
user.metadata.lastSignInTime != null)
|
||||||
|
? user.metadata.lastSignInTime! == user.metadata.creationTime!
|
||||||
|
: null,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
phoneNumber: user.phoneNumber,
|
||||||
|
photoURL: user.photoURL,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -55,6 +65,8 @@ class AuthenticationFirebaseDataSourceImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
||||||
|
/// {@macro signup}
|
||||||
Future<Account> signUp({
|
Future<Account> signUp({
|
||||||
required String email,
|
required String email,
|
||||||
required String password,
|
required String password,
|
||||||
@@ -95,9 +107,10 @@ class AuthenticationFirebaseDataSourceImpl
|
|||||||
} else {
|
} else {
|
||||||
throw Exception(); // Get caught just after.
|
throw Exception(); // Get caught just after.
|
||||||
}
|
}
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw GetIdTokenFailureFirebase.fromCode(e.code);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// TODO(hpcl): implement a non ambiguous exception for this case
|
throw GetIdTokenFailureFirebase();
|
||||||
throw ServerException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,4 +120,83 @@ class AuthenticationFirebaseDataSourceImpl
|
|||||||
final Account? account = (user.isNotNull) ? _mapper(user!) : null;
|
final Account? account = (user.isNotNull) ? _mapper(user!) : null;
|
||||||
return account;
|
return account;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> confirmPasswordReset({
|
||||||
|
required String code,
|
||||||
|
required String newPassword,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
await _firebaseAuth.confirmPasswordReset(
|
||||||
|
code: code,
|
||||||
|
newPassword: newPassword,
|
||||||
|
);
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw ConfirmPasswordResetFailureFirebase.fromCode(e.code);
|
||||||
|
} catch (_) {
|
||||||
|
throw ConfirmPasswordResetFailureFirebase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendEmailVerification() async {
|
||||||
|
try {
|
||||||
|
await _firebaseAuth.currentUser!.sendEmailVerification();
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw SendEmailVerificationFailureFirebase.fromCode(e.code);
|
||||||
|
} catch (_) {
|
||||||
|
throw SendEmailVerificationFailureFirebase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendPasswordResetEmail({required String email}) async {
|
||||||
|
try {
|
||||||
|
await _firebaseAuth.sendPasswordResetEmail(email: email);
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw SendPasswordResetEmailFailureFirebase.fromCode(e.code);
|
||||||
|
} catch (_) {
|
||||||
|
throw SendPasswordResetEmailFailureFirebase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Account> signInAnonymously() async {
|
||||||
|
try {
|
||||||
|
final userCredential = await _firebaseAuth.signInAnonymously();
|
||||||
|
final user = userCredential.user;
|
||||||
|
if (user.isNotNull) {
|
||||||
|
return _mapper(user!);
|
||||||
|
} else {
|
||||||
|
throw Exception(); // Get caught just after.
|
||||||
|
}
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw SignInAnonymouslyFailureFirebase.fromCode(e.code);
|
||||||
|
} catch (_) {
|
||||||
|
throw SignInAnonymouslyFailureFirebase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> verifyPasswordResetCode({required String code}) async {
|
||||||
|
try {
|
||||||
|
final email = await _firebaseAuth.verifyPasswordResetCode(code);
|
||||||
|
return email.isNotNullOrEmpty;
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw VerifyPasswordResetCodeFailureFirebase.fromCode(e.code);
|
||||||
|
} catch (_) {
|
||||||
|
throw VerifyPasswordResetCodeFailureFirebase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> refresh() async {
|
||||||
|
try {
|
||||||
|
await _firebaseAuth.currentUser!.reload();
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
throw RefreshFailureFirebase.fromCode(e.code);
|
||||||
|
} catch (_) {
|
||||||
|
throw RefreshFailureFirebase();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// 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()
|
||||||
|
..add(null);
|
||||||
|
|
||||||
|
final List<Pair<Account, String>>? registeredAccounts;
|
||||||
|
final String idToken;
|
||||||
|
|
||||||
|
AuthenticationMockDataSourceImpl({
|
||||||
|
this.idToken = 'fake-id-token',
|
||||||
|
this.registeredAccounts,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> _randomDelay() async {
|
||||||
|
await Future<void>.delayed(
|
||||||
|
Duration(milliseconds: Random().nextInt(400) + 200),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> confirmPasswordReset({
|
||||||
|
required String code,
|
||||||
|
required String newPassword,
|
||||||
|
}) async {
|
||||||
|
await _randomDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> getIdentityToken() async {
|
||||||
|
await _randomDelay();
|
||||||
|
return idToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> refresh() async {
|
||||||
|
await _randomDelay();
|
||||||
|
if (_connectedMock.isNull) {
|
||||||
|
throw RefreshFailureFirebase();
|
||||||
|
}
|
||||||
|
final refresh = DateTime.now();
|
||||||
|
final mock = (_connectedMock?.left as AccountModel?)
|
||||||
|
?.copyWith(lastSignInTime: refresh);
|
||||||
|
_connectedMock = _connectedMock?.copyWith(left: mock);
|
||||||
|
_streamAccount.add(mock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendEmailVerification() async {
|
||||||
|
await _randomDelay();
|
||||||
|
if (_connectedMock.isNotNull) {
|
||||||
|
final refresh = DateTime.now();
|
||||||
|
final mock = (_connectedMock?.left as AccountModel?)?.copyWith(
|
||||||
|
emailVerified: false,
|
||||||
|
lastSignInTime: refresh,
|
||||||
|
);
|
||||||
|
_streamAccount.add(mock);
|
||||||
|
_connectedMock = _connectedMock?.copyWith(left: mock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw SendEmailVerificationFailureFirebase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendPasswordResetEmail({required String email}) async {
|
||||||
|
await _randomDelay();
|
||||||
|
if (registeredAccounts.isNotNull) {
|
||||||
|
final accounts =
|
||||||
|
registeredAccounts?.where((pair) => pair.left?.email == email);
|
||||||
|
if (accounts.isNotNullOrEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_registeredMock.isNotNull) {
|
||||||
|
if (_registeredMock?.left?.email != email) {
|
||||||
|
throw SendPasswordResetEmailFailureFirebase();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw SendPasswordResetEmailFailureFirebase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Account> signInAnonymously() async {
|
||||||
|
await _randomDelay();
|
||||||
|
final creation = DateTime.now();
|
||||||
|
final mock = AccountModel(
|
||||||
|
uid: 'mock-id-anom',
|
||||||
|
emailVerified: false,
|
||||||
|
isAnonymous: true,
|
||||||
|
providerId: 'wyatt',
|
||||||
|
creationTime: creation,
|
||||||
|
lastSignInTime: creation,
|
||||||
|
);
|
||||||
|
_streamAccount.add(mock);
|
||||||
|
_connectedMock = _connectedMock?.copyWith(left: mock);
|
||||||
|
return Future.value(mock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Account> signInWithEmailAndPassword({
|
||||||
|
required String email,
|
||||||
|
required String password,
|
||||||
|
}) async {
|
||||||
|
await _randomDelay();
|
||||||
|
if (registeredAccounts.isNotNull) {
|
||||||
|
final accounts =
|
||||||
|
registeredAccounts?.where((pair) => pair.left?.email == email);
|
||||||
|
if (accounts.isNotNullOrEmpty) {
|
||||||
|
final account = accounts?.first;
|
||||||
|
if (account?.right != password) {
|
||||||
|
throw SignInWithCredentialFailureFirebase.fromCode('wrong-password');
|
||||||
|
}
|
||||||
|
_streamAccount.add(account!.left);
|
||||||
|
_connectedMock = account.copyWith();
|
||||||
|
return account.left!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_registeredMock.isNotNull) {
|
||||||
|
if (_registeredMock?.left?.email != email) {
|
||||||
|
throw SignInWithCredentialFailureFirebase.fromCode('user-not-found');
|
||||||
|
}
|
||||||
|
if (_registeredMock?.right != password) {
|
||||||
|
throw SignInWithCredentialFailureFirebase.fromCode('wrong-password');
|
||||||
|
}
|
||||||
|
_streamAccount.add(_registeredMock!.left);
|
||||||
|
_connectedMock = _registeredMock!.copyWith();
|
||||||
|
return _registeredMock!.left!;
|
||||||
|
}
|
||||||
|
throw SignInWithCredentialFailureFirebase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> signOut() async {
|
||||||
|
_connectedMock = null;
|
||||||
|
_streamAccount.add(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Account> signUp({
|
||||||
|
required String email,
|
||||||
|
required String password,
|
||||||
|
}) async {
|
||||||
|
await _randomDelay();
|
||||||
|
if (registeredAccounts.isNotNull) {
|
||||||
|
final accounts =
|
||||||
|
registeredAccounts?.where((pair) => pair.left?.email == email);
|
||||||
|
if (accounts.isNotNullOrEmpty) {
|
||||||
|
throw SignUpWithEmailAndPasswordFailureFirebase.fromCode(
|
||||||
|
'email-already-in-use',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_registeredMock?.left?.email == email) {
|
||||||
|
throw SignUpWithEmailAndPasswordFailureFirebase.fromCode(
|
||||||
|
'email-already-in-use',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final creation = DateTime.now();
|
||||||
|
final mock = AccountModel(
|
||||||
|
uid: 'mock-id-email',
|
||||||
|
emailVerified: false,
|
||||||
|
isAnonymous: false,
|
||||||
|
providerId: 'wyatt',
|
||||||
|
email: email,
|
||||||
|
creationTime: creation,
|
||||||
|
lastSignInTime: creation,
|
||||||
|
);
|
||||||
|
_streamAccount.add(mock);
|
||||||
|
_registeredMock = Pair(mock, password);
|
||||||
|
return Future.value(mock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Account?> streamAccount() => _streamAccount.stream.asBroadcastStream();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> verifyPasswordResetCode({required String code}) async {
|
||||||
|
await _randomDelay();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
@@ -16,12 +17,71 @@
|
|||||||
|
|
||||||
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
|
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
|
||||||
|
|
||||||
class AccountModel implements Account {
|
class AccountModel extends Account {
|
||||||
@override
|
@override
|
||||||
final String uid;
|
final String uid;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String? email;
|
final String? email;
|
||||||
|
|
||||||
AccountModel({required this.uid, required this.email});
|
@override
|
||||||
|
final DateTime? creationTime;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool emailVerified;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool isAnonymous;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool? isNewUser;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final DateTime? lastSignInTime;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? phoneNumber;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? photoURL;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String providerId;
|
||||||
|
|
||||||
|
AccountModel({
|
||||||
|
required this.uid,
|
||||||
|
required this.emailVerified,
|
||||||
|
required this.isAnonymous,
|
||||||
|
required this.providerId,
|
||||||
|
this.lastSignInTime,
|
||||||
|
this.creationTime,
|
||||||
|
this.isNewUser,
|
||||||
|
this.email,
|
||||||
|
this.phoneNumber,
|
||||||
|
this.photoURL,
|
||||||
|
});
|
||||||
|
|
||||||
|
AccountModel copyWith({
|
||||||
|
String? uid,
|
||||||
|
String? email,
|
||||||
|
DateTime? creationTime,
|
||||||
|
bool? emailVerified,
|
||||||
|
bool? isAnonymous,
|
||||||
|
bool? isNewUser,
|
||||||
|
DateTime? lastSignInTime,
|
||||||
|
String? phoneNumber,
|
||||||
|
String? photoURL,
|
||||||
|
String? providerId,
|
||||||
|
}) => AccountModel(
|
||||||
|
uid: uid ?? this.uid,
|
||||||
|
email: email ?? this.email,
|
||||||
|
creationTime: creationTime ?? this.creationTime,
|
||||||
|
emailVerified: emailVerified ?? this.emailVerified,
|
||||||
|
isAnonymous: isAnonymous ?? this.isAnonymous,
|
||||||
|
isNewUser: isNewUser ?? this.isNewUser,
|
||||||
|
lastSignInTime: lastSignInTime ?? this.lastSignInTime,
|
||||||
|
phoneNumber: phoneNumber ?? this.phoneNumber,
|
||||||
|
photoURL: photoURL ?? this.photoURL,
|
||||||
|
providerId: providerId ?? this.providerId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
@@ -24,4 +25,12 @@ class AccountWrapperModel<T> extends AccountWrapper<T> {
|
|||||||
final T? data;
|
final T? data;
|
||||||
|
|
||||||
AccountWrapperModel(this.account, this.data);
|
AccountWrapperModel(this.account, this.data);
|
||||||
|
|
||||||
|
AccountWrapperModel<T> copyWith({
|
||||||
|
Account? account,
|
||||||
|
T? data,
|
||||||
|
}) => AccountWrapperModel<T>(
|
||||||
|
account ?? this.account,
|
||||||
|
data ?? this.data,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ class AuthenticationRepositoryImpl<T extends Object>
|
|||||||
|
|
||||||
final OnAuthChange<T>? _onAccountChanges;
|
final OnAuthChange<T>? _onAccountChanges;
|
||||||
|
|
||||||
|
bool _pause = false; // Semaphore
|
||||||
|
|
||||||
AuthenticationRepositoryImpl({
|
AuthenticationRepositoryImpl({
|
||||||
required AuthenticationCacheDataSource<T> authenticationCacheDataSource,
|
required AuthenticationCacheDataSource<T> authenticationCacheDataSource,
|
||||||
required AuthenticationRemoteDataSource authenticationRemoteDataSource,
|
required AuthenticationRemoteDataSource authenticationRemoteDataSource,
|
||||||
@@ -120,6 +122,7 @@ class AuthenticationRepositoryImpl<T extends Object>
|
|||||||
}) =>
|
}) =>
|
||||||
Result.tryCatchAsync<Account, AppException, AppException>(
|
Result.tryCatchAsync<Account, AppException, AppException>(
|
||||||
() async {
|
() async {
|
||||||
|
_pause = true;
|
||||||
final account = await _authenticationRemoteDataSource.signUp(
|
final account = await _authenticationRemoteDataSource.signUp(
|
||||||
email: email,
|
email: email,
|
||||||
password: password,
|
password: password,
|
||||||
@@ -132,12 +135,16 @@ class AuthenticationRepositoryImpl<T extends Object>
|
|||||||
);
|
);
|
||||||
await dataResult.foldAsync(
|
await dataResult.foldAsync(
|
||||||
_authenticationLocalDataSource.storeData,
|
_authenticationLocalDataSource.storeData,
|
||||||
(error) => throw error,
|
(error) async => error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
_pause = false;
|
||||||
return account;
|
return account;
|
||||||
},
|
},
|
||||||
(error) => error,
|
(error) {
|
||||||
|
_pause = false;
|
||||||
|
return error;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -200,7 +207,7 @@ class AuthenticationRepositoryImpl<T extends Object>
|
|||||||
@override
|
@override
|
||||||
Stream<FutureResult<AccountWrapper<T>>> streamAccount() =>
|
Stream<FutureResult<AccountWrapper<T>>> streamAccount() =>
|
||||||
_authenticationRemoteDataSource.streamAccount().map((account) async {
|
_authenticationRemoteDataSource.streamAccount().map((account) async {
|
||||||
if (_onAccountChanges.isNotNull) {
|
if (_onAccountChanges.isNotNull && !_pause) {
|
||||||
final dataResult = await _onAccountChanges!.call(account);
|
final dataResult = await _onAccountChanges!.call(account);
|
||||||
return dataResult.map((data) => AccountWrapperModel(account, data));
|
return dataResult.map((data) => AccountWrapperModel(account, data));
|
||||||
}
|
}
|
||||||
@@ -208,4 +215,70 @@ class AuthenticationRepositoryImpl<T extends Object>
|
|||||||
AccountWrapperModel<T>(account, null),
|
AccountWrapperModel<T>(account, null),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<void> confirmPasswordReset({
|
||||||
|
required String code,
|
||||||
|
required String newPassword,
|
||||||
|
}) =>
|
||||||
|
Result.tryCatchAsync<void, AppException, AppException>(
|
||||||
|
() async {
|
||||||
|
await _authenticationRemoteDataSource.confirmPasswordReset(
|
||||||
|
code: code,
|
||||||
|
newPassword: newPassword,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<void> sendEmailVerification() =>
|
||||||
|
Result.tryCatchAsync<void, AppException, AppException>(
|
||||||
|
() async {
|
||||||
|
await _authenticationRemoteDataSource.sendEmailVerification();
|
||||||
|
},
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<void> sendPasswordResetEmail({required String email}) =>
|
||||||
|
Result.tryCatchAsync<void, AppException, AppException>(
|
||||||
|
() async {
|
||||||
|
await _authenticationRemoteDataSource.sendPasswordResetEmail(
|
||||||
|
email: email,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<Account> signInAnonymously() =>
|
||||||
|
Result.tryCatchAsync<Account, AppException, AppException>(
|
||||||
|
() async {
|
||||||
|
final account =
|
||||||
|
await _authenticationRemoteDataSource.signInAnonymously();
|
||||||
|
return account;
|
||||||
|
},
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<bool> verifyPasswordResetCode({required String code}) =>
|
||||||
|
Result.tryCatchAsync<bool, AppException, AppException>(
|
||||||
|
() async {
|
||||||
|
final response = await _authenticationRemoteDataSource
|
||||||
|
.verifyPasswordResetCode(code: code);
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureResult<void> refresh() =>
|
||||||
|
Result.tryCatchAsync<void, AppException, AppException>(
|
||||||
|
() async {
|
||||||
|
await _authenticationRemoteDataSource.refresh();
|
||||||
|
},
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,22 @@ abstract class AuthenticationRemoteDataSource extends BaseRemoteDataSource {
|
|||||||
|
|
||||||
Future<void> signOut();
|
Future<void> signOut();
|
||||||
|
|
||||||
|
Future<void> refresh();
|
||||||
|
|
||||||
Stream<Account?> streamAccount();
|
Stream<Account?> streamAccount();
|
||||||
|
|
||||||
Future<String> getIdentityToken();
|
Future<String> getIdentityToken();
|
||||||
|
|
||||||
|
Future<void> sendEmailVerification();
|
||||||
|
|
||||||
|
Future<void> sendPasswordResetEmail({required String email});
|
||||||
|
|
||||||
|
Future<void> confirmPasswordReset({
|
||||||
|
required String code,
|
||||||
|
required String newPassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<bool> verifyPasswordResetCode({required String code});
|
||||||
|
|
||||||
|
Future<Account> signInAnonymously();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,10 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:wyatt_architecture/wyatt_architecture.dart';
|
import 'package:wyatt_architecture/wyatt_architecture.dart';
|
||||||
|
|
||||||
abstract class Account extends Entity {
|
abstract class Account extends Equatable implements Entity {
|
||||||
/// The user's unique ID.
|
/// The user's unique ID.
|
||||||
String get uid;
|
String get uid;
|
||||||
|
|
||||||
@@ -24,4 +25,59 @@ abstract class Account extends Entity {
|
|||||||
///
|
///
|
||||||
/// Will be `null` if signing in anonymously.
|
/// Will be `null` if signing in anonymously.
|
||||||
String? get email;
|
String? get email;
|
||||||
|
|
||||||
|
/// Returns whether the users email address has been verified.
|
||||||
|
///
|
||||||
|
/// To send a verification email, see `SendEmailVerification`.
|
||||||
|
bool get emailVerified;
|
||||||
|
|
||||||
|
/// Returns whether the user is a anonymous.
|
||||||
|
bool get isAnonymous;
|
||||||
|
|
||||||
|
/// Returns the users account creation time.
|
||||||
|
///
|
||||||
|
/// When this account was created as dictated by the server clock.
|
||||||
|
DateTime? get creationTime;
|
||||||
|
|
||||||
|
/// When the user last signed in as dictated by the server clock.
|
||||||
|
DateTime? get lastSignInTime;
|
||||||
|
|
||||||
|
/// Returns the users phone number.
|
||||||
|
///
|
||||||
|
/// This property will be `null` if the user has not signed in or been has
|
||||||
|
/// their phone number linked.
|
||||||
|
String? get phoneNumber;
|
||||||
|
|
||||||
|
/// Returns a photo URL for the user.
|
||||||
|
///
|
||||||
|
/// This property will be populated if the user has signed in or been linked
|
||||||
|
/// with a 3rd party OAuth provider (such as Google).
|
||||||
|
String? get photoURL;
|
||||||
|
|
||||||
|
/// The provider ID for the user.
|
||||||
|
String get providerId;
|
||||||
|
|
||||||
|
/// Whether the user account has been recently created.
|
||||||
|
bool? get isNewUser;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
uid,
|
||||||
|
email,
|
||||||
|
emailVerified,
|
||||||
|
isAnonymous,
|
||||||
|
creationTime,
|
||||||
|
lastSignInTime,
|
||||||
|
phoneNumber,
|
||||||
|
photoURL,
|
||||||
|
providerId,
|
||||||
|
isNewUser,
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AccountModel(uid: $uid, email: $email, '
|
||||||
|
'creationTime: $creationTime, emailVerified: $emailVerified, '
|
||||||
|
'isAnonymous: $isAnonymous, isNewUser: $isNewUser, lastSignInTime: '
|
||||||
|
'$lastSignInTime, phoneNumber: $phoneNumber, photoURL: $photoURL, '
|
||||||
|
'providerId: $providerId)';
|
||||||
}
|
}
|
||||||
|
|||||||