Compare commits

..
Author SHA1 Message Date
hugo f6ef5828bb chore(release): publish packages
continuous-integration/drone/push Build is passing
- wyatt_authentication_bloc@0.4.1
2022-12-13 17:29:38 -05:00
Malo Léon f22ba300cb Merge pull request 'authentication/feature/google_signin' (#105) from authentication/feature/google_signin into master
continuous-integration/drone/push Build is passing
Reviewed-on: #105
Reviewed-by: Malo Léon <malo.leon@wyattapp.io>
2022-12-13 22:07:16 +00:00
hugo 5f52b2fc3d refactor(authentication): use fromFirebaseUser factory to parse user 2022-12-13 17:00:26 -05:00
hugo f46707f5c1 style(authentication): Merge 'origin/master' into authentication/feature/google_signin 2022-12-13 16:42:50 -05:00
AN12345 17ff0f8ba1 clean(packages): apply dart fix (close #106)
continuous-integration/drone/push Build is passing
2022-12-13 13:52:49 -05:00
hugo 1a3631f691 feat(authentication): add google sign in in signIn cubit 2022-12-13 00:36:20 -05:00
hugo 197c5d54e6 feat(authentication): add google sign in parameter in firebase data source constructor 2022-12-12 23:47:29 -05:00
hugo c7b241de2d feat(authentication): add google sign_in support (closes #59) 2022-12-12 23:16:10 -05:00
hugo 94d573a584 refactor(authentication): update deps and re-export them 2022-12-12 23:14:55 -05:00
hugo 37e00fe9c4 style(analysis): dart format + follows wyatt analysis 2.3.0 2022-12-12 22:43:54 -05:00
hugo 3c8ff373a1 chore(release): publish packages
continuous-integration/drone/push Build is passing
- wyatt_analysis@2.3.0
2022-12-12 22:17:35 -05:00
hugo 640cfaf118 refactor(analysis): follow melos versioning
continuous-integration/drone/push Build is passing
2022-12-12 22:15:32 -05:00
hugo 7fc3736854 feat(analysis): exclude generated code, and add dart>2.16 rules
continuous-integration/drone/push Build is passing
2022-12-12 22:02:31 -05:00
hugo 29d8ec640f feat(analysis): add overwrite protection 2022-12-12 21:38:57 -05:00
hugo 7f9621b4f6 feat(analysis): add 2.2.2 files for archive 2022-12-12 21:37:20 -05:00
hugo 7b61f3f08a feat(analysis): add utils script to easely bump version 2022-12-12 21:36:39 -05:00
hugo 75a5facfdc fix(analysis): change file include to keep coherence 2022-12-12 21:30:22 -05:00
AN12345 c93b7fb5f7 feat(bloc_layout): export layout_package & adapt imports
continuous-integration/drone/push Build is passing
2022-12-12 20:32:29 -05:00
hugo aee7060a0b ci: fix http client test error
continuous-integration/drone/push Build is passing
2022-12-12 15:20:47 -05:00
AN12345 4f49f4931c refactor(ui_layout): update example (#92)
continuous-integration/drone/push Build is failing
2022-12-12 14:21:48 -05:00
AN12345 e8172004a3 refactor(ui_component): update example (#92) 2022-12-12 14:21:13 -05:00
AN12345 c6d8ca37fe refactor(bloc_layout): update example (#92) 2022-12-12 14:20:20 -05:00
AN12345 e01f47d41a refactor(ui_layout): update package using nullable component data (#92) 2022-12-12 14:14:15 -05:00
AN12345 7a24a36af0 refactor(bloc_layout): update package using nullable component data (#92) 2022-12-12 14:13:00 -05:00
AN12345 244a286509 refactor(ui_components): update example (#92) 2022-12-12 14:10:41 -05:00
AN12345 81a7ca5a1f feat(ui_components): make component data field nullable (#92) 2022-12-12 14:08:36 -05:00
AN12345 45c7bd6fca refactor(ui_layout): update example with components updates (close #90)
continuous-integration/drone/push Build is failing
2022-12-12 12:42:11 -05:00
AN12345 cd2806b07b refactor(bloc_layout): update example with new app bar entity (#90) 2022-12-12 12:36:40 -05:00
AN12345 dd275460f6 feat(ui_components): make app bar leading & actions customizable 2022-12-12 12:34:02 -05:00
AN12345 dc0f0c61c6 feat(bloc_layout): export useful packages (close #88)
continuous-integration/drone/push Build is failing
2022-12-12 11:25:40 -05:00
hugo 1e305c75bd docs: update readme with melos commands
continuous-integration/drone/push Build is failing
2022-12-11 21:28:47 -05:00
hugo 9af76b8e1f ci: add package tests and validation 2022-12-12 01:21:37 +00:00
hugo 5593620181 refactor: change quality check rule name 2022-12-12 01:21:37 +00:00
hugo a19d4bc5ce chore: update melos config 2022-12-12 01:21:37 +00:00
hugo 21b18054ba chore(package): clean unimplemented tests 2022-12-12 01:21:37 +00:00
AN12345 ff90af47cd fix(bloc_layout): update export file 2022-12-11 23:38:54 +00:00
AN12345 3bb57e65f8 fix(bloc_layout): rename file 2022-12-11 23:38:54 +00:00
AN12345 9d0aac7894 feat(bloc_layout): add all layout avalaible with crud bloc 2022-12-11 23:38:54 +00:00
AN12345 a561cbff23 feat(bloc_layout): link error widget to error state 2022-12-11 23:38:54 +00:00
AN12345 fafec78546 clean(bloc_layout): remove test folder 2022-12-11 23:38:54 +00:00
AN12345 feca378131 feat(bloc_layout): add new package to combine bloc_helper, crud_bloc, ui_components, and ui_layout 2022-12-11 23:38:54 +00:00
hugo f223811f4b chore(medium): remove unused package 2022-12-08 14:32:19 -05:00
hugo ed65489316 chore(cli): remove unused package 2022-12-08 14:31:57 -05:00
AN12345 33797e4dbf refactor(ui_layout): update appbar component (close #78) 2022-12-08 18:58:37 +00:00
AN12345 3aa6d03dd6 refactor(crud_bloc): update package using new architecture usecase execute method (close #77) 2022-12-08 13:29:01 -05:00
hugo bf03324fd0 chore: add pre-commit rule (see #72) 2022-12-08 03:46:10 +00:00
hugo 5e83c9e74e docs(auth): add correct header 2022-12-07 19:52:01 -05:00
hugo 672aba7084 fix(auth): fix mock on register 2022-12-07 19:52:00 -05:00
hugo 94d7efbe8c fix(form): fix textController rebuild 2022-12-07 19:51:59 -05:00
hugo 5298bd99ed feat(authentication): add reauthenticate, updateEmail and updatePassword 2022-12-07 19:50:19 -05:00
AN12345 d792f4cbe9 feat(ui_layout): update example & move configure function (#69) 2022-12-07 18:54:48 -05:00
AN12345 098ec9715e feat(ui_layout): add loading & error widget to component theme (close #69) 2022-12-07 18:20:04 -05:00
AN12345 6d3c64d17c feat(ui_layout): add frame layout (close #67) 2022-12-07 17:14:00 -05:00
AN12345 c615186f19 docs(ui_layout): update models 2022-12-07 15:44:05 -05:00
AN12345 55e24fb23b fix(ui_layout): fix emample after splititng packages 2022-12-07 15:37:46 -05:00
AN12345 4d04c3f446 doc: update readme(s) 2022-12-07 15:04:48 -05:00
AN12345 a20e4a1715 refractor: migrate components in right package 2022-12-07 14:56:05 -05:00
AN12345 cf9f1ddb65 feat: create new package to split components and layouts 2022-12-07 14:13:24 -05:00
AN12345 0c141c715e fix(ui_layout): remove unecessary dependencies 2022-12-07 02:38:14 +00:00
AN12345 d0f59bbf29 refactor(ui_layout): update field name 2022-12-07 02:38:14 +00:00
AN12345 562f84dd33 doc(ui_layout): add class models 2022-12-07 02:38:14 +00:00
AN12345 771f600eb7 feat(ui_layout): add new package for app compenant and start implementing layouts 2022-12-07 02:38:14 +00:00
hugo 7fcb9e8ac3 chore(release): publish packages
- wyatt_bloc_helper@2.0.0
 - wyatt_type_utils@0.0.4
 - wyatt_authentication_bloc@0.4.0+3
 - wyatt_crud_bloc@0.1.0+2
 - wyatt_architecture@0.1.0+1
 - wyatt_form_bloc@0.2.0+2
2022-12-04 21:08:15 -05:00
hugo 2a4e29f38c chore(release): publish packages
- wyatt_architecture@0.1.0
 - wyatt_authentication_bloc@0.4.0+2
 - wyatt_crud_bloc@0.1.0+1
 - wyatt_form_bloc@0.2.0+1
2022-12-03 20:40:23 -05:00
hugo ca70f2eb1d feat(authentication)!: add repo in callback usecases (close #55) 2022-12-03 18:53:20 -05:00
hugo f123e537ae fix(authentication): allow email/password validators customization (closes #57) 2022-12-03 18:41:04 -05:00
AN12345 c3315d968c refactor(bloc_helper)!: update onWrap label (close #46) 2022-11-24 18:16:20 -05:00
AN12345 45312473d1 feat: add gen-class-models in melos scripts 2022-11-24 17:55:07 -05:00
AN12345 17e7bce9e6 refactor(architecture): update naming methodes (close #47) 2022-11-24 17:25:44 -05:00
hugo 3886ef080e fix(crud)!: use FutureOrResult extension (close #42) 2022-11-24 15:24:59 -05:00
hugo 626d951443 fix(authentication)!: use FutureOrResult extension (close #41) 2022-11-24 11:18:16 -05:00
hugo f36e0b51e0 feat(type): add Result extension on FutureOr type (close #40) 2022-11-23 19:13:04 -05:00
AN12345 80ce7aff91 fix(architecture): fix execute method on error handler. (#38) 2022-11-23 23:11:04 +00:00
AN12345 a9121b6862 refactor(architecture): update example. (close #38) 2022-11-23 23:11:04 +00:00
AN12345 54f92e8042 docs(architecture): generate class model. (#38) 2022-11-23 23:11:04 +00:00
AN12345 00393a6ba7 docs(architecture): update Readme. (#38) 2022-11-23 23:11:04 +00:00
AN12345 136e0e8c98 chore(architecture)!: update exports. (#38) 2022-11-23 23:11:04 +00:00
AN12345 500a6ae51d feat(architecture): update usecase en add & sepcific case. (#38) 2022-11-23 23:11:04 +00:00
AN12345 79fdde25fc feat(architecture): add obsevers for alternative usecase scenarions (#38) 2022-11-23 23:11:04 +00:00
Malo Léon 742bf1d614 Merge pull request 'config/update_project_management' (#32) from config/update_project_management into master
Reviewed-on: #32
Reviewed-by: Hugo Pointcheval <hugo@noreply@wyatt-studio.fr>
2022-11-22 02:33:48 +00:00
AN12345 0dbde68d7d typo: fix readme typo error 2022-11-21 21:32:36 -05:00
AN12345 cd881da072 doc: update readme 2022-11-21 21:20:25 -05:00
AN12345 5918a5cf1f doc: add simple workflow diagramm 2022-11-21 21:20:01 -05:00
AN12345 775d766d14 build: update melos configuration and update targets 2022-11-21 20:59:36 -05:00
AN12345 383098b147 build: add mason configuration files 2022-11-21 20:59:02 -05:00
AN12345 4d51e2b786 build: add coverage script 2022-11-21 20:58:18 -05:00
AN12345 32cc1f55d9 chore: update gitignore 2022-11-21 20:57:52 -05:00
AN12345 6ee101bf36 doc: update readme 2022-11-21 20:54:57 -05:00
hugo f0ed08bd49 feat(auth): add reloadCache method in AuthenticationCubit 2022-11-16 19:52:24 -05:00
hugo 390fb81b1e fix(auth): fix stream account, now including correctly extra data 2022-11-16 19:51:45 -05:00
hugo 279d451cd8 fix(auth): fix mock isNewUser 2022-11-16 19:48:03 -05:00
hugo a66db23ace chore(release): publish packages
- wyatt_authentication_bloc@0.4.0+1
2022-11-16 17:20:57 -05:00
hugo 3d7af98ac9 fix(auth): add sort of mutex on onAuthChanges callback trigger 2022-11-16 17:16:41 -05:00
AN12345 b033b97917 feat(notification_bloc) add new package for push notification management 2022-11-16 16:58:19 -05:00
hugo 14b7f31b53 chore(form): use hard link for vscode folder 2022-11-16 16:32:05 -05:00
hugo 403bcafe93 chore(form): remove vscode symlink 2022-11-16 16:30:49 -05:00
hugo 21ab6f6021 chore(auth): use hardlink for vscode folder 2022-11-16 16:27:51 -05:00
hugo 198ecac5da chore(auth): remove vscode symlink to fix publish 2022-11-16 16:18:23 -05:00
450 changed files with 10653 additions and 5084 deletions
+36
View File
@@ -0,0 +1,36 @@
# 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/>.
kind: pipeline
type: docker
name: default
steps:
- name: quality-check
image: git.wyatt-studio.fr/wyatt-foss/flutter-melos:2.9.0
commands:
- melos run quality-check
- melos run publish:validate
- name: publish
image: git.wyatt-studio.fr/wyatt-foss/flutter-melos:2.9.0
commands:
- melos run publish:validate
trigger:
branch:
- master
event:
- push
+3
View File
@@ -198,3 +198,6 @@ $RECYCLE.BIN/
*.iml *.iml
google-services.json google-services.json
pubspec_overrides.yaml pubspec_overrides.yaml
# Mason
.mason/
+7
View File
@@ -0,0 +1,7 @@
repos:
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v2.1.1
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
args: [build, ci, docs, feat, fix, perf, refactor, style, test, chore]
+151
View File
@@ -3,6 +3,157 @@
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-12-13
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`wyatt_authentication_bloc` - `v0.4.1`](#wyatt_authentication_bloc---v041)
---
#### `wyatt_authentication_bloc` - `v0.4.1`
- **REFACTOR**: use fromFirebaseUser factory to parse user.
- **REFACTOR**: update deps and re-export them.
- **FIX**: fix mock on register.
- **FEAT**: add google sign in in signIn cubit.
- **FEAT**: add google sign in parameter in firebase data source constructor.
- **FEAT**: add google sign_in support (closes #59).
- **FEAT**: add reauthenticate, updateEmail and updatePassword.
- **DOCS**: add correct header.
## 2022-12-12
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`wyatt_analysis` - `v2.3.0`](#wyatt_analysis---v230)
---
#### `wyatt_analysis` - `v2.3.0`
- **REFACTOR**: follow melos versioning.
- **FIX**: change file include to keep coherence.
- **FEAT**: exclude generated code, and add dart>2.16 rules.
- **FEAT**: add overwrite protection.
- **FEAT**: add 2.2.2 files for archive.
- **FEAT**: add utils script to easely bump version.
## 2022-12-04
### Changes
---
Packages with breaking changes:
- [`wyatt_bloc_helper` - `v2.0.0`](#wyatt_bloc_helper---v200)
Packages with other changes:
- [`wyatt_type_utils` - `v0.0.4`](#wyatt_type_utils---v004)
- [`wyatt_authentication_bloc` - `v0.4.0+3`](#wyatt_authentication_bloc---v0403)
- [`wyatt_crud_bloc` - `v0.1.0+2`](#wyatt_crud_bloc---v0102)
- [`wyatt_architecture` - `v0.1.0+1`](#wyatt_architecture---v0101)
- [`wyatt_form_bloc` - `v0.2.0+2`](#wyatt_form_bloc---v0202)
Packages with dependency updates only:
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
- `wyatt_authentication_bloc` - `v0.4.0+3`
- `wyatt_crud_bloc` - `v0.1.0+2`
- `wyatt_architecture` - `v0.1.0+1`
- `wyatt_form_bloc` - `v0.2.0+2`
---
#### `wyatt_bloc_helper` - `v2.0.0`
- **BREAKING** **REFACTOR**: update onWrap label (close #46).
#### `wyatt_type_utils` - `v0.0.4`
- **FEAT**: add Result extension on FutureOr type (close #40).
## 2022-12-03
### Changes
---
Packages with breaking changes:
- [`wyatt_architecture` - `v0.1.0`](#wyatt_architecture---v010)
Packages with other changes:
- [`wyatt_authentication_bloc` - `v0.4.0+2`](#wyatt_authentication_bloc---v0402)
- [`wyatt_crud_bloc` - `v0.1.0+1`](#wyatt_crud_bloc---v0101)
- [`wyatt_form_bloc` - `v0.2.0+1`](#wyatt_form_bloc---v0201)
Packages with dependency updates only:
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
- `wyatt_authentication_bloc` - `v0.4.0+2`
- `wyatt_crud_bloc` - `v0.1.0+1`
- `wyatt_form_bloc` - `v0.2.0+1`
---
#### `wyatt_architecture` - `v0.1.0`
- **REFACTOR**: update naming methodes (close #47).
- **REFACTOR**: update example. (close #38).
- **FIX**: fix execute method on error handler. (#38).
- **FEAT**: update usecase en add & sepcific case. (#38).
- **FEAT**: add obsevers for alternative usecase scenarions (#38).
- **DOCS**: generate class model. (#38).
- **DOCS**: update Readme. (#38).
- **BREAKING** **CHORE**: update exports. (#38).
## 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 ## 2022-11-16
### Changes ### Changes
+131 -17
View File
@@ -51,36 +51,51 @@ and open source.
--- ---
## Development ## Contribution
Clone this repo. Clone this repo.
You have to install [melos](https://github.com/invertase/melos). ```shell
git clone ssh://git@git.wyatt-studio.fr:993/Wyatt-FOSS/wyatt-packages.git
```
### Prerequisite
- [Flutter](https://github.com/flutter/flutter).
- [Melos](https://github.com/invertase/melos). Used to manage Dart projects with multiple packages.
```shell ```shell
dart pub global activate melos dart pub global activate melos
``` ```
Then bootstrap with `melos bs`. - [Mason](https://github.com/felangel/mason). Used for code generation.
```shell
dart pub global activate mason_cli
```
- [Lcov](https://github.com/linux-test-project/lcov). Used to generate coverage data.
```shell
# on macos
brew install lcov
```
- genhtml. Used to convert coverage data into html pages.
After installing all these packages, bootstrap with `melos bs`.
### Create a new package ### Create a new package
Create a new package in `packages/` folder. Create a new package in `packages/` folder.
```shell ```shell
dart create -t package-simple wyatt_<name> mason make wyatt_package --package_name wyatt_<name> --description A new Wyatt package --flutter_only false
```
Remove any `example/` subfolder. Then create a new sample project.
```shell
flutter create --platforms android --project-name <name>_example example
``` ```
Then bootstrap project with `melos bs` command. Then bootstrap project with `melos bs` command.
### Convention
#### Naming #### Naming
In the previous instructions `<name>` variable is important. In the previous instructions `<name>` variable is important.
@@ -93,15 +108,44 @@ You **MUST** use `wyatt` prefix for package.
You **MUST** name example with specific name. You **MUST** name example with specific name.
For example, if name is CRUD BLOC For example, if name is CRUD BLOC
- name will be crud_bloc - name will be crud_bloc
- so the package will be: `wyatt_crud_bloc` - so the package will be: `wyatt_crud_bloc`
- and the example will be: `crud_bloc_example` - and the example will be: `crud_bloc_example`
#### Commits ### Create issues
Add the issues directly related to what you want to develop and label them correctly.
```
Type(scope): issue.
```
For example, on notification bloc package :
- `Feature(notification_bloc): add firebase messaging data source.`
- `Test(notification_bloc): add test for pushwoosh datasource.`
### Branches
Master is protected. You can't push on it. Please develop your feature on another branch. The name of your branch has to match with the issue opened.
`type(scope):issue`
Example :
Issue :
- `Feature(notification_bloc): add firebase messaging data source.`
Branch related to this issue :
- `feature(notification_bloc)/add_firebase_messaging_data_source`
### Commits
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
tl;dr : `type(scope): description`. tl;dr : `type(scope): description #issue`.
Here allowed <type> values: Here allowed <type> values:
- **feat** for a new feature for the user, not a new feature for build script. Such commit will trigger a release bumping a MINOR version. - **feat** for a new feature for the user, not a new feature for build script. Such commit will trigger a release bumping a MINOR version.
@@ -115,11 +159,74 @@ Here allowed <type> values:
Some examples : Some examples :
- `feat(auth): add AWS support.` = add a feature in authentication_bloc package. - `feat(auth): add AWS support. (#31)` = add a feature in authentication_bloc package, linked to the 31st issue.
- `docs: update readme.` = update **this** readme file. - `docs: update readme.` = update **this** readme file.
- `fix(crud)!: fix bug in awesome() function.` = fix a bug, `!` is important and indicate `BREAKING CHANGES`. - `fix(crud)!: fix bug in awesome() function. (#32)` = fix a bug, `!` is important and indicate `BREAKING CHANGES` linked with the 32nd issue.
Conventional commits are important for `melos version` command ! When you have finished developing, and are ready to close the issue, close it via your commit :
`feat(auth): add AWS support. (close #31)`
Note that your issue will be close after merging on master.
Before closing the issue, please check tests and update coverage. You might run :
```shell
melos run test:all # this will run all tests in this project
melos run gen-coverage # this will generate coverage report
melos run gen-class-models # this will generate plantuml class diagrams
melos run quality-check # this will run all targets generally expected in CI
melos run publish:validate # this will run a validation before publish packages
melos run publish # this will publish packages
```
> Note that only modified package will be verified and published.
#### Merge your work
After closing your issue, some work may have been done on master in the meantime. To keep a clean git history, please rebase before opening a change request.
Two situations :
- You have not yet created your branch and have committed locally :
```shell
git pull --rebase
```
- You are already working on your branch :
```shell
git rebase -i master
```
If possible, please use `--fixup` on your commit in interactive rebase.
Then, opend your PR, get the necessary approval and merge your work. Then you're done ✅.
#### Update version.
Once your work is on the master, you can update your package version. Please filter packages to update only your package. `--scope` is used to do it.
For example, after merging changes on `wyatt_otification_bloc` package, you might run :
```shell
melos version --scope="*wyatt_notification_bloc*"
```
In fact, melos will filter all packages that match the `wyatt_notification_block` pattern.
You can update check all packages tests by running :
```shell
melos run test
```
#### Publish your package
If package is ready for procution, publish it runnig :
```shell
melos publish --scope"*package*"
```
#### Badging #### Badging
@@ -130,6 +237,7 @@ In the package `readme.md` file, please specify the supported SDK:
```markdown ```markdown
![SDK: Dart & Flutter](https://img.shields.io/badge/SDK-Dart%20%7C%20Flutter-blue?style=flat-square) ![SDK: Dart & Flutter](https://img.shields.io/badge/SDK-Dart%20%7C%20Flutter-blue?style=flat-square)
``` ```
or or
![SDK: Dart](https://img.shields.io/badge/SDK-Dart-blue?style=flat-square) ![SDK: Dart](https://img.shields.io/badge/SDK-Dart-blue?style=flat-square)
@@ -162,3 +270,9 @@ Here you can change `package name` and `package version`.
![Status: Experimental](https://img.shields.io/badge/Status-WIP-red?style=flat-square) ![Status: Experimental](https://img.shields.io/badge/Status-WIP-red?style=flat-square)
This repository is maintained by Wyatt Studio but work is in progress. This repository is maintained by Wyatt Studio but work is in progress.
## Simple work flow diagramm
To sum up, here a simple work flow diagramm.
![Dev Wokflow](doc/workflow.svg)
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
escapedPath="$(echo `pwd` | sed 's/\//\\\//g')"
if grep flutter pubspec.yaml > /dev/null; then
if [ -d "coverage" ]; then
# combine line coverage info from package tests to a common file
if [ ! -d "$MELOS_ROOT_PATH/coverage" ]; then
mkdir "$MELOS_ROOT_PATH/coverage"
fi
sed "s/^SF:lib/SF:$escapedPath\/lib/g" coverage/lcov.info >> "$MELOS_ROOT_PATH/coverage/lcov.info"
rm -rf "coverage"
fi
fi
+15
View File
@@ -0,0 +1,15 @@
@startuml Simple Developpment Workflow
start
:Create an issue;
:Create branch related to the issue;
repeat :Commit related to the issue;
repeat while (feature is done)
:Update coverage;
:Close issue via last commit;
:Rebase from `master`;
:Create pull request;
:Merge branches;
:Update version;
:Publish package;
stop
@enduml
+25
View File
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.5 KiB

+1
View File
@@ -0,0 +1 @@
{"bricks":{"wyatt_clean_code":{"git":{"url":"ssh://git@git.wyatt-studio.fr:993/Wyatt-FOSS/wyatt-bricks.git","path":"bricks/wyatt_clean_code","ref":"033baa65408ec6b68e6a2ef43e64c24708ef16a1"}},"wyatt_feature_brick":{"git":{"url":"ssh://git@git.wyatt-studio.fr:993/Wyatt-FOSS/wyatt-bricks.git","path":"bricks/wyatt_feature_brick","ref":"033baa65408ec6b68e6a2ef43e64c24708ef16a1"}},"wyatt_package":{"git":{"url":"ssh://git@git.wyatt-studio.fr:993/Wyatt-FOSS/wyatt-bricks.git","path":"bricks/wyatt_package","ref":"033baa65408ec6b68e6a2ef43e64c24708ef16a1"}}}}
+13
View File
@@ -0,0 +1,13 @@
bricks:
wyatt_clean_code:
git:
url: ssh://git@git.wyatt-studio.fr:993/Wyatt-FOSS/wyatt-bricks.git
path: bricks/wyatt_clean_code
wyatt_feature_brick:
git:
url: ssh://git@git.wyatt-studio.fr:993/Wyatt-FOSS/wyatt-bricks.git
path: bricks/wyatt_feature_brick
wyatt_package:
git:
url: ssh://git@git.wyatt-studio.fr:993/Wyatt-FOSS/wyatt-bricks.git
path: bricks/wyatt_package
+35 -2
View File
@@ -21,8 +21,7 @@ scripts:
analyze: analyze:
run: | run: |
melos exec -c 10 -- \ melos exec -c 1 -- flutter analyze --fatal-infos
flutter analyze --fatal-infos
description: Run `flutter analyze` for all packages. description: Run `flutter analyze` for all packages.
format: format:
@@ -37,6 +36,40 @@ scripts:
run: git clean -x -d -f -q run: git clean -x -d -f -q
description: Clean things very deeply with `git clean`. description: Clean things very deeply with `git clean`.
test:selective_unit_test:
run: melos exec -- flutter test --no-pub --coverage
description: Run Flutter tests for a specific package in this project.
select-package:
dir-exists:
- test
ignore:
- '*example*'
test:all:
run: melos run test:selective_unit_test --no-select
description: Run all Flutter tests in this project.
gen-coverage: melos exec -- sh "\$MELOS_ROOT_PATH/combine_coverage.sh" && genhtml coverage/lcov.info -o coverage/html
gen-class-models:
run: melos exec -- dart pub global run dcdg -o models/class-models.puml
select-package:
ignore:
- '*example*'
quality-check:
run: |
melos clean && \
melos bootstrap && \
melos run test:all
description: Run all targets generally expected in CI for a full local quality check.
publish:validate:
run: melos publish --diff="origin/$DRONE_COMMIT_BRANCH...HEAD" --yes
# publish:
# run: melos publish --diff="origin/$DRONE_COMMIT_BRANCH...HEAD" --no-dry-run --yes
# Additional cleanup lifecycle script, executed when `melos clean` is run. # Additional cleanup lifecycle script, executed when `melos clean` is run.
postclean: > postclean: >
melos exec -c 6 -- "flutter clean" melos exec -c 6 -- "flutter clean"
+9
View File
@@ -1,3 +1,12 @@
## 2.3.0
- **REFACTOR**: follow melos versioning.
- **FIX**: change file include to keep coherence.
- **FEAT**: exclude generated code, and add dart>2.16 rules.
- **FEAT**: add overwrite protection.
- **FEAT**: add 2.2.2 files for archive.
- **FEAT**: add utils script to easely bump version.
## 2.2.2 ## 2.2.2
- **DOCS**: add publish_to self hosted pub server. - **DOCS**: add publish_to self hosted pub server.
+6 -9
View File
@@ -20,7 +20,7 @@
![SDK: Dart & Flutter](https://img.shields.io/badge/SDK-Dart%20%7C%20Flutter-blue?style=flat-square) ![SDK: Dart & Flutter](https://img.shields.io/badge/SDK-Dart%20%7C%20Flutter-blue?style=flat-square)
This package provides lint rules for Dart and Flutter which are used at [Wyatt](https://wyattapp.io) and [Wyatt Studio](https://wyatt-studio.fr). For more information, see the complete list of options in **lib/analysis_options.2.0.0.yaml**. This package provides lint rules for Dart and Flutter which are used at [Wyatt](https://wyattapp.io) and [Wyatt Studio](https://wyatt-studio.fr). For more information, see the complete list of options in **lib/analysis_options.2.3.0.yaml**.
**Note**: This package was heavily inspired by [pedantic](https://github.com/dart-lang/pedantic), [Very Good Analysis](https://github.com/VeryGoodOpenSource/very_good_analysis) and the official [flutter_lints](https://pub.dev/packages/flutter_lints). **Note**: This package was heavily inspired by [pedantic](https://github.com/dart-lang/pedantic), [Very Good Analysis](https://github.com/VeryGoodOpenSource/very_good_analysis) and the official [flutter_lints](https://pub.dev/packages/flutter_lints).
@@ -29,12 +29,9 @@ This package provides lint rules for Dart and Flutter which are used at [Wyatt](
To use the lints, add a dependency in your `pubspec.yaml`: To use the lints, add a dependency in your `pubspec.yaml`:
```yaml ```yaml
dev_dependencies: wyatt_analysis:
wyatt_analysis: hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
git: version: 2.3.0
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_analysis-v2.0.0
path: packages/wyatt_analysis
``` ```
Then, add an include in `analysis_options.yaml`: Then, add an include in `analysis_options.yaml`:
@@ -52,13 +49,13 @@ include: package:wyatt_analysis/analysis_options.flutter.yaml
If you wish to restrict the lint version, specify a version of `analysis_options.yaml` instead: If you wish to restrict the lint version, specify a version of `analysis_options.yaml` instead:
```yaml ```yaml
include: package:wyatt_analysis/analysis_options.flutter.2.0.0.yaml include: package:wyatt_analysis/analysis_options.flutter.2.3.0.yaml
``` ```
If you just want **Dart** version: If you just want **Dart** version:
```yaml ```yaml
include: package:wyatt_analysis/analysis_options.2.0.0.yaml include: package:wyatt_analysis/analysis_options.2.3.0.yaml
``` ```
## Suppressing Lints ## Suppressing Lints
@@ -0,0 +1,206 @@
# 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/>.
analyzer:
language:
strict-casts: true
strict-inference: true
strict-raw-types: true
errors:
todo: ignore
close_sinks: ignore
missing_required_param: error
missing_return: error
exclude:
- example/**
- lib/l10n/**
- lib/generated/**
- lib/generated_plugin_registrant.dart
- test/.test_coverage.dart
linter:
rules:
- always_declare_return_types
- always_require_non_null_named_parameters
- always_put_control_body_on_new_line
- always_put_required_named_parameters_first
- always_use_package_imports
- annotate_overrides
- avoid_bool_literals_in_conditional_expressions
- avoid_catching_errors
- avoid_double_and_int_checks
- avoid_dynamic_calls
- avoid_empty_else
- avoid_equals_and_hash_code_on_mutable_classes
- avoid_escaping_inner_quotes
- avoid_field_initializers_in_const_classes
- avoid_function_literals_in_foreach_calls
- avoid_init_to_null
- avoid_js_rounded_ints
- avoid_null_checks_in_equality_operators
- avoid_positional_boolean_parameters
- avoid_private_typedef_functions
- avoid_redundant_argument_values
- avoid_relative_lib_imports
- avoid_renaming_method_parameters
- avoid_return_types_on_setters
- avoid_returning_null
- avoid_returning_null_for_future
- avoid_returning_null_for_void
- avoid_returning_this
- avoid_setters_without_getters
- avoid_shadowing_type_parameters
- avoid_single_cascade_in_expression_statements
- avoid_slow_async_io
- avoid_type_to_string
- avoid_types_as_parameter_names
- avoid_types_on_closure_parameters
- avoid_unused_constructor_parameters
- avoid_void_async
- await_only_futures
- camel_case_extensions
- camel_case_types
- cancel_subscriptions
- cascade_invocations
- cast_nullable_to_non_nullable
- comment_references
- conditional_uri_does_not_exist
- constant_identifier_names
- control_flow_in_finally
- curly_braces_in_flow_control_structures
- depend_on_referenced_packages
- deprecated_consistency
- directives_ordering
- empty_catches
- empty_constructor_bodies
- empty_statements
- eol_at_end_of_file
- exhaustive_cases
- file_names
- flutter_style_todos
- hash_and_equals
- implementation_imports
- iterable_contains_unrelated_type
- join_return_with_assignment
- leading_newlines_in_multiline_strings
- library_names
- library_prefixes
- library_private_types_in_public_api
- lines_longer_than_80_chars
- list_remove_unrelated_type
- literal_only_boolean_expressions
- missing_whitespace_between_adjacent_strings
- no_adjacent_strings_in_list
- no_default_cases
- no_duplicate_case_values
- no_leading_underscores_for_local_identifiers
- no_runtimeType_toString
- non_constant_identifier_names
- noop_primitive_operations
- null_check_on_nullable_type_parameter
- null_closures
- one_member_abstracts
- only_throw_errors
- overridden_fields
- package_api_docs
- package_names
- package_prefixed_library_names
- parameter_assignments
- prefer_adjacent_string_concatenation
- prefer_asserts_in_initializer_lists
- prefer_asserts_with_message
- prefer_collection_literals
- prefer_conditional_assignment
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_const_literals_to_create_immutables
- prefer_constructors_over_static_methods
- prefer_contains
- prefer_equal_for_default_values
- prefer_expression_function_bodies
- prefer_final_fields
- prefer_final_in_for_each
- prefer_final_locals
- prefer_for_elements_to_map_fromIterable
- prefer_function_declarations_over_variables
- prefer_generic_function_type_aliases
- prefer_if_elements_to_conditional_expressions
- prefer_if_null_operators
- prefer_initializing_formals
- prefer_inlined_adds
- prefer_int_literals
- prefer_interpolation_to_compose_strings
- prefer_is_empty
- prefer_is_not_empty
- prefer_is_not_operator
- prefer_iterable_whereType
- prefer_mixin
- prefer_null_aware_operators
- prefer_null_aware_method_calls
- prefer_single_quotes
- prefer_spread_collections
- prefer_typing_uninitialized_variables
- prefer_void_to_null
- provide_deprecation_message
- recursive_getters
- require_trailing_commas
- slash_for_doc_comments
- sort_unnamed_constructors_first
- test_types_in_equals
- throw_in_finally
- tighten_type_of_initializing_formals
- type_annotate_public_apis
- type_init_formals
- unawaited_futures
- unnecessary_await_in_return
- unnecessary_brace_in_string_interps
- unnecessary_const
- unnecessary_constructor_name
- unnecessary_getters_setters
- unnecessary_lambdas
- unnecessary_late
- unnecessary_new
- unnecessary_null_aware_assignments
- unnecessary_null_checks
- unnecessary_null_in_if_null_operators
- unnecessary_nullable_for_final_variable_declarations
- unnecessary_overrides
- unnecessary_parenthesis
- unnecessary_raw_strings
- unnecessary_statements
- unnecessary_string_escapes
- unnecessary_string_interpolations
- unnecessary_this
- unrelated_type_equality_checks
- unsafe_html
- use_enums
- use_function_type_syntax_for_parameters
- use_if_null_to_convert_nulls_to_bools
- use_is_even_rather_than_modulo
- use_late_for_private_fields_and_variables
- use_named_constants
- use_raw_strings
- use_rethrow_when_possible
- use_setters_to_change_properties
- use_string_buffers
- use_super_parameters
- use_test_throws_matchers
- use_to_and_as_if_applicable
- valid_regexps
- void_checks
@@ -0,0 +1,213 @@
# 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/>.
analyzer:
language:
strict-casts: true
strict-inference: true
strict-raw-types: true
errors:
todo: info
close_sinks: ignore
missing_required_param: error
missing_return: error
exclude:
- lib/l10n/**
- lib/generated/**
- lib/gen/**
- lib/generated_plugin_registrant.dart
- test/.test_coverage.dart
- '**/*.g.dart'
- '**/*.freezed.dart'
linter:
rules:
- always_declare_return_types
- always_put_control_body_on_new_line
- always_put_required_named_parameters_first
- always_require_non_null_named_parameters
- always_use_package_imports
- annotate_overrides
- avoid_bool_literals_in_conditional_expressions
- avoid_catching_errors
- avoid_double_and_int_checks
- avoid_dynamic_calls
- avoid_empty_else
- avoid_equals_and_hash_code_on_mutable_classes
- avoid_escaping_inner_quotes
- avoid_field_initializers_in_const_classes
- avoid_final_parameters
- avoid_function_literals_in_foreach_calls
- avoid_init_to_null
- avoid_js_rounded_ints
- avoid_multiple_declarations_per_line
- avoid_null_checks_in_equality_operators
- avoid_positional_boolean_parameters
- avoid_private_typedef_functions
- avoid_redundant_argument_values
- avoid_relative_lib_imports
- avoid_renaming_method_parameters
- avoid_return_types_on_setters
- avoid_returning_null
- avoid_returning_null_for_future
- avoid_returning_null_for_void
- avoid_returning_this
- avoid_setters_without_getters
- avoid_shadowing_type_parameters
- avoid_single_cascade_in_expression_statements
- avoid_slow_async_io
- avoid_type_to_string
- avoid_types_as_parameter_names
- avoid_types_on_closure_parameters
- avoid_unused_constructor_parameters
- avoid_void_async
- await_only_futures
- camel_case_extensions
- camel_case_types
- cancel_subscriptions
- cascade_invocations
- cast_nullable_to_non_nullable
- comment_references
- conditional_uri_does_not_exist
- constant_identifier_names
- control_flow_in_finally
- curly_braces_in_flow_control_structures
- depend_on_referenced_packages
- deprecated_consistency
- directives_ordering
- empty_catches
- empty_constructor_bodies
- empty_statements
- eol_at_end_of_file
- exhaustive_cases
- file_names
- flutter_style_todos
- hash_and_equals
- implementation_imports
- iterable_contains_unrelated_type
- join_return_with_assignment
- leading_newlines_in_multiline_strings
- library_names
- library_prefixes
- library_private_types_in_public_api
- lines_longer_than_80_chars
- list_remove_unrelated_type
- literal_only_boolean_expressions
- missing_whitespace_between_adjacent_strings
- no_adjacent_strings_in_list
- no_default_cases
- no_duplicate_case_values
- no_leading_underscores_for_library_prefixes
- no_leading_underscores_for_local_identifiers
- no_runtimeType_toString
- non_constant_identifier_names
- noop_primitive_operations
- null_check_on_nullable_type_parameter
- null_closures
- one_member_abstracts
- only_throw_errors
- overridden_fields
- package_api_docs
- package_names
- package_prefixed_library_names
- parameter_assignments
- prefer_adjacent_string_concatenation
- prefer_asserts_in_initializer_lists
- prefer_asserts_with_message
- prefer_collection_literals
- prefer_conditional_assignment
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_const_literals_to_create_immutables
- prefer_constructors_over_static_methods
- prefer_contains
- prefer_equal_for_default_values
- prefer_expression_function_bodies
- prefer_final_fields
- prefer_final_in_for_each
- prefer_final_locals
- prefer_for_elements_to_map_fromIterable
- prefer_function_declarations_over_variables
- prefer_generic_function_type_aliases
- prefer_if_elements_to_conditional_expressions
- prefer_if_null_operators
- prefer_initializing_formals
- prefer_inlined_adds
- prefer_int_literals
- prefer_interpolation_to_compose_strings
- prefer_is_empty
- prefer_is_not_empty
- prefer_is_not_operator
- prefer_iterable_whereType
- prefer_mixin
- prefer_null_aware_method_calls
- prefer_null_aware_operators
- prefer_single_quotes
- prefer_spread_collections
- prefer_typing_uninitialized_variables
- prefer_void_to_null
- provide_deprecation_message
- recursive_getters
- require_trailing_commas
- slash_for_doc_comments
- sort_constructors_first
- sort_unnamed_constructors_first
- test_types_in_equals
- throw_in_finally
- tighten_type_of_initializing_formals
- type_annotate_public_apis
- type_init_formals
- unawaited_futures
- unnecessary_await_in_return
- unnecessary_brace_in_string_interps
- unnecessary_const
- unnecessary_constructor_name
- unnecessary_getters_setters
- unnecessary_lambdas
- unnecessary_late
- unnecessary_new
- unnecessary_null_aware_assignments
- unnecessary_null_checks
- unnecessary_null_in_if_null_operators
- unnecessary_nullable_for_final_variable_declarations
- unnecessary_overrides
- unnecessary_parenthesis
- unnecessary_raw_strings
- unnecessary_statements
- unnecessary_string_escapes
- unnecessary_string_interpolations
- unnecessary_this
- unnecessary_to_list_in_spreads
- unrelated_type_equality_checks
- unsafe_html
- use_enums
- use_function_type_syntax_for_parameters
- use_if_null_to_convert_nulls_to_bools
- use_is_even_rather_than_modulo
- use_late_for_private_fields_and_variables
- use_named_constants
- use_raw_strings
- use_rethrow_when_possible
- use_setters_to_change_properties
- use_string_buffers
- use_super_parameters
- use_test_throws_matchers
- use_to_and_as_if_applicable
- valid_regexps
- void_checks
@@ -14,7 +14,7 @@
# 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/>.
include: package:wyatt_analysis/analysis_options.2.1.1.yaml include: package:wyatt_analysis/analysis_options.2.2.0.yaml
linter: linter:
rules: rules:
@@ -0,0 +1,32 @@
# 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/>.
include: package:wyatt_analysis/analysis_options.2.2.2.yaml
linter:
rules:
- avoid_print
- avoid_unnecessary_containers
- avoid_web_libraries_in_flutter
- no_logic_in_create_state
- sized_box_for_whitespace
- sized_box_shrink_expand
- sort_child_properties_last
- use_build_context_synchronously
- use_colored_box
- use_decorated_box
- use_full_hex_values_for_flutter_colors
- use_key_in_widget_constructors
@@ -0,0 +1,32 @@
# 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/>.
include: package:wyatt_analysis/analysis_options.2.3.0.yaml
linter:
rules:
- avoid_print
- avoid_unnecessary_containers
- avoid_web_libraries_in_flutter
- no_logic_in_create_state
- sized_box_for_whitespace
- sized_box_shrink_expand
- sort_child_properties_last
- use_build_context_synchronously
- use_colored_box
- use_decorated_box
- use_full_hex_values_for_flutter_colors
- use_key_in_widget_constructors
@@ -1,3 +1,4 @@
# Copyright (C) 2022 WYATT GROUP # Copyright (C) 2022 WYATT GROUP
# Please see the AUTHORS file for details. # Please see the AUTHORS file for details.
# #
@@ -14,4 +15,5 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
include: package:wyatt_analysis/analysis_options.flutter.2.2.1.yaml include: package:wyatt_analysis/analysis_options.flutter.2.3.0.yaml
@@ -1,3 +1,4 @@
# Copyright (C) 2022 WYATT GROUP # Copyright (C) 2022 WYATT GROUP
# Please see the AUTHORS file for details. # Please see the AUTHORS file for details.
# #
@@ -14,4 +15,5 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
include: package:wyatt_analysis/analysis_options.2.2.1.yaml include: package:wyatt_analysis/analysis_options.2.3.0.yaml
@@ -15,8 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
/// Wyatt analyzer settings and best practices /// Wyatt analyzer settings and best practices
/// used internally at [Wyatt](https://wyattapp.io) /// used internally at [Wyatt Studio](https://wyatt-studio.fr).
/// and [Wyatt Studio](https://wyatt-studio.fr).
library wyatt_analysis; library wyatt_analysis;
const String wyattAnalysisVersion = '2.2.1'; const String wyattAnalysisVersion = '2.3.0';
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env sh
# 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/>.
usage="
usage:
$(basename "$0") <previous_version> <new_version> -- create new analyzer files.
where:
previous_version: last version, for example \`2.3.0\`
new_version: new version, for example \`2.4.0\`"
old=$1
new=$2
if [ -z "$old" ]; then
echo "previous_version cannot be null"
echo "${usage}"
exit 1
fi
if [ -z "$new" ]; then
echo "new_version cannot be null"
echo "${usage}"
exit 1
fi
SCRIPT_PATH="${BASH_SOURCE:-$0}"
ABS_SCRIPT_PATH="$(realpath "${SCRIPT_PATH}")"
ABS_DIRECTORY="$(dirname "${ABS_SCRIPT_PATH}")"
LIB="${ABS_DIRECTORY}/lib/wyatt_analysis.dart"
OPTIONS="${ABS_DIRECTORY}/lib/analysis_options.yaml"
OLD_OPTIONS="${ABS_DIRECTORY}/lib/analysis_options.${old}.yaml"
NEW_OPTIONS="${ABS_DIRECTORY}/lib/analysis_options.${new}.yaml"
OPTIONS_FLUTTER="${ABS_DIRECTORY}/lib/analysis_options.flutter.yaml"
OLD_OPTIONS_FLUTTER="${ABS_DIRECTORY}/lib/analysis_options.flutter.${old}.yaml"
NEW_OPTIONS_FLUTTER="${ABS_DIRECTORY}/lib/analysis_options.flutter.${new}.yaml"
if [ ! -e "$OLD_OPTIONS" ]; then
echo "analysis_options.${old}.yaml doesn't exists"
echo "${usage}"
exit 1
fi
if [ ! -e "$OLD_OPTIONS_FLUTTER" ]; then
echo "analysis_options.flutter.${old}.yaml doesn't exists"
echo "${usage}"
exit 1
fi
if [ -e "$NEW_OPTIONS" ]; then
echo "analysis_options.${new}.yaml already exists"
echo "${usage}"
exit 1
fi
if [ -e "$NEW_OPTIONS_FLUTTER" ]; then
echo "analysis_options.flutter.${old}.yaml already exists"
echo "${usage}"
exit 1
fi
# Copy previous version files
cp "${OLD_OPTIONS}" "${NEW_OPTIONS}"
cp "${OLD_OPTIONS_FLUTTER}" "${NEW_OPTIONS_FLUTTER}"
# Search and replace old version string occurences in new files
sed -e "s/${old}/${new}/g" "${NEW_OPTIONS_FLUTTER}" > tempfile.tmp
mv -f tempfile.tmp "${NEW_OPTIONS_FLUTTER}"
sed -e "s/${old}/${new}/g" "${LIB}" > tempfile.tmp
mv -f tempfile.tmp "${LIB}"
sed -e "s/${old}/${new}/g" "README.md" > tempfile.tmp
mv -f tempfile.tmp "README.md"
echo "
# 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/>.
include: package:wyatt_analysis/analysis_options.${new}.yaml
" > "${OPTIONS}"
echo "
# 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/>.
include: package:wyatt_analysis/analysis_options.flutter.${new}.yaml
" > "${OPTIONS_FLUTTER}"
exit 0
+2 -2
View File
@@ -1,7 +1,7 @@
name: wyatt_analysis name: wyatt_analysis
description: Lint rules for Dart and Flutter used internally at Wyatt and Wyatt Studio. description: Lint rules for Dart and Flutter used internally at Wyatt Studio.
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis
version: 2.2.2 version: 2.3.0
publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub
+17
View File
@@ -1,3 +1,20 @@
## 0.1.0+1
- Update a dependency to the latest release.
## 0.1.0
> Note: This release has breaking changes.
- **REFACTOR**: update naming methodes (close #47).
- **REFACTOR**: update example. (close #38).
- **FIX**: fix execute method on error handler. (#38).
- **FEAT**: update usecase en add & sepcific case. (#38).
- **FEAT**: add obsevers for alternative usecase scenarions (#38).
- **DOCS**: generate class model. (#38).
- **DOCS**: update Readme. (#38).
- **BREAKING** **CHORE**: update exports. (#38).
## 0.0.2 ## 0.0.2
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries. - Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
+61 -12
View File
@@ -31,10 +31,10 @@ The Wyatt Architecture for Flutter.
## Features ## Features
* Usecase - Usecase
* Repository - Repository
* DataSource - DataSource
* Entity - Entity
## Usage ## Usage
@@ -73,19 +73,20 @@ abstract class PhotoRepository extends BaseRepository {
} }
``` ```
> Here the repository is just a proxy of the data sources with result type (to have beautiful error handling). > Here the repository is just a proxy of the data sources with result type (to have beautiful error handling).
And finaly create your different usecases by using `UseCase<Parameters, ReturnType>` : And finaly create your different usecases :
Several use cases are supported :
- Classic usecase :
```dart ```dart
class RetrieveAllPhoto extends UseCase<QueryParameters, List<Photo>> { class Test extends AsyncUseCase<QueryParameters, List<Photo>>> {
final PhotoRepository _photoRepository;
RetrieveAllPhotos(this._photoRepository);
@override @override
FutureResult<List<Photo>> call(QueryParameters params) { FuturOrResult<List<Photo>>> call(QueryParameters? params) {
final photos = _photoRepository.getAllPhotos( final photos = _photoRepository.getAllPhotos(
start: params.start, start: params.start,
limit: params.limit, limit: params.limit,
); );
@@ -94,6 +95,54 @@ class RetrieveAllPhoto extends UseCase<QueryParameters, List<Photo>> {
} }
``` ```
You can add alternatve scenarios and check pre/post conditions using our extensions :
```dart
class SearchPhotos extends AsyncUseCase<QueryParameters, List<Photo>>> {
@override
FutureOrResult<List<Photo>>> call(QueryParameters? params) {
final photos = _photoRepository.getAllPhotos(
start: params.start,
limit: params.limit,
);
return photos;
}
@override
FutureOr<void> onStart(QueryParameters? params) {
if(params.start == null || params.limit == null){
throw ClientException('Préconndition non valides');
}
}
}
```
You can implement error scenarios overriding `onError`, or check postconditions by overriding `onComplete` .
- Stream usecase :
```dart
class SearchPhotos extends StreamUseCase<QueryParameters, List<Photo>>> {
@override
FutureOrResult<Stream<List<Photo>>>> call(QueryParameters? params) {
final photos = _photoRepository.getAllPhotos(
start: params.start,
limit: params.limit,
);
return photos;
}
}
```
On this case, observers allow you to add alternative scénarios when data changed, overriding `onData` or `onDone`.
Please note that to use handlers, call `call` method and not `execute`.
> In fact, here we need a new parameter object, so let's create it: > In fact, here we need a new parameter object, so let's create it:
```dart ```dart
@@ -24,9 +24,8 @@ import 'package:wyatt_http_client/wyatt_http_client.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class AlbumApiDataSourceImpl extends AlbumRemoteDataSource { class AlbumApiDataSourceImpl extends AlbumRemoteDataSource {
final MiddlewareClient _client;
AlbumApiDataSourceImpl(this._client); AlbumApiDataSourceImpl(this._client);
final MiddlewareClient _client;
@override @override
Future<Album> getAlbum(int id) async { Future<Album> getAlbum(int id) async {
@@ -24,9 +24,8 @@ import 'package:wyatt_http_client/wyatt_http_client.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class PhotoApiDataSourceImpl extends PhotoRemoteDataSource { class PhotoApiDataSourceImpl extends PhotoRemoteDataSource {
final MiddlewareClient _client;
PhotoApiDataSourceImpl(this._client); PhotoApiDataSourceImpl(this._client);
final MiddlewareClient _client;
@override @override
Future<Photo> getPhoto(int id) async { Future<Photo> getPhoto(int id) async {
@@ -24,57 +24,58 @@ import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class PhotoRepositoryImpl extends PhotoRepository { class PhotoRepositoryImpl extends PhotoRepository {
final PhotoRemoteDataSource _photoRemoteDataSource;
final AlbumRemoteDataSource _albumRemoteDataSource;
final FavoriteLocalDataSource _favoriteLocalDataSource;
PhotoRepositoryImpl( PhotoRepositoryImpl(
this._photoRemoteDataSource, this._photoRemoteDataSource,
this._albumRemoteDataSource, this._albumRemoteDataSource,
this._favoriteLocalDataSource, this._favoriteLocalDataSource,
); );
final PhotoRemoteDataSource _photoRemoteDataSource;
final AlbumRemoteDataSource _albumRemoteDataSource;
final FavoriteLocalDataSource _favoriteLocalDataSource;
@override @override
FutureResult<void> addPhotoToFavorites(Photo photo) => Result.tryCatchAsync( FutureOrResult<void> addPhotoToFavorites(Photo photo) => Result.tryCatchAsync(
() => _favoriteLocalDataSource.addPhotoToFavorites(photo), () => _favoriteLocalDataSource.addPhotoToFavorites(photo),
(error) => ClientException('Cannot add photo to favorites.'), (error) => ClientException('Cannot add photo to favorites.'),
); );
@override @override
FutureResult<bool> checkIfPhotoIsInFavorites(int id) => Result.tryCatchAsync( FutureOrResult<bool> checkIfPhotoIsInFavorites(int id) =>
Result.tryCatchAsync(
() => _favoriteLocalDataSource.checkIfPhotoIsInFavorites(id), () => _favoriteLocalDataSource.checkIfPhotoIsInFavorites(id),
(error) => (error) =>
ClientException('Cannot check if photo `$id` is in favorites.'), ClientException('Cannot check if photo `$id` is in favorites.'),
); );
@override @override
FutureResult<void> deletePhotoFromFavorites(int id) => Result.tryCatchAsync( FutureOrResult<void> deletePhotoFromFavorites(int id) => Result.tryCatchAsync(
() => _favoriteLocalDataSource.deletePhotoFromFavorites(id), () => _favoriteLocalDataSource.deletePhotoFromFavorites(id),
(error) => ClientException('Cannot delete photo `$id` from favorites.'), (error) => ClientException('Cannot delete photo `$id` from favorites.'),
); );
@override @override
FutureResult<Album> getAlbum(int id) => Result.tryCatchAsync( FutureOrResult<Album> getAlbum(int id) => Result.tryCatchAsync(
() => _albumRemoteDataSource.getAlbum(id), () => _albumRemoteDataSource.getAlbum(id),
(error) => ServerException('Cannot retrieve album $id.'), (error) => ServerException('Cannot retrieve album $id.'),
); );
@override @override
FutureResult<List<Album>> getAllAlbums({int? start, int? limit}) => FutureOrResult<List<Album>> getAllAlbums({int? start, int? limit}) =>
Result.tryCatchAsync( Result.tryCatchAsync(
() => _albumRemoteDataSource.getAllAlbums(start: start, limit: limit), () => _albumRemoteDataSource.getAllAlbums(start: start, limit: limit),
(error) => ServerException('Cannot retrieve all albums.'), (error) => ServerException('Cannot retrieve all albums.'),
); );
@override @override
FutureResult<List<Photo>> getAllPhotos({int? start, int? limit}) async => FutureOrResult<List<Photo>> getAllPhotos({int? start, int? limit}) async =>
Result.tryCatchAsync( Result.tryCatchAsync(
() => _photoRemoteDataSource.getAllPhotos(start: start, limit: limit), () => _photoRemoteDataSource.getAllPhotos(start: start, limit: limit),
(error) => ServerException('Cannot retrieve all photos.'), (error) => ServerException('Cannot retrieve all photos.'),
); );
@override @override
FutureResult<List<Photo>> getAllPhotosFromFavorites() async { FutureOrResult<List<Photo>> getAllPhotosFromFavorites() async {
try { try {
final response = <Photo>[]; final response = <Photo>[];
final favorites = final favorites =
@@ -95,13 +96,13 @@ class PhotoRepositoryImpl extends PhotoRepository {
} }
@override @override
FutureResult<Photo> getPhoto(int id) => Result.tryCatchAsync( FutureOrResult<Photo> getPhoto(int id) => Result.tryCatchAsync(
() => _photoRemoteDataSource.getPhoto(id), () => _photoRemoteDataSource.getPhoto(id),
(error) => ServerException('Cannot retrieve photo $id.'), (error) => ServerException('Cannot retrieve photo $id.'),
); );
@override @override
FutureResult<List<Photo>> getPhotosFromAlbum( FutureOrResult<List<Photo>> getPhotosFromAlbum(
int albumId, { int albumId, {
int? start, int? start,
int? limit, int? limit,
@@ -19,17 +19,17 @@ import 'package:architecture_example/domain/entities/photo.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
abstract class PhotoRepository extends BaseRepository { abstract class PhotoRepository extends BaseRepository {
FutureResult<Album> getAlbum(int id); FutureOrResult<Album> getAlbum(int id);
FutureResult<List<Album>> getAllAlbums({int? start, int? limit}); FutureOrResult<List<Album>> getAllAlbums({int? start, int? limit});
FutureResult<Photo> getPhoto(int id); FutureOrResult<Photo> getPhoto(int id);
FutureResult<List<Photo>> getAllPhotos({int? start, int? limit}); FutureOrResult<List<Photo>> getAllPhotos({int? start, int? limit});
FutureResult<List<Photo>> getPhotosFromAlbum( FutureOrResult<List<Photo>> getPhotosFromAlbum(
int albumId, { int albumId, {
int? start, int? start,
int? limit, int? limit,
}); });
FutureResult<void> addPhotoToFavorites(Photo photo); FutureOrResult<void> addPhotoToFavorites(Photo photo);
FutureResult<void> deletePhotoFromFavorites(int id); FutureOrResult<void> deletePhotoFromFavorites(int id);
FutureResult<List<Photo>> getAllPhotosFromFavorites(); FutureOrResult<List<Photo>> getAllPhotosFromFavorites();
FutureResult<bool> checkIfPhotoIsInFavorites(int id); FutureOrResult<bool> checkIfPhotoIsInFavorites(int id);
} }
@@ -14,18 +14,27 @@
// 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 'dart:async';
import 'package:architecture_example/domain/entities/photo.dart'; import 'package:architecture_example/domain/entities/photo.dart';
import 'package:architecture_example/domain/repositories/photo_repository.dart'; import 'package:architecture_example/domain/repositories/photo_repository.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
class AddPhotoToFavorites extends UseCase<Photo, List<Photo>> { class AddPhotoToFavorites extends AsyncUseCase<Photo, List<Photo>> {
final PhotoRepository _photoRepository;
AddPhotoToFavorites(this._photoRepository); AddPhotoToFavorites(this._photoRepository);
final PhotoRepository _photoRepository;
@override @override
FutureResult<List<Photo>> call(Photo params) async { FutureOrResult<List<Photo>> execute(Photo? params) async {
await _photoRepository.addPhotoToFavorites(params); await _photoRepository.addPhotoToFavorites(params!);
return _photoRepository.getAllPhotosFromFavorites(); return _photoRepository.getAllPhotosFromFavorites();
} }
@override
FutureOr<void> onStart(Photo? params) {
if (params == null) {
throw ClientException('Photo cannot be null');
}
}
} }
@@ -14,15 +14,24 @@
// 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 'dart:async';
import 'package:architecture_example/domain/repositories/photo_repository.dart'; import 'package:architecture_example/domain/repositories/photo_repository.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
class CheckIfPhotoIsInFavorites extends UseCase<int, bool> { class CheckIfPhotoIsInFavorites extends AsyncUseCase<int, bool> {
final PhotoRepository _photoRepository;
CheckIfPhotoIsInFavorites(this._photoRepository); CheckIfPhotoIsInFavorites(this._photoRepository);
final PhotoRepository _photoRepository;
@override @override
FutureResult<bool> call(int params) async => FutureOrResult<bool> execute(int? params) async =>
_photoRepository.checkIfPhotoIsInFavorites(params); _photoRepository.checkIfPhotoIsInFavorites(params!);
@override
FutureOr<void> onStart(int? params) {
if (params == null) {
throw ClientException('id cannot be null');
}
}
} }
@@ -18,13 +18,13 @@ import 'package:architecture_example/domain/entities/photo.dart';
import 'package:architecture_example/domain/repositories/photo_repository.dart'; import 'package:architecture_example/domain/repositories/photo_repository.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
class DisplayFavorites extends UseCase<void, List<Photo>> { class DisplayFavorites extends AsyncUseCase<NoParam, List<Photo>> {
final PhotoRepository _photoRepository;
DisplayFavorites(this._photoRepository); DisplayFavorites(this._photoRepository);
final PhotoRepository _photoRepository;
@override @override
FutureResult<List<Photo>> call(void params) { FutureOrResult<List<Photo>> execute(void params) {
final photos = _photoRepository.getAllPhotosFromFavorites(); final photos = _photoRepository.getAllPhotosFromFavorites();
return photos; return photos;
} }
@@ -14,18 +14,27 @@
// 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 'dart:async';
import 'package:architecture_example/domain/entities/photo.dart'; import 'package:architecture_example/domain/entities/photo.dart';
import 'package:architecture_example/domain/repositories/photo_repository.dart'; import 'package:architecture_example/domain/repositories/photo_repository.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
class DisplayPhoto extends UseCase<int, Photo> { class DisplayPhoto extends AsyncUseCase<int, Photo> {
final PhotoRepository _photoRepository;
DisplayPhoto(this._photoRepository); DisplayPhoto(this._photoRepository);
final PhotoRepository _photoRepository;
@override @override
FutureResult<Photo> call(int params) { FutureOrResult<Photo> execute(int? params) {
final photo = _photoRepository.getPhoto(params); final photo = _photoRepository.getPhoto(params!);
return photo; return photo;
} }
@override
FutureOr<void> onStart(int? params) {
if (params == null) {
throw ClientException('id cannot be null');
}
}
} }
@@ -14,24 +14,33 @@
// 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 'dart:async';
import 'package:architecture_example/domain/entities/photo.dart'; import 'package:architecture_example/domain/entities/photo.dart';
import 'package:architecture_example/domain/repositories/photo_repository.dart'; import 'package:architecture_example/domain/repositories/photo_repository.dart';
import 'package:architecture_example/domain/usecases/photos/params/query_parameters.dart'; import 'package:architecture_example/domain/usecases/photos/params/query_parameters.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
class OpenAlbum extends UseCase<QueryParameters, List<Photo>> { class OpenAlbum extends AsyncUseCase<QueryParameters, List<Photo>> {
final PhotoRepository _photoRepository;
OpenAlbum(this._photoRepository); OpenAlbum(this._photoRepository);
final PhotoRepository _photoRepository;
@override @override
FutureResult<List<Photo>> call(QueryParameters params) { FutureOrResult<List<Photo>> execute(QueryParameters? params) {
final photos = _photoRepository.getPhotosFromAlbum( final photos = _photoRepository.getPhotosFromAlbum(
params.albumId, params!.albumId,
start: params.start, start: params.start,
limit: params.limit, limit: params.limit,
); );
return photos; return photos;
} }
@override
FutureOr<void> onStart(QueryParameters? params) {
if (params == null) {
throw ClientException('params cannot be null');
}
}
} }
@@ -15,9 +15,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
class QueryParameters { class QueryParameters {
QueryParameters(this.start, this.limit, {this.albumId = -1});
final int albumId; final int albumId;
final int? start; final int? start;
final int? limit; final int? limit;
QueryParameters(this.start, this.limit, {this.albumId = -1});
} }
@@ -14,18 +14,27 @@
// 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 'dart:async';
import 'package:architecture_example/domain/entities/photo.dart'; import 'package:architecture_example/domain/entities/photo.dart';
import 'package:architecture_example/domain/repositories/photo_repository.dart'; import 'package:architecture_example/domain/repositories/photo_repository.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
class RemovePhotoFromFavorites extends UseCase<int, List<Photo>> { class RemovePhotoFromFavorites extends AsyncUseCase<int, List<Photo>> {
final PhotoRepository _photoRepository;
RemovePhotoFromFavorites(this._photoRepository); RemovePhotoFromFavorites(this._photoRepository);
final PhotoRepository _photoRepository;
@override @override
FutureResult<List<Photo>> call(int params) async { FutureOrResult<List<Photo>> execute(int? params) async {
await _photoRepository.deletePhotoFromFavorites(params); await _photoRepository.deletePhotoFromFavorites(params!);
return _photoRepository.getAllPhotosFromFavorites(); return _photoRepository.getAllPhotosFromFavorites();
} }
@override
FutureOr<void> onStart(int? params) {
if (params == null) {
throw ClientException('id cannot be null');
}
}
} }
@@ -14,22 +14,31 @@
// 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 'dart:async';
import 'package:architecture_example/domain/entities/album.dart'; import 'package:architecture_example/domain/entities/album.dart';
import 'package:architecture_example/domain/repositories/photo_repository.dart'; import 'package:architecture_example/domain/repositories/photo_repository.dart';
import 'package:architecture_example/domain/usecases/photos/params/query_parameters.dart'; import 'package:architecture_example/domain/usecases/photos/params/query_parameters.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
class RetrieveAllAlbums extends UseCase<QueryParameters, List<Album>> { class RetrieveAllAlbums extends AsyncUseCase<QueryParameters, List<Album>> {
final PhotoRepository _photoRepository;
RetrieveAllAlbums(this._photoRepository); RetrieveAllAlbums(this._photoRepository);
final PhotoRepository _photoRepository;
@override @override
FutureResult<List<Album>> call(QueryParameters params) { FutureOrResult<List<Album>> execute(QueryParameters? params) {
final albums = _photoRepository.getAllAlbums( final albums = _photoRepository.getAllAlbums(
start: params.start, start: params!.start,
limit: params.limit, limit: params.limit,
); );
return albums; return albums;
} }
@override
FutureOr<void> onStart(QueryParameters? params) {
if (params == null) {
throw ClientException('params cannot be null');
}
}
} }
@@ -33,7 +33,6 @@ EventTransformer<E> throttleDroppable<E>(Duration duration) =>
(events, mapper) => droppable<E>().call(events.throttle(duration), mapper); (events, mapper) => droppable<E>().call(events.throttle(duration), mapper);
class AlbumBloc extends Bloc<AlbumEvent, AlbumState> { class AlbumBloc extends Bloc<AlbumEvent, AlbumState> {
final RetrieveAllAlbums _retrieveAllAlbums;
AlbumBloc(this._retrieveAllAlbums) : super(const AlbumState()) { AlbumBloc(this._retrieveAllAlbums) : super(const AlbumState()) {
on<AlbumFetched>( on<AlbumFetched>(
@@ -41,6 +40,7 @@ class AlbumBloc extends Bloc<AlbumEvent, AlbumState> {
transformer: throttleDroppable(throttleDuration), transformer: throttleDroppable(throttleDuration),
); );
} }
final RetrieveAllAlbums _retrieveAllAlbums;
Future<void> _onAlbumFetched( Future<void> _onAlbumFetched(
AlbumFetched event, AlbumFetched event,
@@ -26,19 +26,19 @@ abstract class PhotoDetailsState extends Equatable {
class PhotoDetailsInitial extends PhotoDetailsState {} class PhotoDetailsInitial extends PhotoDetailsState {}
class PhotoDetailsSuccess extends PhotoDetailsState { class PhotoDetailsSuccess extends PhotoDetailsState {
final Photo photo;
final bool isFavorite;
const PhotoDetailsSuccess(this.photo, {required this.isFavorite}); const PhotoDetailsSuccess(this.photo, {required this.isFavorite});
final Photo photo;
final bool isFavorite;
@override @override
List<Object> get props => [photo, isFavorite]; List<Object> get props => [photo, isFavorite];
} }
class PhotoDetailsFailure extends PhotoDetailsState { class PhotoDetailsFailure extends PhotoDetailsState {
final String error;
const PhotoDetailsFailure(this.error); const PhotoDetailsFailure(this.error);
final String error;
@override @override
List<Object> get props => [error]; List<Object> get props => [error];
@@ -23,10 +23,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
part 'favorite_checker_state.dart'; part 'favorite_checker_state.dart';
class FavoriteCheckerCubit extends Cubit<FavoriteCheckerState> { class FavoriteCheckerCubit extends Cubit<FavoriteCheckerState> {
final CheckIfPhotoIsInFavorites _checkIfPhotoIsInFavorites;
FavoriteCheckerCubit(this._checkIfPhotoIsInFavorites) FavoriteCheckerCubit(this._checkIfPhotoIsInFavorites)
: super(FavoriteCheckerInitial()); : super(FavoriteCheckerInitial());
final CheckIfPhotoIsInFavorites _checkIfPhotoIsInFavorites;
FutureOr<void> checkIfPhotoIsInFavorites(int photoId) async { FutureOr<void> checkIfPhotoIsInFavorites(int photoId) async {
final response = await _checkIfPhotoIsInFavorites.call(photoId); final response = await _checkIfPhotoIsInFavorites.call(photoId);
@@ -10,19 +10,19 @@ abstract class FavoriteCheckerState extends Equatable {
class FavoriteCheckerInitial extends FavoriteCheckerState {} class FavoriteCheckerInitial extends FavoriteCheckerState {}
class FavoriteCheckerSuccess extends FavoriteCheckerState { class FavoriteCheckerSuccess extends FavoriteCheckerState {
final int photoId;
final bool isFavorite;
const FavoriteCheckerSuccess(this.photoId, {required this.isFavorite}); const FavoriteCheckerSuccess(this.photoId, {required this.isFavorite});
final int photoId;
final bool isFavorite;
@override @override
List<Object> get props => [photoId, isFavorite]; List<Object> get props => [photoId, isFavorite];
} }
class FavoriteCheckerFailure extends FavoriteCheckerState { class FavoriteCheckerFailure extends FavoriteCheckerState {
final String error;
const FavoriteCheckerFailure(this.error); const FavoriteCheckerFailure(this.error);
final String error;
@override @override
List<Object> get props => [error]; List<Object> get props => [error];
@@ -33,7 +33,6 @@ EventTransformer<E> throttleDroppable<E>(Duration duration) =>
(events, mapper) => droppable<E>().call(events.throttle(duration), mapper); (events, mapper) => droppable<E>().call(events.throttle(duration), mapper);
class PhotoBloc extends Bloc<PhotoEvent, PhotoState> { class PhotoBloc extends Bloc<PhotoEvent, PhotoState> {
final OpenAlbum _openAlbum;
PhotoBloc(this._openAlbum) : super(const PhotoState()) { PhotoBloc(this._openAlbum) : super(const PhotoState()) {
on<PhotoFetched>( on<PhotoFetched>(
@@ -41,6 +40,7 @@ class PhotoBloc extends Bloc<PhotoEvent, PhotoState> {
transformer: throttleDroppable(throttleDuration), transformer: throttleDroppable(throttleDuration),
); );
} }
final OpenAlbum _openAlbum;
Future<void> _onPhotoFetched( Future<void> _onPhotoFetched(
PhotoFetched event, PhotoFetched event,
@@ -24,7 +24,7 @@ abstract class PhotoEvent extends Equatable {
} }
class PhotoFetched extends PhotoEvent { class PhotoFetched extends PhotoEvent {
final int albumId;
const PhotoFetched(this.albumId); const PhotoFetched(this.albumId);
final int albumId;
} }
@@ -36,7 +36,7 @@ dependencies:
wyatt_type_utils: wyatt_type_utils:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub
version: ^0.0.3+1 version: ^0.0.4
wyatt_http_client: wyatt_http_client:
git: git:
@@ -49,7 +49,7 @@ dependencies:
wyatt_bloc_helper: wyatt_bloc_helper:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: ^1.2.0 version: ^2.0.0
equatable: ^2.0.5 equatable: ^2.0.5
stream_transform: ^2.1.0 stream_transform: ^2.1.0
bloc_concurrency: ^0.2.0 bloc_concurrency: ^0.2.0
@@ -67,7 +67,7 @@ dev_dependencies:
wyatt_analysis: wyatt_analysis:
git: git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_analysis-v2.2.2 ref: wyatt_analysis-v2.3.0
path: packages/wyatt_analysis path: packages/wyatt_analysis
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
@@ -17,9 +17,9 @@
import 'package:wyatt_type_utils/wyatt_type_utils.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
abstract class AppException implements Exception { abstract class AppException implements Exception {
final String? message;
AppException([this.message]); AppException([this.message]);
final String? message;
@override @override
String toString() { String toString() {
@@ -0,0 +1,44 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
/// Usecase observers
mixin Observer<Parameters, ReturnType> {
/// Called before usecase is runned.
/// Usefull to check the preconditions
FutureOr<void> onStart(Parameters? params) {}
/// Called when error occures during main scenario
/// Usefull to run alternative scenario
FutureOr<void> onError(AppException? error) {}
}
/// Specific observer for classic usecase
mixin AsyncObserver<ReturnType> {
FutureOr<void> onComplete(ReturnType? data) {}
}
/// Specific observer for stream case usecase
mixin StreamObserver<ReturnType> {
/// Replaces the data event handler of this subscription.
void onDone() {}
/// Replaces the done event handler of this subscription.
void onData(ReturnType? data) {}
}
@@ -14,14 +14,67 @@
// 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 'dart:async';
import 'package:wyatt_architecture/src/core/exceptions/exceptions.dart'; import 'package:wyatt_architecture/src/core/exceptions/exceptions.dart';
import 'package:wyatt_architecture/src/domain/usecases/observers.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
typedef FutureResult<T> = Future<Result<T, AppException>>; typedef FutureOrResult<T> = FutureOr<Result<T, AppException>>;
typedef StreamResult<T> = Stream<Result<T, AppException>>; typedef StreamResult<T> = Stream<Result<T, AppException>>;
typedef Res<T> = Result<T, AppException>;
// ignore: one_member_abstracts /// Abstract class of a use case
abstract class UseCase<Parameters, ReturnType> { abstract class BaseUseCase<Parameters, ReturnType> {
FutureResult<ReturnType> call(Parameters params); /// Run use case scenarios
ReturnType call(Parameters parameters);
/// Private function to implement main scenario
/// of your usecase.
ReturnType execute(Parameters params);
}
/// Abstract class of a use case that deals specifically
/// with the response and its state.
abstract class UseCase<Parameters, ReturnType>
extends BaseUseCase<Parameters?, FutureOrResult<ReturnType>>
with Observer<Parameters, ReturnType> {
FutureOr<void> _onSuccess(ReturnType data);
/// Supports the result of the main scenario and integrates
/// some alternative scenarios if necessary.
@override
FutureOrResult<ReturnType> call(Parameters? parameters) async {
try {
await onStart(parameters);
final response = await execute(parameters);
if (response.isErr) {
await onError(response.err);
} else if (response.isOk && response.ok != null) {
await _onSuccess(response.ok as ReturnType);
}
return response;
} catch (e) {
return Err(ClientException(e.toString()));
}
}
}
/// Abtstract classic usecase.
abstract class AsyncUseCase<Parameters, ReturnType>
extends UseCase<Parameters?, ReturnType> with AsyncObserver<ReturnType> {
@override
FutureOr<void> _onSuccess(ReturnType data) => onComplete(data);
}
/// Abstract specific usecase bases on streams
abstract class StreamUseCase<Parameters, ReturnType>
extends UseCase<Parameters?, Stream<ReturnType>>
with StreamObserver<ReturnType> {
@override
FutureOr<void> _onSuccess(Stream<ReturnType> data) {
data.listen(
onData,
onDone: onDone,
);
}
} }
@@ -15,4 +15,5 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'no_param.dart'; export 'no_param.dart';
export 'observers.dart';
export 'usecase.dart'; export 'usecase.dart';
Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

@@ -0,0 +1,86 @@
@startuml
set namespaceSeparator ::
abstract class "wyatt_architecture::src::core::exceptions::exceptions.dart::AppException" {
+String? message
+String toString()
}
"dart::core::Exception" <|-- "wyatt_architecture::src::core::exceptions::exceptions.dart::AppException"
class "wyatt_architecture::src::core::exceptions::exceptions.dart::ClientException" {
}
"wyatt_architecture::src::core::exceptions::exceptions.dart::AppException" <|-- "wyatt_architecture::src::core::exceptions::exceptions.dart::ClientException"
class "wyatt_architecture::src::core::exceptions::exceptions.dart::ServerException" {
}
"wyatt_architecture::src::core::exceptions::exceptions.dart::AppException" <|-- "wyatt_architecture::src::core::exceptions::exceptions.dart::ServerException"
abstract class "wyatt_architecture::src::domain::repositories::base_repository.dart::BaseRepository" {
}
abstract class "wyatt_architecture::src::domain::data_sources::local::base_local_data_source.dart::BaseLocalDataSource" {
}
"wyatt_architecture::src::domain::data_sources::base_data_source.dart::BaseDataSource" <|-- "wyatt_architecture::src::domain::data_sources::local::base_local_data_source.dart::BaseLocalDataSource"
abstract class "wyatt_architecture::src::domain::data_sources::base_data_source.dart::BaseDataSource" {
}
abstract class "wyatt_architecture::src::domain::data_sources::remote::base_remote_data_source.dart::BaseRemoteDataSource" {
}
"wyatt_architecture::src::domain::data_sources::base_data_source.dart::BaseDataSource" <|-- "wyatt_architecture::src::domain::data_sources::remote::base_remote_data_source.dart::BaseRemoteDataSource"
class "wyatt_architecture::src::domain::usecases::no_param.dart::NoParam" {
}
"wyatt_architecture::src::domain::entities::entity.dart::Entity" <|-- "wyatt_architecture::src::domain::usecases::no_param.dart::NoParam"
abstract class "wyatt_architecture::src::domain::usecases::observers.dart::Observer" {
+FutureOr<void> onStart()
+FutureOr<void> onError()
}
abstract class "wyatt_architecture::src::domain::usecases::observers.dart::AsyncObserver" {
+FutureOr<void> onComplete()
}
abstract class "wyatt_architecture::src::domain::usecases::observers.dart::StreamObserver" {
+void onDone()
+void onData()
}
abstract class "wyatt_architecture::src::domain::usecases::usecase.dart::BaseUseCase" {
+ReturnType execute()
+ReturnType call()
}
abstract class "wyatt_architecture::src::domain::usecases::usecase.dart::UseCase" {
-FutureOr<void> _onSuccess()
+FutureOr<Result<ReturnType, AppException>> execute()
}
"wyatt_architecture::src::domain::usecases::usecase.dart::BaseUseCase" <|-- "wyatt_architecture::src::domain::usecases::usecase.dart::UseCase"
"wyatt_architecture::src::domain::usecases::observers.dart::Observer" <|-- "wyatt_architecture::src::domain::usecases::usecase.dart::UseCase"
abstract class "wyatt_architecture::src::domain::usecases::usecase.dart::AsyncUseCase" {
-FutureOr<void> _onSuccess()
}
"wyatt_architecture::src::domain::usecases::usecase.dart::UseCase" <|-- "wyatt_architecture::src::domain::usecases::usecase.dart::AsyncUseCase"
"wyatt_architecture::src::domain::usecases::observers.dart::AsyncObserver" <|-- "wyatt_architecture::src::domain::usecases::usecase.dart::AsyncUseCase"
abstract class "wyatt_architecture::src::domain::usecases::usecase.dart::StreamUseCase" {
-FutureOr<void> _onSuccess()
}
"wyatt_architecture::src::domain::usecases::usecase.dart::UseCase" <|-- "wyatt_architecture::src::domain::usecases::usecase.dart::StreamUseCase"
"wyatt_architecture::src::domain::usecases::observers.dart::StreamObserver" <|-- "wyatt_architecture::src::domain::usecases::usecase.dart::StreamUseCase"
abstract class "wyatt_architecture::src::domain::entities::entity.dart::Entity" {
}
@enduml
+3 -3
View File
@@ -1,7 +1,7 @@
name: wyatt_architecture name: wyatt_architecture
description: A new Wyatt package description: A new Wyatt package
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_architecture repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_architecture
version: 0.0.2 version: 0.1.0+1
publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub
@@ -15,7 +15,7 @@ dependencies:
wyatt_type_utils: wyatt_type_utils:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub
version: ^0.0.3+1 version: ^0.0.4
dev_dependencies: dev_dependencies:
@@ -24,4 +24,4 @@ dev_dependencies:
wyatt_analysis: wyatt_analysis:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub
version: ^2.2.2 version: ^2.3.0
@@ -1 +1,2 @@
firebase_options.dart firebase_options.dart
.vscode
@@ -1 +0,0 @@
../../.vscode/
@@ -0,0 +1,76 @@
{
// Dart
"dart.runPubGetOnPubspecChanges": "never",
// Gitea
"gitea.owner": "Wyatt-FOSS",
"gitea.repo": "wyatt-packages",
// Header
"psi-header.changes-tracking": {
"isActive": true
},
"psi-header.config": {
"blankLinesAfter": 1,
"forceToTop": true
},
"psi-header.lang-config": [
{
"beforeHeader": [
"# -*- coding:utf-8 -*-",
"#!/usr/bin/env python3"
],
"begin": "###",
"end": "###",
"language": "python",
"prefix": "# "
},
{
"beforeHeader": [
"#!/usr/bin/env sh",
""
],
"language": "shellscript",
"begin": "",
"end": "",
"prefix": "# "
},
{
"begin": "",
"end": "",
"language": "dart",
"prefix": "// "
},
{
"begin": "",
"end": "",
"language": "yaml",
"prefix": "# "
},
{
"begin": "<!--",
"end": "-->",
"language": "markdown",
},
],
"psi-header.templates": [
{
"language": "*",
"template": [
"Copyright (C) <<year>> WYATT GROUP",
"Please see the AUTHORS file for details.",
"",
"This program is free software: you can redistribute it and/or modify",
"it under the terms of the GNU General Public License as published by",
"the Free Software Foundation, either version 3 of the License, or",
"any later version.",
"",
"This program is distributed in the hope that it will be useful,",
"but WITHOUT ANY WARRANTY; without even the implied warranty of",
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
"GNU General Public License for more details.",
"",
"You should have received a copy of the GNU General Public License",
"along with this program. If not, see <https://www.gnu.org/licenses/>."
],
}
],
}
@@ -1,3 +1,26 @@
## 0.4.1
- **REFACTOR**: use fromFirebaseUser factory to parse user.
- **REFACTOR**: update deps and re-export them.
- **FIX**: fix mock on register.
- **FEAT**: add google sign in in signIn cubit.
- **FEAT**: add google sign in parameter in firebase data source constructor.
- **FEAT**: add google sign_in support (closes #59).
- **FEAT**: add reauthenticate, updateEmail and updatePassword.
- **DOCS**: add correct header.
## 0.4.0+3
- Update a dependency to the latest release.
## 0.4.0+2
- Update a dependency to the latest release.
## 0.4.0+1
- **FIX**: add sort of mutex on onAuthChanges callback trigger.
## 0.4.0 ## 0.4.0
> Note: This release has breaking changes. > Note: This release has breaking changes.
@@ -1,4 +1 @@
include: package:wyatt_analysis/analysis_options.flutter.yaml include: package:wyatt_analysis/analysis_options.flutter.yaml
analyzer:
exclude: "!example/**"
@@ -45,5 +45,3 @@ app.*.map.json
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
/android/app/release /android/app/release
firebase_options.dart
@@ -4,7 +4,7 @@
* ----- * -----
* File: launch.json * File: launch.json
* Created Date: 19/08/2022 15:12:25 * Created Date: 19/08/2022 15:12:25
* Last Modified: 19/08/2022 15:22:02 * Last Modified: Tue Dec 13 2022
* ----- * -----
* Copyright (c) 2022 * Copyright (c) 2022
*/ */
@@ -15,11 +15,18 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "example_router", "name": "Mock",
"request": "launch", "request": "launch",
"type": "dart", "type": "dart",
"program": "lib/main.dart", "program": "lib/main.dart",
"flutterMode": "debug" "flutterMode": "debug"
},
{
"name": "Firebase",
"request": "launch",
"type": "dart",
"program": "lib/main_firebase.dart",
"flutterMode": "debug"
} }
] ]
} }
@@ -5,41 +5,30 @@ PODS:
- AppAuth/Core (1.6.0) - AppAuth/Core (1.6.0)
- AppAuth/ExternalUserAgent (1.6.0): - AppAuth/ExternalUserAgent (1.6.0):
- AppAuth/Core - AppAuth/Core
- FBAEMKit (14.1.0): - Firebase/Auth (10.3.0):
- FBSDKCoreKit_Basics (= 14.1.0)
- FBSDKCoreKit (14.1.0):
- FBAEMKit (= 14.1.0)
- FBSDKCoreKit_Basics (= 14.1.0)
- FBSDKCoreKit_Basics (14.1.0)
- FBSDKLoginKit (14.1.0):
- FBSDKCoreKit (= 14.1.0)
- Firebase/Auth (10.0.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseAuth (~> 10.0.0) - FirebaseAuth (~> 10.3.0)
- Firebase/CoreOnly (10.0.0): - Firebase/CoreOnly (10.3.0):
- FirebaseCore (= 10.0.0) - FirebaseCore (= 10.3.0)
- firebase_auth (4.1.1): - firebase_auth (4.2.0):
- Firebase/Auth (= 10.0.0) - Firebase/Auth (= 10.3.0)
- firebase_core - firebase_core
- Flutter - Flutter
- firebase_core (2.1.1): - firebase_core (2.4.0):
- Firebase/CoreOnly (= 10.0.0) - Firebase/CoreOnly (= 10.3.0)
- Flutter - Flutter
- FirebaseAuth (10.0.0): - FirebaseAuth (10.3.0):
- FirebaseCore (~> 10.0) - FirebaseCore (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.8) - GoogleUtilities/AppDelegateSwizzler (~> 7.8)
- GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/Environment (~> 7.8)
- GTMSessionFetcher/Core (~> 2.1) - GTMSessionFetcher/Core (< 4.0, >= 2.1)
- FirebaseCore (10.0.0): - FirebaseCore (10.3.0):
- FirebaseCoreInternal (~> 10.0) - FirebaseCoreInternal (~> 10.0)
- GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/Environment (~> 7.8)
- GoogleUtilities/Logger (~> 7.8) - GoogleUtilities/Logger (~> 7.8)
- FirebaseCoreInternal (10.1.0): - FirebaseCoreInternal (10.3.0):
- "GoogleUtilities/NSData+zlib (~> 7.8)" - "GoogleUtilities/NSData+zlib (~> 7.8)"
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_facebook_auth (4.4.1):
- FBSDKLoginKit (= 14.1.0)
- Flutter
- google_sign_in_ios (0.0.1): - google_sign_in_ios (0.0.1):
- Flutter - Flutter
- GoogleSignIn (~> 6.2) - GoogleSignIn (~> 6.2)
@@ -47,47 +36,36 @@ PODS:
- AppAuth (~> 1.5) - AppAuth (~> 1.5)
- GTMAppAuth (~> 1.3) - GTMAppAuth (~> 1.3)
- GTMSessionFetcher/Core (< 3.0, >= 1.1) - GTMSessionFetcher/Core (< 3.0, >= 1.1)
- GoogleUtilities/AppDelegateSwizzler (7.8.0): - GoogleUtilities/AppDelegateSwizzler (7.10.0):
- GoogleUtilities/Environment - GoogleUtilities/Environment
- GoogleUtilities/Logger - GoogleUtilities/Logger
- GoogleUtilities/Network - GoogleUtilities/Network
- GoogleUtilities/Environment (7.8.0): - GoogleUtilities/Environment (7.10.0):
- PromisesObjC (< 3.0, >= 1.2) - PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/Logger (7.8.0): - GoogleUtilities/Logger (7.10.0):
- GoogleUtilities/Environment - GoogleUtilities/Environment
- GoogleUtilities/Network (7.8.0): - GoogleUtilities/Network (7.10.0):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib" - "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability - GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (7.8.0)" - "GoogleUtilities/NSData+zlib (7.10.0)"
- GoogleUtilities/Reachability (7.8.0): - GoogleUtilities/Reachability (7.10.0):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- GTMAppAuth (1.3.1): - GTMAppAuth (1.3.1):
- AppAuth/Core (~> 1.6) - AppAuth/Core (~> 1.6)
- GTMSessionFetcher/Core (< 3.0, >= 1.5) - GTMSessionFetcher/Core (< 3.0, >= 1.5)
- GTMSessionFetcher/Core (2.1.0) - GTMSessionFetcher/Core (2.3.0)
- PromisesObjC (2.1.1) - PromisesObjC (2.1.1)
- sign_in_with_apple (0.0.1):
- Flutter
- twitter_login (0.0.1):
- Flutter
DEPENDENCIES: DEPENDENCIES:
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_facebook_auth (from `.symlinks/plugins/flutter_facebook_auth/ios`)
- google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/ios`) - google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/ios`)
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
- twitter_login (from `.symlinks/plugins/twitter_login/ios`)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- AppAuth - AppAuth
- FBAEMKit
- FBSDKCoreKit
- FBSDKCoreKit_Basics
- FBSDKLoginKit
- Firebase - Firebase
- FirebaseAuth - FirebaseAuth
- FirebaseCore - FirebaseCore
@@ -105,37 +83,24 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/firebase_core/ios" :path: ".symlinks/plugins/firebase_core/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_facebook_auth:
:path: ".symlinks/plugins/flutter_facebook_auth/ios"
google_sign_in_ios: google_sign_in_ios:
:path: ".symlinks/plugins/google_sign_in_ios/ios" :path: ".symlinks/plugins/google_sign_in_ios/ios"
sign_in_with_apple:
:path: ".symlinks/plugins/sign_in_with_apple/ios"
twitter_login:
:path: ".symlinks/plugins/twitter_login/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
AppAuth: 8fca6b5563a5baef2c04bee27538025e4ceb2add AppAuth: 8fca6b5563a5baef2c04bee27538025e4ceb2add
FBAEMKit: a899515e45476027f73aef377b5cffadcd56ca3a Firebase: f92fc551ead69c94168d36c2b26188263860acd9
FBSDKCoreKit: 24f8bc8d3b5b2a8c5c656a1329492a12e8efa792 firebase_auth: 579a0dc15451491cc83fccaa5102296635f24938
FBSDKCoreKit_Basics: 6e578c9bdc7aa1365dbbbde633c9ebb536bcaa98 firebase_core: 6f2f753e316765799d88568232ed59e300ff53db
FBSDKLoginKit: 787de205d524c3a4b17d527916f1d066e4361660 FirebaseAuth: 0e415d29d846c1dce2fb641e46f35e9888d9bec6
Firebase: 1b810f3d0c0532e27a48f1961f8c0400a668a2cf FirebaseCore: 988754646ab3bd4bdcb740f1bfe26b9f6c0d5f2a
firebase_auth: dd33e93fce72a1c72040f7380dacf06e89db5705 FirebaseCoreInternal: 29b76f784d607df8b2a1259d73c3f04f1210137b
firebase_core: 5c0bb0ca7d0e70480a68a6e9ad9bf55d1edd5305
FirebaseAuth: 493382cf533cc45e2862b00e9aa4cfe4c98daf71
FirebaseCore: 97f48a3a567a72b8d4daa0f03c3aadb78df4e995
FirebaseCoreInternal: 96d75228e10fd369564da51bd898414eb0f54df5
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_facebook_auth: 361ac7a57263ebf327f26089507ead0d66558ee8
google_sign_in_ios: 4f85eb9f937450765c8573bb85fd8cd6a5af675c google_sign_in_ios: 4f85eb9f937450765c8573bb85fd8cd6a5af675c
GoogleSignIn: 5651ce3a61e56ca864160e79b484cd9ed3f49b7a GoogleSignIn: 5651ce3a61e56ca864160e79b484cd9ed3f49b7a
GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7 GoogleUtilities: bad72cb363809015b1f7f19beb1f1cd23c589f95
GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd
GTMSessionFetcher: ffbb25ec00ebcb5201adab0a56d808f6f1902d9f GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2
PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb
sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440
twitter_login: 2794db69b7640681171b17b3c2c84ad9dfb4a57f
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
@@ -28,6 +28,17 @@
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>
<string>Main</string> <string>Main</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.405351917235-2jv4ff02kovoim58f8d6d0rsa14apgkj</string>
</array>
</dict>
</array>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
@@ -1,7 +1,7 @@
{ {
"file_generated_by": "FlutterFire CLI", "file_generated_by": "FlutterFire CLI",
"purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory",
"GOOGLE_APP_ID": "1:136771801992:ios:bcdca68d2b7d227097203d", "GOOGLE_APP_ID": "1:405351917235:ios:869f0ad8ace08db899f2c6",
"FIREBASE_PROJECT_ID": "tchat-beta", "FIREBASE_PROJECT_ID": "meerabel-dev",
"GCM_SENDER_ID": "136771801992" "GCM_SENDER_ID": "405351917235"
} }
@@ -1,19 +1,64 @@
// Author: Hugo Pointcheval // Copyright (C) 2022 WYATT GROUP
// Email: git@pcl.ovh // Please see the AUTHORS file for details.
// ----- //
// File: bootstrap.dart // This program is free software: you can redistribute it and/or modify
// Created Date: 19/08/2022 15:05:17 // it under the terms of the GNU General Public License as published by
// Last Modified: Fri Nov 11 2022 // the Free Software Foundation, either version 3 of the License, or
// ----- // any later version.
// Copyright (c) 2022 //
// 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: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';
class MockSettings {
static MockSettings? _instance;
/// Data source mode
late bool enable = false;
MockSettings._(this.enable);
factory MockSettings.enable() {
_instance ??= MockSettings._(true);
if (_instance!.enable != true) {
throw Exception('Mock already initialized in: ${_instance!.enable}');
}
return _instance!;
}
factory MockSettings.disable() {
_instance ??= MockSettings._(false);
if (_instance!.enable != false) {
throw Exception('Mock already initialized in: ${_instance!.enable}');
}
return _instance!;
}
static bool isEnable() {
if (_instance == null) {
throw Exception('MockSettings not initialized!');
}
return _instance!.enable == true;
}
static bool isDisable() {
return !isEnable();
}
}
Future<void> bootstrap(FutureOr<Widget> Function() builder) async { Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
await runZonedGuarded( await runZonedGuarded(
() async { () async {
@@ -23,6 +68,12 @@ Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
FlutterError.onError = (details) { FlutterError.onError = (details) {
debugPrint(details.toString()); debugPrint(details.toString());
}; };
if (MockSettings.isDisable()) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
}
await GetItInitializer.init(); await GetItInitializer.init();
runApp(await builder()); runApp(await builder());
@@ -1,12 +1,20 @@
// Author: Hugo Pointcheval // Copyright (C) 2022 WYATT GROUP
// Email: git@pcl.ovh // Please see the AUTHORS file for details.
// ----- //
// File: form_field.dart // This program is free software: you can redistribute it and/or modify
// Created Date: 19/08/2022 11:52:33 // it under the terms of the GNU General Public License as published by
// Last Modified: 19/08/2022 16:35:39 // the Free Software Foundation, either version 3 of the License, or
// ----- // any later version.
// Copyright (c) 2022 //
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
abstract class AppFormField { abstract class AppFormField {
static const oldPassword = 'oldPassword';
static const confirmedPassword = 'confirmedPassword'; static const confirmedPassword = 'confirmedPassword';
} }
@@ -0,0 +1,19 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
abstract class AppFormName {
static const editProfile = 'editProfile';
}
@@ -14,6 +14,8 @@
// 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:example_router/bootstrap.dart';
import 'package:example_router/firebase_options.dart';
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'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
@@ -24,28 +26,32 @@ abstract class GetItInitializer {
static Future<void> init() async { static Future<void> init() async {
getIt getIt
..registerLazySingleton<AuthenticationRemoteDataSource>( ..registerLazySingleton<AuthenticationRemoteDataSource>(
() => AuthenticationMockDataSourceImpl(registeredAccounts: [ MockSettings.isEnable()
Pair( ? () => AuthenticationMockDataSourceImpl(registeredAccounts: [
AccountModel( Pair(
uid: '1', AccountModel(
emailVerified: true, uid: '1',
isAnonymous: false, emailVerified: true,
providerId: 'wyatt', isAnonymous: false,
email: 'toto@test.fr', providerId: 'wyatt',
), email: 'toto@test.fr',
'toto1234', ),
), 'toto1234',
Pair( ),
AccountModel( Pair(
uid: '2', AccountModel(
emailVerified: false, uid: '2',
isAnonymous: false, emailVerified: false,
providerId: 'wyatt', isAnonymous: false,
email: 'tata@test.fr', providerId: 'wyatt',
), email: 'tata@test.fr',
'tata1234', ),
), 'tata1234',
]), ),
])
: () => AuthenticationFirebaseDataSourceImpl(
googleSignIn: GoogleSignIn(
clientId: DefaultFirebaseOptions.ios.iosClientId)),
) )
..registerLazySingleton<AuthenticationCacheDataSource<int>>( ..registerLazySingleton<AuthenticationCacheDataSource<int>>(
() => AuthenticationCacheDataSourceImpl<int>(), () => AuthenticationCacheDataSourceImpl<int>(),
@@ -1,12 +1,20 @@
// Author: Hugo Pointcheval // Copyright (C) 2022 WYATT GROUP
// Email: git@pcl.ovh // Please see the AUTHORS file for details.
// ----- //
// File: router.dart // This program is free software: you can redistribute it and/or modify
// Created Date: 19/08/2022 11:52:22 // it under the terms of the GNU General Public License as published by
// Last Modified: 19/08/2022 16:39:07 // the Free Software Foundation, either version 3 of the License, or
// ----- // any later version.
// Copyright (c) 2022 //
// 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:example_router/presentation/features/edit_profile/edit_profile_page.dart';
import 'package:example_router/presentation/features/home/home_page.dart'; import 'package:example_router/presentation/features/home/home_page.dart';
import 'package:example_router/presentation/features/sign_in/sign_in_page.dart'; import 'package:example_router/presentation/features/sign_in/sign_in_page.dart';
import 'package:example_router/presentation/features/sign_up/sign_up_page.dart'; import 'package:example_router/presentation/features/sign_up/sign_up_page.dart';
@@ -75,5 +83,14 @@ class AppRouter {
const SubPage(), const SubPage(),
), ),
), ),
GoRoute(
path: '/home/edit',
name: EditProfilePage.pageName,
pageBuilder: (context, state) => defaultTransition(
context,
state,
const EditProfilePage(),
),
),
]; ];
} }
@@ -1,11 +1,18 @@
// Author: Hugo Pointcheval // Copyright (C) 2022 WYATT GROUP
// Email: git@pcl.ovh // Please see the AUTHORS file for details.
// ----- //
// File: app_bloc_observer.dart // This program is free software: you can redistribute it and/or modify
// Created Date: 19/08/2022 12:02:23 // it under the terms of the GNU General Public License as published by
// Last Modified: 19/08/2022 12:02:45 // the Free Software Foundation, either version 3 of the License, or
// ----- // any later version.
// Copyright (c) 2022 //
// 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/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@@ -0,0 +1,30 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class CustomPassword extends RegexValidator<ValidationStandardError> {
const CustomPassword.pure([super.value]) : super.pure();
const CustomPassword.dirty([super.value]) : super.dirty();
@override
ValidationStandardError get onEmpty => ValidationStandardError.empty;
@override
ValidationStandardError get onError => ValidationStandardError.invalid;
@override
RegExp get regex => RegExp(r'^(?=.*[A-Za-z\w\s])(?=.*\d).{6,}$');
}
@@ -14,33 +14,19 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
part of prompt.framework; import 'package:example_router/core/constants/form_field.dart';
import 'package:example_router/core/constants/form_name.dart';
import 'package:example_router/core/utils/custom_password.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
abstract class Widget<T extends dynamic> { abstract class AppForms {
int _initLineCount = 0; static WyattForm getEditProfileForm() => WyattFormImpl(
[
/// Creates the mutable state for this widget FormInput(AuthFormField.email, const Email.pure()),
@protected FormInput(AuthFormField.password, const CustomPassword.pure()),
@factory FormInput(AppFormField.oldPassword, const CustomPassword.pure()),
State createState(); ],
name: AppFormName.editProfile,
State pipeState(State state) => state; );
T prompt() {
final state = pipeState(createState())
.._widget = this
..initState();
_initLineCount = state.context.lineCount;
state.context.resetLineCount();
state.build();
state.context.incrementBuildCount();
final dynamic out = state.prompt();
state.context.wipe();
state.context.erasePreviousLine(_initLineCount);
state.dispose();
return out as T;
}
} }
@@ -0,0 +1,67 @@
// 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: 'AIzaSyDDmtf0KN7Xw12_pqUsxoBfAxMuvCMmMmk',
appId: '1:405351917235:ios:869f0ad8ace08db899f2c6',
messagingSenderId: '405351917235',
projectId: 'meerabel-dev',
storageBucket: 'meerabel-dev.appspot.com',
androidClientId:
'405351917235-4g1dh3475tq6t1sa2qoh7ol60nf4ta05.apps.googleusercontent.com',
iosClientId:
'405351917235-2jv4ff02kovoim58f8d6d0rsa14apgkj.apps.googleusercontent.com',
iosBundleId: 'com.example.exampleRouter',
);
}
@@ -1,6 +1,23 @@
// 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:example_router/bootstrap.dart'; import 'package:example_router/bootstrap.dart';
import 'package:example_router/presentation/features/app/app.dart'; import 'package:example_router/presentation/features/app/app.dart';
void main() { void main() {
MockSettings.enable();
bootstrap(App.new); bootstrap(App.new);
} }
@@ -14,6 +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 'dart:io'; import 'package:example_router/bootstrap.dart';
import 'package:example_router/presentation/features/app/app.dart';
bool get supportsAnsiColor => stdout.supportsAnsiEscapes; void main() {
MockSettings.disable();
bootstrap(App.new);
}
@@ -1,11 +1,18 @@
// Author: Hugo Pointcheval // Copyright (C) 2022 WYATT GROUP
// Email: git@pcl.ovh // Please see the AUTHORS file for details.
// ----- //
// File: app.dart // This program is free software: you can redistribute it and/or modify
// Created Date: 19/08/2022 12:05:38 // it under the terms of the GNU General Public License as published by
// Last Modified: Thu Nov 10 2022 // the Free Software Foundation, either version 3 of the License, or
// ----- // any later version.
// Copyright (c) 2022 //
// 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:async';
import 'dart:math'; import 'dart:math';
@@ -13,6 +20,8 @@ import 'dart:math';
import 'package:example_router/core/constants/form_field.dart'; import 'package:example_router/core/constants/form_field.dart';
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/routes/router.dart'; import 'package:example_router/core/routes/router.dart';
import 'package:example_router/core/utils/custom_password.dart';
import 'package:example_router/core/utils/forms.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -21,7 +30,8 @@ import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
FutureResult<int?> onSignUpSuccess( FutureOrResult<int?> onSignUpSuccess(
AuthenticationRepository<int> repo,
Account? account, Account? account,
WyattForm form, WyattForm form,
) async { ) async {
@@ -34,9 +44,15 @@ FutureResult<int?> onSignUpSuccess(
return const Ok<int, AppException>(id); return const Ok<int, AppException>(id);
} }
FutureResult<int?> onAccountChanges(Account? account) async { FutureOrResult<int?> onAccountChanges(
AuthenticationRepository<int> repo,
Account? account,
) async {
final id = Random().nextInt(1000); final id = Random().nextInt(1000);
debugPrint('onAccountChanges: $account, generatedId: $id'); final token =
await repo.getIdentityToken().fold((value) => value, (error) => 'null');
debugPrint('onAccountChanges: $account, token: $token, generatedId: $id');
return Ok<int, AppException>(id); return Ok<int, AppException>(id);
} }
@@ -47,6 +63,7 @@ class App extends StatelessWidget {
authenticationRemoteDataSource: getIt<AuthenticationRemoteDataSource>(), authenticationRemoteDataSource: getIt<AuthenticationRemoteDataSource>(),
onSignUpSuccess: onSignUpSuccess, onSignUpSuccess: onSignUpSuccess,
onAuthChange: onAccountChanges, onAuthChange: onAccountChanges,
customPasswordValidator: const CustomPassword.pure(),
extraSignUpInputs: [ extraSignUpInputs: [
FormInput( FormInput(
AppFormField.confirmedPassword, AppFormField.confirmedPassword,
@@ -60,7 +77,7 @@ class App extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
AuthenticationState? previous; AuthenticationState<int>? previous;
final AuthenticationCubit<int> authenticationCubit = final AuthenticationCubit<int> authenticationCubit =
AuthenticationCubit(authenticationRepository: authenticationRepository); AuthenticationCubit(authenticationRepository: authenticationRepository);
@@ -76,29 +93,18 @@ class App extends StatelessWidget {
redirect: (context, state) { redirect: (context, state) {
final authState = authenticationCubit.state; final authState = authenticationCubit.state;
if (authState != previous) { if (authState.status != previous?.status) {
previous = authState; previous = authState;
// Check if current user is logged in
final loggedIn =
authState.status == AuthenticationStatus.authenticated;
// Checking if current path is onboarding or not // Checking if current path is onboarding or not
final isOnboarding = AppRouter.publicRoutes.contains(state.subloc); final isOnboarding = AppRouter.publicRoutes.contains(state.subloc);
if (authState.status == AuthenticationStatus.unauthenticated) {
if (!loggedIn) {
debugPrint('Not logged'); debugPrint('Not logged');
if (isOnboarding) { if (!isOnboarding) {
return null;
} else {
return '/'; return '/';
} }
} else { } else if (authState.status == AuthenticationStatus.authenticated) {
debugPrint('Logged'); debugPrint('Logged');
if (isOnboarding) { return '/home';
return '/home';
} else {
return null;
}
} }
} }
return null; return null;
@@ -107,9 +113,13 @@ class App extends StatelessWidget {
return MultiRepositoryProvider( return MultiRepositoryProvider(
providers: [ providers: [
RepositoryProvider<AuthenticationRepository>.value( RepositoryProvider<AuthenticationRepository<int>>.value(
value: authenticationRepository, value: authenticationRepository,
), ),
RepositoryProvider<FormRepository>(
create: (context) =>
FormRepositoryImpl()..registerForm(AppForms.getEditProfileForm()),
),
], ],
child: MultiBlocProvider( child: MultiBlocProvider(
providers: [ providers: [
@@ -0,0 +1,74 @@
// 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:example_router/core/constants/form_field.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class EditProfileCubit extends FormDataCubitImpl {
final AuthenticationRepository<int> authenticationRepository;
EditProfileCubit(
super._formRepository,
super._formName, {
required this.authenticationRepository,
}) : super();
@override
Future<void> submit() async {
emit(
state.copyWith(
status: FormStatus.submissionInProgress,
),
);
final user = (await authenticationRepository.getAccount()).ok;
final form = state.form;
final email = form.valueOf<String?>(AuthFormField.email);
final oldPassword = form.valueOf<String?>(AppFormField.oldPassword);
final newPassword = form.valueOf<String?>(AuthFormField.password);
if (email.isNullOrEmpty ||
oldPassword.isNullOrEmpty ||
newPassword.isNullOrEmpty) {
emit(
state.copyWith(
errorMessage: 'An error occured while retrieving data from the form.',
status: FormStatus.submissionFailure,
),
);
}
try {
// await authenticationRepository.signInWithEmailAndPassword(
// email: user?.email ?? '',
// password: oldPassword ?? '',
// );
// await authenticationRepository.reauthenticateWithCredential();
await authenticationRepository.updateEmail(email: email!);
await authenticationRepository.updatePassword(password: newPassword!);
} on Exception catch (e) {
emit(
state.copyWith(
status: FormStatus.submissionFailure,
errorMessage: e.toString(),
),
);
}
emit(state.copyWith(status: FormStatus.submissionSuccess));
}
}
@@ -0,0 +1,61 @@
// 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:example_router/core/constants/form_name.dart';
import 'package:example_router/presentation/features/edit_profile/blocs/edit_profile_cubit.dart';
import 'package:example_router/presentation/features/edit_profile/widgets/edit_profile_form.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 EditProfilePage extends StatelessWidget {
const EditProfilePage({Key? key}) : super(key: key);
static String pageName = 'EditProfile';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Edit Profile')),
body: Padding(
padding: const EdgeInsets.all(8),
child: SingleChildScrollView(
child: BlocProvider<EditProfileCubit>(
create: (context) => EditProfileCubit(
context.read<FormRepository>(),
AppFormName.editProfile,
authenticationRepository:
context.read<AuthenticationRepository<int>>(),
)..dataChanged<String?>(
AuthFormField.email,
Email.dirty(
context
.read<AuthenticationCubit<int>>()
.state
.accountWrapper
?.account
?.email ??
'',
),
),
child: const EditProfileForm(),
),
),
),
);
}
}
@@ -0,0 +1,136 @@
// 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:example_router/core/constants/form_field.dart';
import 'package:example_router/core/utils/custom_password.dart';
import 'package:example_router/presentation/features/edit_profile/blocs/edit_profile_cubit.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 InputBuilderTextController<EditProfileCubit, String?, void>(
field: AuthFormField.email,
builder: ((context, cubit, state, field, input, controller, extra) {
return TextField(
onChanged: (email) => cubit.dataChanged<String?>(
AuthFormField.email, Email.dirty(email)),
keyboardType: TextInputType.emailAddress,
controller: controller,
decoration: InputDecoration(
labelText: 'Email',
helperText: '',
errorText: input.validator.invalid ? 'Invalid email' : null,
),
);
}),
);
}
}
class _OldPasswordInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return InputBuilder<EditProfileCubit>(
field: AppFormField.oldPassword,
builder: ((context, cubit, state, field, input) {
return TextField(
onChanged: (pwd) => cubit.dataChanged<String?>(
AppFormField.oldPassword, CustomPassword.dirty(pwd)),
obscureText: true,
decoration: InputDecoration(
labelText: 'Old Password',
helperText: '',
errorText: input.validator.invalid ? 'Invalid password' : null,
),
);
}),
);
}
}
class _NewPasswordInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return InputBuilder<EditProfileCubit>(
field: AuthFormField.password,
builder: ((context, cubit, state, field, input) {
return TextField(
onChanged: (pwd) => cubit.dataChanged<String?>(
AuthFormField.password, CustomPassword.dirty(pwd)),
obscureText: true,
decoration: InputDecoration(
labelText: 'New Password',
helperText: '',
errorText: input.validator.invalid ? 'Invalid password' : null,
),
);
}),
);
}
}
class _EditButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SubmitBuilder<EditProfileCubit>(
builder: ((context, cubit, status) {
return status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: status.isValidated ? () => cubit.submit() : null,
child: const Text('Edit profile'),
);
}),
);
}
}
class EditProfileForm extends StatelessWidget {
const EditProfileForm({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocListener<EditProfileCubit, FormDataState>(
listener: (context, state) {
if (state.status == FormStatus.submissionFailure) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'Edit Failure')),
);
}
},
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_EmailInput(),
const SizedBox(height: 8),
_OldPasswordInput(),
const SizedBox(height: 8),
_NewPasswordInput(),
const SizedBox(height: 16),
_EditButton(),
],
),
),
);
}
}
@@ -1,12 +1,20 @@
// Author: Hugo Pointcheval // Copyright (C) 2022 WYATT GROUP
// Email: git@pcl.ovh // Please see the AUTHORS file for details.
// ----- //
// File: home_page.dart // This program is free software: you can redistribute it and/or modify
// Created Date: 19/08/2022 14:38:24 // it under the terms of the GNU General Public License as published by
// Last Modified: Wed Nov 09 2022 // the Free Software Foundation, either version 3 of the License, or
// ----- // any later version.
// Copyright (c) 2022 //
// 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:example_router/presentation/features/edit_profile/edit_profile_page.dart';
import 'package:example_router/presentation/features/sub/sub_page.dart'; import 'package:example_router/presentation/features/sub/sub_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@@ -25,7 +33,8 @@ class HomePage extends StatelessWidget {
title: const Text('Home'), title: const Text('Home'),
actions: [ actions: [
IconButton( IconButton(
onPressed: () => context.read<AuthenticationCubit<int>>().signOut(), onPressed: () =>
context.read<AuthenticationCubit<int>>().signOut(),
icon: const Icon(Icons.logout_rounded)) icon: const Icon(Icons.logout_rounded))
], ],
), ),
@@ -35,8 +44,8 @@ class HomePage extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
AuthenticationBuilder<int>( AuthenticationBuilder<int>(
authenticated: (context, accountWrapper) => authenticated: (context, accountWrapper) => Text(
Text('Logged as ${accountWrapper.account?.email} | GeneratedId is ${accountWrapper.data}'), 'Logged as ${accountWrapper.account?.email} | GeneratedId is ${accountWrapper.data}'),
unauthenticated: (context) => unauthenticated: (context) =>
const Text('Not logged (unauthenticated)'), const Text('Not logged (unauthenticated)'),
unknown: (context) => const Text('Not logged (unknown)'), unknown: (context) => const Text('Not logged (unknown)'),
@@ -45,8 +54,13 @@ class HomePage extends StatelessWidget {
height: 8, height: 8,
), ),
ElevatedButton( ElevatedButton(
onPressed: () => context.pushNamed(SubPage.pageName), onPressed: () => context.pushNamed(SubPage.pageName),
child: const Text('Go to sub page')), child: const Text('Go to sub page'),
),
ElevatedButton(
onPressed: () => context.pushNamed(EditProfilePage.pageName),
child: const Text('Go to edit profile page'),
),
], ],
), ),
), ),
@@ -1,11 +1,18 @@
// Author: Hugo Pointcheval // Copyright (C) 2022 WYATT GROUP
// Email: git@pcl.ovh // Please see the AUTHORS file for details.
// ----- //
// File: sign_in_page.dart // This program is free software: you can redistribute it and/or modify
// Created Date: 19/08/2022 12:41:42 // it under the terms of the GNU General Public License as published by
// Last Modified: 19/08/2022 15:26:36 // the Free Software Foundation, either version 3 of the License, or
// ----- // any later version.
// Copyright (c) 2022 //
// 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:example_router/presentation/features/sign_in/widgets/sign_in_form.dart'; import 'package:example_router/presentation/features/sign_in/widgets/sign_in_form.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -1,12 +1,20 @@
// Author: Hugo Pointcheval // Copyright (C) 2022 WYATT GROUP
// Email: git@pcl.ovh // Please see the AUTHORS file for details.
// ----- //
// File: sign_in_form.dart // This program is free software: you can redistribute it and/or modify
// Created Date: 19/08/2022 15:24:37 // it under the terms of the GNU General Public License as published by
// Last Modified: Wed Nov 16 2022 // the Free Software Foundation, either version 3 of the License, or
// ----- // any later version.
// Copyright (c) 2022 //
// 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:example_router/core/utils/custom_password.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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';
@@ -38,7 +46,8 @@ class _PasswordInput extends StatelessWidget {
field: AuthFormField.password, field: AuthFormField.password,
builder: ((context, cubit, state, field, input) { builder: ((context, cubit, state, field, input) {
return TextField( return TextField(
onChanged: (pwd) => cubit.passwordChanged(pwd), onChanged: (pwd) => cubit
.passwordCustomChanged<CustomPassword>(CustomPassword.dirty(pwd)),
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Password', labelText: 'Password',
@@ -59,7 +68,9 @@ class _SignInButton extends StatelessWidget {
return status.isSubmissionInProgress return status.isSubmissionInProgress
? const CircularProgressIndicator() ? const CircularProgressIndicator()
: ElevatedButton( : ElevatedButton(
onPressed: status.isValidated ? () => cubit.signInWithEmailAndPassword() : null, onPressed: status.isValidated
? () => cubit.signInWithEmailAndPassword()
: null,
child: const Text('Sign in with credentials'), child: const Text('Sign in with credentials'),
); );
}), }),
@@ -83,6 +94,22 @@ class _SignInAnonymouslyButton extends StatelessWidget {
} }
} }
class _SignInWithGoogleButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SubmitBuilder<SignInCubit<int>>(
builder: ((context, cubit, status) {
return status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () => cubit.signInWithGoogle(),
child: const Text('Sign in Google'),
);
}),
);
}
}
class SignInForm extends StatelessWidget { class SignInForm extends StatelessWidget {
const SignInForm({Key? key}) : super(key: key); const SignInForm({Key? key}) : super(key: key);
@@ -105,6 +132,8 @@ class SignInForm extends StatelessWidget {
_SignInButton(), _SignInButton(),
const SizedBox(height: 16), const SizedBox(height: 16),
_SignInAnonymouslyButton(), _SignInAnonymouslyButton(),
const SizedBox(height: 16),
_SignInWithGoogleButton(),
], ],
), ),
), ),
@@ -1,11 +1,18 @@
// Author: Hugo Pointcheval // Copyright (C) 2022 WYATT GROUP
// Email: git@pcl.ovh // Please see the AUTHORS file for details.
// ----- //
// File: sign_up_page.dart // This program is free software: you can redistribute it and/or modify
// Created Date: 19/08/2022 12:41:27 // it under the terms of the GNU General Public License as published by
// Last Modified: 19/08/2022 14:58:51 // the Free Software Foundation, either version 3 of the License, or
// ----- // any later version.
// Copyright (c) 2022 //
// 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:example_router/presentation/features/sign_up/widgets/sign_up_form.dart'; import 'package:example_router/presentation/features/sign_up/widgets/sign_up_form.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -1,13 +1,21 @@
// Author: Hugo Pointcheval // Copyright (C) 2022 WYATT GROUP
// Email: git@pcl.ovh // Please see the AUTHORS file for details.
// ----- //
// File: sign_up_form.dart // This program is free software: you can redistribute it and/or modify
// Created Date: 19/08/2022 14:41:08 // it under the terms of the GNU General Public License as published by
// Last Modified: Wed Nov 16 2022 // the Free Software Foundation, either version 3 of the License, or
// ----- // any later version.
// Copyright (c) 2022 //
// 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:example_router/core/constants/form_field.dart'; import 'package:example_router/core/constants/form_field.dart';
import 'package:example_router/core/utils/custom_password.dart';
import 'package:flutter/material.dart' hide FormField; import 'package:flutter/material.dart' hide FormField;
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';
@@ -40,7 +48,8 @@ class _PasswordInput extends StatelessWidget {
builder: ((context, cubit, state, field, input) { builder: ((context, cubit, state, field, input) {
return TextField( return TextField(
onChanged: (pwd) { onChanged: (pwd) {
cubit.passwordChanged(pwd); cubit.passwordCustomChanged<CustomPassword>(
CustomPassword.dirty(pwd));
cubit.dataChanged( cubit.dataChanged(
AppFormField.confirmedPassword, AppFormField.confirmedPassword,
ConfirmedPassword.dirty( ConfirmedPassword.dirty(
@@ -1,11 +1,18 @@
// Author: Hugo Pointcheval // Copyright (C) 2022 WYATT GROUP
// Email: git@pcl.ovh // Please see the AUTHORS file for details.
// ----- //
// File: sub_page.dart // This program is free software: you can redistribute it and/or modify
// Created Date: 19/08/2022 16:10:05 // it under the terms of the GNU General Public License as published by
// Last Modified: Wed Nov 09 2022 // the Free Software Foundation, either version 3 of the License, or
// ----- // any later version.
// Copyright (c) 2022 //
// 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/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@@ -23,8 +30,13 @@ class SubPage extends StatelessWidget {
title: const Text('Sub'), title: const Text('Sub'),
actions: [ actions: [
IconButton( IconButton(
onPressed: () => context.read<AuthenticationCubit<int>>().signOut(), onPressed: () =>
icon: const Icon(Icons.logout_rounded)) context.read<AuthenticationCubit<int>>().signOut(),
icon: const Icon(Icons.logout_rounded)),
IconButton(
onPressed: () =>
context.read<AuthenticationRepository<int>>().refresh(),
icon: const Icon(Icons.refresh))
], ],
), ),
body: const Padding( body: const Padding(
@@ -1,11 +1,18 @@
// Author: Hugo Pointcheval // Copyright (C) 2022 WYATT GROUP
// Email: git@pcl.ovh // Please see the AUTHORS file for details.
// ----- //
// File: welcome_page.dart // This program is free software: you can redistribute it and/or modify
// Created Date: 19/08/2022 12:33:21 // it under the terms of the GNU General Public License as published by
// Last Modified: Wed Nov 09 2022 // the Free Software Foundation, either version 3 of the License, or
// ----- // any later version.
// Copyright (c) 2022 //
// 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:example_router/presentation/features/sign_in/sign_in_page.dart'; import 'package:example_router/presentation/features/sign_in/sign_in_page.dart';
import 'package:example_router/presentation/features/sign_up/sign_up_page.dart'; import 'package:example_router/presentation/features/sign_up/sign_up_page.dart';
@@ -40,15 +40,15 @@ dependencies:
wyatt_form_bloc: wyatt_form_bloc:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.2.0 version: 0.2.0+2
wyatt_architecture: wyatt_architecture:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.0.2 version: 0.1.0+1
wyatt_type_utils: wyatt_type_utils:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.0.3+1 version: 0.0.4
# 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.
@@ -18,4 +18,3 @@ export 'constants/form_field.dart';
export 'constants/form_name.dart'; export 'constants/form_name.dart';
export 'enums/enums.dart'; export 'enums/enums.dart';
export 'exceptions/exceptions.dart'; export 'exceptions/exceptions.dart';
export 'utils/utils.dart';
@@ -20,15 +20,14 @@ part 'exceptions_firebase.dart';
abstract class AuthenticationFailureInterface extends AppException abstract class AuthenticationFailureInterface extends AppException
implements Exception { implements Exception {
AuthenticationFailureInterface(this.code, this.msg);
AuthenticationFailureInterface.fromCode(this.code)
: msg = 'An unknown error occurred.';
String code; String code;
String msg; String msg;
@override @override
String get message => msg; String get message => msg;
AuthenticationFailureInterface(this.code, this.msg);
AuthenticationFailureInterface.fromCode(this.code)
: msg = 'An unknown error occurred.';
} }
/// {@template apply_action_code_failure} /// {@template apply_action_code_failure}
@@ -256,3 +255,31 @@ abstract class GetIdTokenFailureInterface
GetIdTokenFailureInterface.fromCode(super.code) : super.fromCode(); GetIdTokenFailureInterface.fromCode(super.code) : super.fromCode();
} }
abstract class ReauthenticateFailureInterface
extends AuthenticationFailureInterface {
ReauthenticateFailureInterface(super.code, super.msg);
ReauthenticateFailureInterface.fromCode(super.code) : super.fromCode();
}
abstract class UpdateEmailFailureInterface
extends AuthenticationFailureInterface {
UpdateEmailFailureInterface(super.code, super.msg);
UpdateEmailFailureInterface.fromCode(super.code) : super.fromCode();
}
abstract class UpdatePasswordFailureInterface
extends AuthenticationFailureInterface {
UpdatePasswordFailureInterface(super.code, super.msg);
UpdatePasswordFailureInterface.fromCode(super.code) : super.fromCode();
}
abstract class ModelParsingFailureInterface
extends AuthenticationFailureInterface {
ModelParsingFailureInterface(super.code, super.msg);
ModelParsingFailureInterface.fromCode(super.code) : super.fromCode();
}
@@ -277,3 +277,82 @@ class GetIdTokenFailureFirebase extends GetIdTokenFailureInterface {
GetIdTokenFailureFirebase.fromCode(super.code) : super.fromCode(); GetIdTokenFailureFirebase.fromCode(super.code) : super.fromCode();
} }
class ReauthenticateFailureFirebase extends ReauthenticateFailureInterface {
ReauthenticateFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
ReauthenticateFailureFirebase.fromCode(String code) : super.fromCode(code) {
switch (code) {
case 'user-mismatch':
msg = 'Given credential does not correspond to the user.';
break;
case 'user-not-found':
msg = 'User is not found, please create an account.';
break;
case 'invalid-credential':
msg = 'The credential received is malformed or has expired.';
break;
case 'invalid-email':
msg = 'Email is not valid or badly formatted.';
break;
case 'wrong-password':
msg = 'Incorrect password, please try again.';
break;
case 'invalid-verification-code':
msg = 'The credential verification code received is invalid.';
break;
case 'invalid-verification-id':
msg = 'The credential verification ID received is invalid.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
class UpdateEmailFailureFirebase extends UpdateEmailFailureInterface {
UpdateEmailFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
UpdateEmailFailureFirebase.fromCode(String code) : super.fromCode(code) {
switch (code) {
case 'invalid-email':
msg = 'Email is not valid or badly formatted.';
break;
case 'email-already-in-use':
msg = 'An account already exists for that email.';
break;
case 'requires-recent-login':
msg = "User's last sign-in time does not meet the security threshold.";
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
class UpdatePasswordFailureFirebase extends UpdatePasswordFailureInterface {
UpdatePasswordFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
UpdatePasswordFailureFirebase.fromCode(String code) : super.fromCode(code) {
switch (code) {
case 'weak-password':
msg = 'Please enter a stronger password.';
break;
case 'requires-recent-login':
msg = "User's last sign-in time does not meet the security threshold.";
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
class ModelParsingFailureFirebase extends ModelParsingFailureInterface {
ModelParsingFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
ModelParsingFailureFirebase.fromCode(super.code) : super.fromCode();
}
@@ -1,39 +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:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
class Cryptography {
/// Generates a cryptographically secure random nonce, to be included in a
/// credential request.
static String generateNonce([int length = 32]) {
const charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();
return List.generate(length, (_) => charset[random.nextInt(charset.length)])
.join();
}
/// Returns the sha256 hash of [input] in hex notation.
static String sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}
}
@@ -20,11 +20,10 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class AuthenticationCacheDataSourceImpl<T extends Object> class AuthenticationCacheDataSourceImpl<T extends Object>
extends AuthenticationCacheDataSource<T> { extends AuthenticationCacheDataSource<T> {
AuthenticationCacheDataSourceImpl();
Account? _account; Account? _account;
T? _data; T? _data;
AuthenticationCacheDataSourceImpl();
@override @override
Future<void> storeAccount(Account? account) async { Future<void> storeAccount(Account? account) async {
_account = account; _account = account;
@@ -14,32 +14,21 @@
// 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:firebase_auth/firebase_auth.dart'; import 'package:wyatt_authentication_bloc/src/data/models/account_model_firebase.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';
class AuthenticationFirebaseDataSourceImpl class AuthenticationFirebaseDataSourceImpl
extends AuthenticationRemoteDataSource { extends AuthenticationRemoteDataSource {
AuthenticationFirebaseDataSourceImpl({
FirebaseAuth? firebaseAuth,
GoogleSignIn? googleSignIn,
}) : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance,
_googleSignIn = googleSignIn ?? GoogleSignIn();
final FirebaseAuth _firebaseAuth; final FirebaseAuth _firebaseAuth;
final GoogleSignIn _googleSignIn;
AuthenticationFirebaseDataSourceImpl({FirebaseAuth? firebaseAuth}) UserCredential? _latestCreds;
: _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance;
Account _mapper(User user) => AccountModel(
uid: user.uid,
emailVerified: user.emailVerified,
isAnonymous: user.isAnonymous,
providerId: user.providerData.first.providerId,
creationTime: user.metadata.creationTime,
lastSignInTime: user.metadata.lastSignInTime,
isNewUser: (user.metadata.creationTime != null &&
user.metadata.lastSignInTime != null)
? user.metadata.lastSignInTime! == user.metadata.creationTime!
: null,
email: user.email,
phoneNumber: user.phoneNumber,
photoURL: user.photoURL,
);
@override @override
Future<Account> signInWithEmailAndPassword({ Future<Account> signInWithEmailAndPassword({
@@ -51,12 +40,9 @@ class AuthenticationFirebaseDataSourceImpl
email: email, email: email,
password: password, password: password,
); );
_latestCreds = userCredential;
final user = userCredential.user; final user = userCredential.user;
if (user.isNotNull) { return AccountModelFirebase.fromFirebaseUser(user);
return _mapper(user!);
} else {
throw Exception(); // Get caught just after.
}
} on FirebaseAuthException catch (e) { } on FirebaseAuthException catch (e) {
throw SignInWithEmailAndPasswordFailureFirebase.fromCode(e.code); throw SignInWithEmailAndPasswordFailureFirebase.fromCode(e.code);
} catch (_) { } catch (_) {
@@ -76,12 +62,9 @@ class AuthenticationFirebaseDataSourceImpl
email: email, email: email,
password: password, password: password,
); );
_latestCreds = userCredential;
final user = userCredential.user; final user = userCredential.user;
if (user.isNotNull) { return AccountModelFirebase.fromFirebaseUser(user);
return _mapper(user!);
} else {
throw Exception(); // Get caught just after.
}
} on FirebaseAuthException catch (e) { } on FirebaseAuthException catch (e) {
throw SignUpWithEmailAndPasswordFailureFirebase.fromCode(e.code); throw SignUpWithEmailAndPasswordFailureFirebase.fromCode(e.code);
} catch (_) { } catch (_) {
@@ -92,6 +75,7 @@ class AuthenticationFirebaseDataSourceImpl
@override @override
Future<void> signOut() async { Future<void> signOut() async {
try { try {
_latestCreds = null;
await _firebaseAuth.signOut(); await _firebaseAuth.signOut();
} catch (_) { } catch (_) {
throw SignOutFailureFirebase(); throw SignOutFailureFirebase();
@@ -117,8 +101,11 @@ class AuthenticationFirebaseDataSourceImpl
@override @override
Stream<Account?> streamAccount() => Stream<Account?> streamAccount() =>
_firebaseAuth.userChanges().map<Account?>((user) { _firebaseAuth.userChanges().map<Account?>((user) {
final Account? account = (user.isNotNull) ? _mapper(user!) : null; try {
return account; return AccountModelFirebase.fromFirebaseUser(user);
} on FirebaseAuthException {
return null;
}
}); });
@override @override
@@ -164,12 +151,9 @@ class AuthenticationFirebaseDataSourceImpl
Future<Account> signInAnonymously() async { Future<Account> signInAnonymously() async {
try { try {
final userCredential = await _firebaseAuth.signInAnonymously(); final userCredential = await _firebaseAuth.signInAnonymously();
_latestCreds = userCredential;
final user = userCredential.user; final user = userCredential.user;
if (user.isNotNull) { return AccountModelFirebase.fromFirebaseUser(user);
return _mapper(user!);
} else {
throw Exception(); // Get caught just after.
}
} on FirebaseAuthException catch (e) { } on FirebaseAuthException catch (e) {
throw SignInAnonymouslyFailureFirebase.fromCode(e.code); throw SignInAnonymouslyFailureFirebase.fromCode(e.code);
} catch (_) { } catch (_) {
@@ -177,6 +161,35 @@ class AuthenticationFirebaseDataSourceImpl
} }
} }
@override
Future<Account> signInWithGoogle() async {
try {
// Trigger the authentication flow
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication? googleAuth =
await googleUser?.authentication;
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
final userCredential =
await _firebaseAuth.signInWithCredential(credential);
_latestCreds = userCredential;
final user = userCredential.user;
return AccountModelFirebase.fromFirebaseUser(user);
} on FirebaseAuthException catch (e) {
throw SignInWithGoogleFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithGoogleFailureFirebase();
}
}
@override @override
Future<bool> verifyPasswordResetCode({required String code}) async { Future<bool> verifyPasswordResetCode({required String code}) async {
try { try {
@@ -199,4 +212,48 @@ class AuthenticationFirebaseDataSourceImpl
throw RefreshFailureFirebase(); throw RefreshFailureFirebase();
} }
} }
@override
Future<Account> reauthenticateWithCredential() async {
try {
if (_latestCreds?.credential != null) {
await _firebaseAuth.currentUser
?.reauthenticateWithCredential(_latestCreds!.credential!);
} else {
throw Exception(); // Get caught just after.
}
final user = _firebaseAuth.currentUser;
return AccountModelFirebase.fromFirebaseUser(user);
} on FirebaseAuthException catch (e) {
throw ReauthenticateFailureFirebase.fromCode(e.code);
} catch (_) {
throw ReauthenticateFailureFirebase();
}
}
@override
Future<Account> updateEmail({required String email}) async {
try {
await _firebaseAuth.currentUser!.updateEmail(email);
final user = _firebaseAuth.currentUser;
return AccountModelFirebase.fromFirebaseUser(user);
} on FirebaseAuthException catch (e) {
throw UpdateEmailFailureFirebase.fromCode(e.code);
} catch (_) {
throw UpdateEmailFailureFirebase();
}
}
@override
Future<Account> updatePassword({required String password}) async {
try {
await _firebaseAuth.currentUser!.updatePassword(password);
final user = _firebaseAuth.currentUser;
return AccountModelFirebase.fromFirebaseUser(user);
} on FirebaseAuthException catch (e) {
throw UpdatePasswordFailureFirebase.fromCode(e.code);
} catch (_) {
throw UpdatePasswordFailureFirebase();
}
}
} }
@@ -21,19 +21,19 @@ 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';
class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource {
AuthenticationMockDataSourceImpl({
this.idToken = 'fake-id-token',
this.registeredAccounts,
});
Pair<Account, String>? _connectedMock; Pair<Account, String>? _connectedMock;
Pair<Account, String>? _registeredMock; Pair<Account, String>? _registeredMock;
DateTime _lastSignInTime = DateTime.now();
final StreamController<Account?> _streamAccount = StreamController() final StreamController<Account?> _streamAccount = StreamController()
..add(null); ..add(null);
final List<Pair<Account, String>>? registeredAccounts; final List<Pair<Account, String>>? registeredAccounts;
final String idToken; final String idToken;
AuthenticationMockDataSourceImpl({
this.idToken = 'fake-id-token',
this.registeredAccounts,
});
Future<void> _randomDelay() async { Future<void> _randomDelay() async {
await Future<void>.delayed( await Future<void>.delayed(
Duration(milliseconds: Random().nextInt(400) + 200), Duration(milliseconds: Random().nextInt(400) + 200),
@@ -111,12 +111,33 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource {
uid: 'mock-id-anom', uid: 'mock-id-anom',
emailVerified: false, emailVerified: false,
isAnonymous: true, isAnonymous: true,
providerId: 'wyatt', providerId: 'wyatt-studio.fr',
creationTime: creation, creationTime: creation,
lastSignInTime: creation, lastSignInTime: creation,
isNewUser: creation == creation,
); );
_streamAccount.add(mock); _streamAccount.add(mock);
_connectedMock = _connectedMock?.copyWith(left: mock); _connectedMock = _connectedMock?.copyWith(left: mock);
_lastSignInTime = DateTime.now();
return Future.value(mock);
}
@override
Future<Account> signInWithGoogle() async {
await _randomDelay();
final creation = DateTime.now();
final mock = AccountModel(
uid: 'mock-id-google',
emailVerified: true,
isAnonymous: false,
providerId: 'google.com',
creationTime: creation,
lastSignInTime: creation,
isNewUser: creation == creation,
);
_streamAccount.add(mock);
_connectedMock = _connectedMock?.copyWith(left: mock);
_lastSignInTime = DateTime.now();
return Future.value(mock); return Future.value(mock);
} }
@@ -148,6 +169,7 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource {
} }
_streamAccount.add(_registeredMock!.left); _streamAccount.add(_registeredMock!.left);
_connectedMock = _registeredMock!.copyWith(); _connectedMock = _registeredMock!.copyWith();
_lastSignInTime = DateTime.now();
return _registeredMock!.left!; return _registeredMock!.left!;
} }
throw SignInWithCredentialFailureFirebase(); throw SignInWithCredentialFailureFirebase();
@@ -188,9 +210,12 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource {
email: email, email: email,
creationTime: creation, creationTime: creation,
lastSignInTime: creation, lastSignInTime: creation,
isNewUser: creation == creation,
); );
_streamAccount.add(mock); _streamAccount.add(mock);
_registeredMock = Pair(mock, password); _registeredMock = Pair(mock, password);
_connectedMock = _registeredMock!.copyWith();
_lastSignInTime = DateTime.now();
return Future.value(mock); return Future.value(mock);
} }
@@ -202,4 +227,45 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource {
await _randomDelay(); await _randomDelay();
return true; return true;
} }
@override
Future<Account> reauthenticateWithCredential() async {
await _randomDelay();
if (_connectedMock.isNull) {
throw ReauthenticateFailureFirebase();
}
await refresh();
_lastSignInTime = DateTime.now();
return Future.value(_connectedMock?.left);
}
@override
Future<Account> updateEmail({required String email}) {
final before = DateTime.now().subtract(const Duration(seconds: 10));
if (_lastSignInTime.isBefore(before)) {
throw UpdateEmailFailureFirebase('requires-recent-login');
}
final refresh = DateTime.now();
final mock = (_connectedMock?.left as AccountModel?)
?.copyWith(lastSignInTime: refresh, email: email);
_connectedMock = _connectedMock?.copyWith(left: mock);
_registeredMock = _registeredMock?.copyWith(left: mock);
_streamAccount.add(mock);
return Future.value(_connectedMock?.left);
}
@override
Future<Account> updatePassword({required String password}) {
final before = DateTime.now().subtract(const Duration(seconds: 10));
if (_lastSignInTime.isBefore(before)) {
throw UpdatePasswordFailureFirebase('requires-recent-login');
}
final refresh = DateTime.now();
final mock = (_connectedMock?.left as AccountModel?)
?.copyWith(lastSignInTime: refresh);
_connectedMock = _connectedMock?.copyWith(left: mock, right: password);
_registeredMock = _registeredMock?.copyWith(left: mock, right: password);
_streamAccount.add(mock);
return Future.value(_connectedMock?.left);
}
} }
@@ -72,16 +72,17 @@ class AccountModel extends Account {
String? phoneNumber, String? phoneNumber,
String? photoURL, String? photoURL,
String? providerId, String? providerId,
}) => AccountModel( }) =>
uid: uid ?? this.uid, AccountModel(
email: email ?? this.email, uid: uid ?? this.uid,
creationTime: creationTime ?? this.creationTime, email: email ?? this.email,
emailVerified: emailVerified ?? this.emailVerified, creationTime: creationTime ?? this.creationTime,
isAnonymous: isAnonymous ?? this.isAnonymous, emailVerified: emailVerified ?? this.emailVerified,
isNewUser: isNewUser ?? this.isNewUser, isAnonymous: isAnonymous ?? this.isAnonymous,
lastSignInTime: lastSignInTime ?? this.lastSignInTime, isNewUser: isNewUser ?? this.isNewUser,
phoneNumber: phoneNumber ?? this.phoneNumber, lastSignInTime: lastSignInTime ?? this.lastSignInTime,
photoURL: photoURL ?? this.photoURL, phoneNumber: phoneNumber ?? this.phoneNumber,
providerId: providerId ?? this.providerId, photoURL: photoURL ?? this.photoURL,
); providerId: providerId ?? this.providerId,
);
} }
@@ -0,0 +1,55 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
class AccountModelFirebase extends AccountModel {
AccountModelFirebase._({
required super.uid,
required super.emailVerified,
required super.isAnonymous,
required super.providerId,
super.lastSignInTime,
super.creationTime,
super.isNewUser,
super.email,
super.phoneNumber,
super.photoURL,
});
factory AccountModelFirebase.fromFirebaseUser(User? user) {
if (user != null) {
return AccountModelFirebase._(
uid: user.uid,
emailVerified: user.emailVerified,
isAnonymous: user.isAnonymous,
providerId: user.providerData.first.providerId,
creationTime: user.metadata.creationTime,
lastSignInTime: user.metadata.lastSignInTime,
isNewUser: (user.metadata.creationTime != null &&
user.metadata.lastSignInTime != null)
? user.metadata.lastSignInTime! == user.metadata.creationTime!
: null,
email: user.email,
phoneNumber: user.phoneNumber,
photoURL: user.photoURL,
);
} else {
throw ModelParsingFailureFirebase('null-user', 'User cannot be null!');
}
}
}
@@ -29,8 +29,9 @@ class AccountWrapperModel<T> extends AccountWrapper<T> {
AccountWrapperModel<T> copyWith({ AccountWrapperModel<T> copyWith({
Account? account, Account? account,
T? data, T? data,
}) => AccountWrapperModel<T>( }) =>
account ?? this.account, AccountWrapperModel<T>(
data ?? this.data, account ?? this.account,
); data ?? this.data,
);
} }
@@ -14,6 +14,9 @@
// 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 'dart:async';
import 'package:rxdart/rxdart.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/core/constants/form_field.dart'; import 'package:wyatt_authentication_bloc/src/core/constants/form_field.dart';
import 'package:wyatt_authentication_bloc/src/core/constants/form_name.dart'; import 'package:wyatt_authentication_bloc/src/core/constants/form_name.dart';
@@ -26,30 +29,27 @@ import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
typedef OnSignUpSuccess<T> = FutureResult<T?> Function( typedef OnSignUpSuccess<T> = FutureOrResult<T?> Function(
AuthenticationRepository<T> repo,
Account? account, Account? account,
WyattForm form, WyattForm form,
); );
typedef OnAuthChange<T> = FutureResult<T?> Function(Account? account); typedef OnAuthChange<T> = FutureOrResult<T?> Function(
AuthenticationRepository<T> repo,
Account? account,
);
class AuthenticationRepositoryImpl<T extends Object> class AuthenticationRepositoryImpl<T extends Object>
extends AuthenticationRepository<T> { extends AuthenticationRepository<T> {
final AuthenticationCacheDataSource<T> _authenticationLocalDataSource;
final AuthenticationRemoteDataSource _authenticationRemoteDataSource;
late FormRepository _formRepository;
final OnSignUpSuccess<T>? _onSignUpSuccess;
final OnAuthChange<T>? _onAccountChanges;
AuthenticationRepositoryImpl({ AuthenticationRepositoryImpl({
required AuthenticationCacheDataSource<T> authenticationCacheDataSource, required AuthenticationCacheDataSource<T> authenticationCacheDataSource,
required AuthenticationRemoteDataSource authenticationRemoteDataSource, required AuthenticationRemoteDataSource authenticationRemoteDataSource,
FormRepository? formRepository, FormRepository? formRepository,
// ignore: strict_raw_type // ignore: strict_raw_type
List<FormInput>? extraSignUpInputs, List<FormInput>? extraSignUpInputs,
FormInputValidator<String?, ValidationError>? customEmailValidator,
FormInputValidator<String?, ValidationError>? customPasswordValidator,
OnSignUpSuccess<T>? onSignUpSuccess, OnSignUpSuccess<T>? onSignUpSuccess,
OnAuthChange<T>? onAuthChange, OnAuthChange<T>? onAuthChange,
}) : _authenticationLocalDataSource = authenticationCacheDataSource, }) : _authenticationLocalDataSource = authenticationCacheDataSource,
@@ -64,8 +64,14 @@ class AuthenticationRepositoryImpl<T extends Object>
..registerForm( ..registerForm(
WyattFormImpl( WyattFormImpl(
[ [
FormInput(AuthFormField.email, const Email.pure()), FormInput(
FormInput(AuthFormField.password, const Password.pure()) AuthFormField.email,
customEmailValidator ?? const Email.pure(),
),
FormInput(
AuthFormField.password,
customPasswordValidator ?? const Password.pure(),
)
], ],
name: AuthFormName.signInForm, name: AuthFormName.signInForm,
), ),
@@ -73,20 +79,38 @@ class AuthenticationRepositoryImpl<T extends Object>
..registerForm( ..registerForm(
WyattFormImpl( WyattFormImpl(
[ [
FormInput(AuthFormField.email, const Email.pure()), FormInput(
FormInput(AuthFormField.password, const Password.pure()), AuthFormField.email,
customEmailValidator ?? const Email.pure(),
),
FormInput(
AuthFormField.password,
customPasswordValidator ?? const Password.pure(),
),
...extraSignUpInputs ?? [] ...extraSignUpInputs ?? []
], ],
name: AuthFormName.signUpForm, name: AuthFormName.signUpForm,
), ),
); );
} }
final AuthenticationCacheDataSource<T> _authenticationLocalDataSource;
final AuthenticationRemoteDataSource _authenticationRemoteDataSource;
late FormRepository _formRepository;
final OnSignUpSuccess<T>? _onSignUpSuccess;
final OnAuthChange<T>? _onAccountChanges;
final StreamController<FutureOrResult<AccountWrapper<T>>> _signUpStream =
StreamController();
bool _pause = false;
@override @override
FormRepository get formRepository => _formRepository; FormRepository get formRepository => _formRepository;
@override @override
FutureResult<Account> signInWithEmailAndPassword({ FutureOrResult<Account> signInWithEmailAndPassword({
required String email, required String email,
required String password, required String password,
}) => }) =>
@@ -104,7 +128,7 @@ class AuthenticationRepositoryImpl<T extends Object>
); );
@override @override
FutureResult<void> signOut() => FutureOrResult<void> signOut() =>
Result.tryCatchAsync<void, AppException, AppException>( Result.tryCatchAsync<void, AppException, AppException>(
() async { () async {
await _authenticationRemoteDataSource.signOut(); await _authenticationRemoteDataSource.signOut();
@@ -114,12 +138,13 @@ class AuthenticationRepositoryImpl<T extends Object>
); );
@override @override
FutureResult<Account> signUp({ FutureOrResult<Account> signUp({
required String email, required String email,
required String password, required String password,
}) => }) =>
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,
@@ -127,42 +152,51 @@ class AuthenticationRepositoryImpl<T extends Object>
await _authenticationLocalDataSource.storeAccount(account); await _authenticationLocalDataSource.storeAccount(account);
if (_onSignUpSuccess.isNotNull) { if (_onSignUpSuccess.isNotNull) {
final dataResult = await _onSignUpSuccess!.call( final dataResult = await _onSignUpSuccess!.call(
this,
account, account,
_formRepository.accessForm(AuthFormName.signUpForm).clone(), _formRepository.accessForm(AuthFormName.signUpForm).clone(),
); );
await dataResult.foldAsync( await dataResult.foldAsync(
_authenticationLocalDataSource.storeData, (data) async {
await _authenticationLocalDataSource.storeData(data);
_signUpStream
.add(Future.value(Ok(AccountWrapperModel(account, data))));
},
(error) async => error, (error) async => error,
); );
} }
_pause = false;
return account; return account;
}, },
(error) => error, (error) {
_pause = false;
return error;
},
); );
@override @override
FutureResult<void> destroyCache() => FutureOrResult<void> destroyCache() =>
Result.tryCatchAsync<void, AppException, AppException>( Result.tryCatchAsync<void, AppException, AppException>(
_authenticationLocalDataSource.destroy, _authenticationLocalDataSource.destroy,
(error) => error, (error) => error,
); );
@override @override
FutureResult<AccountWrapper<T>> getCache() => FutureOrResult<AccountWrapper<T>> getCache() =>
Result.tryCatchAsync<AccountWrapper<T>, AppException, AppException>( Result.tryCatchAsync<AccountWrapper<T>, AppException, AppException>(
_authenticationLocalDataSource.load, _authenticationLocalDataSource.load,
(error) => error, (error) => error,
); );
@override @override
FutureResult<Account> getAccount() => FutureOrResult<Account> getAccount() =>
Result.tryCatchAsync<Account, AppException, AppException>( Result.tryCatchAsync<Account, AppException, AppException>(
_authenticationLocalDataSource.loadAccount, _authenticationLocalDataSource.loadAccount,
(error) => error, (error) => error,
); );
@override @override
FutureResult<void> setAccount( FutureOrResult<void> setAccount(
Account account, Account account,
) => ) =>
Result.tryCatchAsync<void, AppException, AppException>( Result.tryCatchAsync<void, AppException, AppException>(
@@ -173,14 +207,14 @@ class AuthenticationRepositoryImpl<T extends Object>
); );
@override @override
FutureResult<T> getData() => FutureOrResult<T> getData() =>
Result.tryCatchAsync<T, AppException, AppException>( Result.tryCatchAsync<T, AppException, AppException>(
_authenticationLocalDataSource.loadData, _authenticationLocalDataSource.loadData,
(error) => error, (error) => error,
); );
@override @override
FutureResult<void> setData( FutureOrResult<void> setData(
T? data, T? data,
) => ) =>
Result.tryCatchAsync<void, AppException, AppException>( Result.tryCatchAsync<void, AppException, AppException>(
@@ -191,26 +225,31 @@ class AuthenticationRepositoryImpl<T extends Object>
); );
@override @override
FutureResult<String> getIdentityToken() => FutureOrResult<String> getIdentityToken() =>
Result.tryCatchAsync<String, AppException, AppException>( Result.tryCatchAsync<String, AppException, AppException>(
_authenticationRemoteDataSource.getIdentityToken, _authenticationRemoteDataSource.getIdentityToken,
(error) => error, (error) => error,
); );
@override @override
Stream<FutureResult<AccountWrapper<T>>> streamAccount() => Stream<FutureOrResult<AccountWrapper<T>>> streamAccount() => MergeStream([
_authenticationRemoteDataSource.streamAccount().map((account) async { _signUpStream.stream.asBroadcastStream(),
if (_onAccountChanges.isNotNull) { _authenticationRemoteDataSource.streamAccount().map((account) async {
final dataResult = await _onAccountChanges!.call(account); if (_onAccountChanges.isNotNull && !_pause) {
return dataResult.map((data) => AccountWrapperModel(account, data)); final dataResult = await _onAccountChanges!.call(this, account);
} return dataResult.map((data) {
return Ok<AccountWrapperModel<T>, AppException>( _authenticationLocalDataSource.storeData(data);
AccountWrapperModel<T>(account, null), return AccountWrapperModel(account, data);
); });
}); }
return Ok<AccountWrapperModel<T>, AppException>(
AccountWrapperModel<T>(account, null),
);
})
]);
@override @override
FutureResult<void> confirmPasswordReset({ FutureOrResult<void> confirmPasswordReset({
required String code, required String code,
required String newPassword, required String newPassword,
}) => }) =>
@@ -225,7 +264,7 @@ class AuthenticationRepositoryImpl<T extends Object>
); );
@override @override
FutureResult<void> sendEmailVerification() => FutureOrResult<void> sendEmailVerification() =>
Result.tryCatchAsync<void, AppException, AppException>( Result.tryCatchAsync<void, AppException, AppException>(
() async { () async {
await _authenticationRemoteDataSource.sendEmailVerification(); await _authenticationRemoteDataSource.sendEmailVerification();
@@ -234,7 +273,7 @@ class AuthenticationRepositoryImpl<T extends Object>
); );
@override @override
FutureResult<void> sendPasswordResetEmail({required String email}) => FutureOrResult<void> sendPasswordResetEmail({required String email}) =>
Result.tryCatchAsync<void, AppException, AppException>( Result.tryCatchAsync<void, AppException, AppException>(
() async { () async {
await _authenticationRemoteDataSource.sendPasswordResetEmail( await _authenticationRemoteDataSource.sendPasswordResetEmail(
@@ -245,7 +284,7 @@ class AuthenticationRepositoryImpl<T extends Object>
); );
@override @override
FutureResult<Account> signInAnonymously() => FutureOrResult<Account> signInAnonymously() =>
Result.tryCatchAsync<Account, AppException, AppException>( Result.tryCatchAsync<Account, AppException, AppException>(
() async { () async {
final account = final account =
@@ -256,7 +295,18 @@ class AuthenticationRepositoryImpl<T extends Object>
); );
@override @override
FutureResult<bool> verifyPasswordResetCode({required String code}) => FutureOrResult<Account> signInWithGoogle() =>
Result.tryCatchAsync<Account, AppException, AppException>(
() async {
final account =
await _authenticationRemoteDataSource.signInWithGoogle();
return account;
},
(error) => error,
);
@override
FutureOrResult<bool> verifyPasswordResetCode({required String code}) =>
Result.tryCatchAsync<bool, AppException, AppException>( Result.tryCatchAsync<bool, AppException, AppException>(
() async { () async {
final response = await _authenticationRemoteDataSource final response = await _authenticationRemoteDataSource
@@ -267,11 +317,45 @@ class AuthenticationRepositoryImpl<T extends Object>
); );
@override @override
FutureResult<void> refresh() => FutureOrResult<void> refresh() =>
Result.tryCatchAsync<void, AppException, AppException>( Result.tryCatchAsync<void, AppException, AppException>(
() async { () async {
await _authenticationRemoteDataSource.refresh(); await _authenticationRemoteDataSource.refresh();
}, },
(error) => error, (error) => error,
); );
@override
FutureOrResult<Account> reauthenticateWithCredential() =>
Result.tryCatchAsync<Account, AppException, AppException>(
() async {
final account = await _authenticationRemoteDataSource
.reauthenticateWithCredential();
return account;
},
(error) => error,
);
@override
FutureOrResult<Account> updateEmail({required String email}) =>
Result.tryCatchAsync<Account, AppException, AppException>(
() async {
final account =
await _authenticationRemoteDataSource.updateEmail(email: email);
return account;
},
(error) => error,
);
@override
FutureOrResult<Account> updatePassword({required String password}) =>
Result.tryCatchAsync<Account, AppException, AppException>(
() async {
final account = await _authenticationRemoteDataSource.updatePassword(
password: password,
);
return account;
},
(error) => error,
);
} }
@@ -48,4 +48,12 @@ abstract class AuthenticationRemoteDataSource extends BaseRemoteDataSource {
Future<bool> verifyPasswordResetCode({required String code}); Future<bool> verifyPasswordResetCode({required String code});
Future<Account> signInAnonymously(); Future<Account> signInAnonymously();
Future<Account> signInWithGoogle();
Future<Account> updateEmail({required String email});
Future<Account> updatePassword({required String password});
Future<Account> reauthenticateWithCredential();
} }
@@ -30,7 +30,7 @@ abstract class AuthenticationRepository<T> extends BaseRepository {
/// Throws a SignUpWithEmailAndPasswordFailureInterface if /// Throws a SignUpWithEmailAndPasswordFailureInterface if
/// an exception occurs. /// an exception occurs.
/// {@endtemplate} /// {@endtemplate}
FutureResult<Account> signUp({ FutureOrResult<Account> signUp({
required String email, required String email,
required String password, required String password,
}); });
@@ -40,21 +40,21 @@ abstract class AuthenticationRepository<T> extends BaseRepository {
/// ///
/// Throws a SendEmailVerificationFailureInterface if an exception occurs. /// Throws a SendEmailVerificationFailureInterface if an exception occurs.
/// {@endtemplate} /// {@endtemplate}
FutureResult<void> sendEmailVerification(); FutureOrResult<void> sendEmailVerification();
/// {@template send_password_reset_email} /// {@template send_password_reset_email}
/// Sends a password reset email to the provided [email]. /// Sends a password reset email to the provided [email].
/// ///
/// Throws a SendPasswordResetEmailFailureInterface if an exception occurs. /// Throws a SendPasswordResetEmailFailureInterface if an exception occurs.
/// {@endtemplate} /// {@endtemplate}
FutureResult<void> sendPasswordResetEmail({required String email}); FutureOrResult<void> sendPasswordResetEmail({required String email});
/// {@template confirm_password_reset} /// {@template confirm_password_reset}
/// Confirms the password reset with the provided [newPassword] and [code]. /// Confirms the password reset with the provided [newPassword] and [code].
/// ///
/// Throws a ConfirmPasswordResetFailureInterface if an exception occurs. /// Throws a ConfirmPasswordResetFailureInterface if an exception occurs.
/// {@endtemplate} /// {@endtemplate}
FutureResult<void> confirmPasswordReset({ FutureOrResult<void> confirmPasswordReset({
required String code, required String code,
required String newPassword, required String newPassword,
}); });
@@ -64,14 +64,21 @@ abstract class AuthenticationRepository<T> extends BaseRepository {
/// ///
/// Throws a VerifyPasswordResetCodeFailureInterface if an exception occurs. /// Throws a VerifyPasswordResetCodeFailureInterface if an exception occurs.
/// {@endtemplate} /// {@endtemplate}
FutureResult<bool> verifyPasswordResetCode({required String code}); FutureOrResult<bool> verifyPasswordResetCode({required String code});
/// {@template signin_anom} /// {@template signin_anom}
/// Sign in anonymously. /// Sign in anonymously.
/// ///
/// Throws a SignInAnonymouslyFailureInterface if an exception occurs. /// Throws a SignInAnonymouslyFailureInterface if an exception occurs.
/// {@endtemplate} /// {@endtemplate}
FutureResult<Account> signInAnonymously(); FutureOrResult<Account> signInAnonymously();
/// {@template signin_google}
/// Starts the Sign In with Google Flow.
///
/// Throws a SignInWithGoogleFailureInterface if an exception occurs.
/// {@endtemplate}
FutureOrResult<Account> signInWithGoogle();
/// {@template signin_pwd} /// {@template signin_pwd}
/// Signs in with the provided [email] and [password]. /// Signs in with the provided [email] and [password].
@@ -79,18 +86,51 @@ abstract class AuthenticationRepository<T> extends BaseRepository {
/// Throws a SignInWithEmailAndPasswordFailureInterface if /// Throws a SignInWithEmailAndPasswordFailureInterface if
/// an exception occurs. /// an exception occurs.
/// {@endtemplate} /// {@endtemplate}
FutureResult<Account> signInWithEmailAndPassword({ FutureOrResult<Account> signInWithEmailAndPassword({
required String email, required String email,
required String password, required String password,
}); });
/// {@template update_email}
/// Update or add [email].
///
/// Throws a UpdateEmailFailureInterface if
/// an exception occurs.
/// {@endtemplate}
FutureOrResult<Account> updateEmail({
required String email,
});
/// {@template update_password}
/// Update or add [password].
///
/// Throws a UpdatePasswordFailureInterface if
/// an exception occurs.
/// {@endtemplate}
FutureOrResult<Account> updatePassword({
required String password,
});
/// {@template reauthenticate}
/// Some security-sensitive actions—such as deleting an account,
/// setting a primary email address, and changing a password—require that
/// the user has recently signed in.
///
/// Throws a ReauthenticateFailureInterface if
/// an exception occurs.
/// {@endtemplate}
FutureOrResult<Account> reauthenticateWithCredential();
/// {@template signout} /// {@template signout}
/// Signs out the current user. /// Signs out the current user.
/// It also clears the cache and the associated data. /// It also clears the cache and the associated data.
/// {@endtemplate} /// {@endtemplate}
FutureResult<void> signOut(); FutureOrResult<void> signOut();
FutureResult<void> refresh(); /// {@template refresh}
/// Refreshes the current user, if signed in.
/// {@endtemplate}
FutureOrResult<void> refresh();
/// {@template stream_account} /// {@template stream_account}
/// Stream of [AccountWrapper] which will emit the current account when /// Stream of [AccountWrapper] which will emit the current account when
@@ -99,16 +139,16 @@ abstract class AuthenticationRepository<T> extends BaseRepository {
/// Emits [AccountWrapper] with null [Account] if the user is not /// Emits [AccountWrapper] with null [Account] if the user is not
/// authenticated. /// authenticated.
/// {@endtemplate} /// {@endtemplate}
Stream<FutureResult<AccountWrapper<T>>> streamAccount(); Stream<FutureOrResult<AccountWrapper<T>>> streamAccount();
FutureResult<String> getIdentityToken(); FutureOrResult<String> getIdentityToken();
FutureResult<Account> getAccount(); FutureOrResult<Account> getAccount();
FutureResult<void> setAccount(Account account); FutureOrResult<void> setAccount(Account account);
FutureResult<T> getData(); FutureOrResult<T> getData();
FutureResult<void> setData(T? data); FutureOrResult<void> setData(T? data);
FutureResult<AccountWrapper<T>> getCache(); FutureOrResult<AccountWrapper<T>> getCache();
FutureResult<void> destroyCache(); FutureOrResult<void> destroyCache();
} }
@@ -26,14 +26,13 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'authentication_state.dart'; part 'authentication_state.dart';
class AuthenticationCubit<Extra> extends Cubit<AuthenticationState<Extra>> { class AuthenticationCubit<Extra> extends Cubit<AuthenticationState<Extra>> {
final AuthenticationRepository<Extra> _authenticationRepository;
AuthenticationCubit({ AuthenticationCubit({
required AuthenticationRepository<Extra> authenticationRepository, required AuthenticationRepository<Extra> authenticationRepository,
}) : _authenticationRepository = authenticationRepository, }) : _authenticationRepository = authenticationRepository,
super(const AuthenticationState.unknown()) { super(const AuthenticationState.unknown()) {
_listenForAuthenticationChanges(); _listenForAuthenticationChanges();
} }
final AuthenticationRepository<Extra> _authenticationRepository;
void _listenForAuthenticationChanges() { void _listenForAuthenticationChanges() {
_authenticationRepository.streamAccount().listen((accountFutureResult) { _authenticationRepository.streamAccount().listen((accountFutureResult) {
@@ -56,6 +55,19 @@ class AuthenticationCubit<Extra> extends Cubit<AuthenticationState<Extra>> {
}); });
} }
/// If authenticated, re-emits state with data freshly loaded from cache.
FutureOr<void> reloadCache() async {
if (state.status == AuthenticationStatus.authenticated) {
final data = await _authenticationRepository.getCache();
emit(
data.fold(
AuthenticationState<Extra>.authenticated,
(error) => state,
),
);
}
}
FutureOr<void> signOut() { FutureOr<void> signOut() {
// TODO(hpcl): maybe force unauthenticated by emitting an event // TODO(hpcl): maybe force unauthenticated by emitting an event
_authenticationRepository.signOut(); _authenticationRepository.signOut();
@@ -17,13 +17,8 @@
part of 'authentication_cubit.dart'; part of 'authentication_cubit.dart';
class AuthenticationState<Extra> extends Equatable { class AuthenticationState<Extra> extends Equatable {
final AuthenticationStatus status; const AuthenticationState.unauthenticated()
final AccountWrapper<Extra>? accountWrapper; : this._(status: AuthenticationStatus.unauthenticated);
const AuthenticationState._({required this.status, this.accountWrapper});
const AuthenticationState.unknown()
: this._(status: AuthenticationStatus.unknown);
const AuthenticationState.authenticated(AccountWrapper<Extra> accountWrapper) const AuthenticationState.authenticated(AccountWrapper<Extra> accountWrapper)
: this._( : this._(
@@ -31,8 +26,12 @@ class AuthenticationState<Extra> extends Equatable {
accountWrapper: accountWrapper, accountWrapper: accountWrapper,
); );
const AuthenticationState.unauthenticated() const AuthenticationState.unknown()
: this._(status: AuthenticationStatus.unauthenticated); : this._(status: AuthenticationStatus.unknown);
const AuthenticationState._({required this.status, this.accountWrapper});
final AuthenticationStatus status;
final AccountWrapper<Extra>? accountWrapper;
@override @override
List<Object?> get props => [status, accountWrapper]; List<Object?> get props => [status, accountWrapper];

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