Compare commits

...
Author SHA1 Message Date
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
hugo c945cd435e chore(release): publish packages
- wyatt_authentication_bloc@0.4.0
 - wyatt_form_bloc@0.2.0
2022-11-16 16:03:13 -05:00
hugo 852d29ef0a refactor(auth): update example with new input builders 2022-11-16 16:02:18 -05:00
hugo 08383ec03b refactor(form)!: input builders provides FormInput 2022-11-16 16:00:13 -05:00
hugo ee8ce76839 fix(form): disable status selection and rebuild SubmitBuilder on every state changes #28 2022-11-16 15:51:34 -05:00
hugo f05f2230e9 fix(auth): add initial null account event in stream in the mock authentication 2022-11-16 15:46:07 -05:00
hugo 54002beaab test(auth): add tests for the SignUpCubit update method 2022-11-16 15:45:11 -05:00
hugo d5698fbffc test(form): add formReplace test case #27 2022-11-16 15:43:52 -05:00
hugo 31ad460757 fix(auth)!: add signInWithEmailAndPassword and signInAnonymously methods in SignInCubit #26 2022-11-15 19:25:20 -05:00
hugo 4fd2a2a16c chore(auth): remove old example and rename actual 2022-11-15 18:52:10 -05:00
hugo 6eb40de72c feat(form): add metadata tag to bypass validation #29 2022-11-15 18:48:02 -05:00
hugo cecbc94a95 chore(auth): add global vscode folder symlink 2022-11-15 18:13:56 -05:00
hugo 3820fc4764 chore(auth): remove local vscode folder 2022-11-15 18:13:30 -05:00
hugo 1b893a5b84 test(form): add some tests 2022-11-15 18:10:50 -05:00
hugo 72661a343f fix(form): fix set operations behaviors 2022-11-15 18:10:30 -05:00
hugo 9ebe8e21dc fix(form): add nullable validator on boolean value 2022-11-15 18:09:39 -05:00
hugo 141f15b444 feat(form): add shorthands to access validator status 2022-11-15 18:09:13 -05:00
hugo 3a8a604a48 chore(form): add vscode folder symlink 2022-11-15 18:06:13 -05:00
hugo 41493207b3 chore(form): remove local vscode folder 2022-11-15 18:05:18 -05:00
hugo cbc4a2e149 chore: change vscode global settings 2022-11-15 18:03:30 -05:00
hugo 7345e415a7 chore(release): publish packages
- wyatt_crud_bloc@0.1.0
2022-11-13 20:29:19 -05:00
hugo ff680614aa typo(crud): fix line length 2022-11-13 20:21:20 -05:00
hugo 3e110e2eb5 fix(crud): update example 2022-11-13 20:19:21 -05:00
hugo 1ccb0a540e refactor(crud)!: migrate to wyatt architecture 2022-11-13 20:19:10 -05:00
hugo f9da719b91 refactor(crud): add vscode config 2022-11-13 20:18:10 -05:00
hugo 95f41025f4 chore(release): publish packages
- wyatt_authentication_bloc@0.3.0+1
2022-11-11 19:09:01 -05:00
hugo eceda684f0 fix(auth): fix some bugs to validate publishing 2022-11-11 19:08:28 -05:00
289 changed files with 6309 additions and 2436 deletions
+7 -2
View File
@@ -1,4 +1,10 @@
{ {
// Dart
"dart.runPubGetOnPubspecChanges": "never",
// Gitea
"gitea.owner": "Wyatt-FOSS",
"gitea.repo": "wyatt-packages",
// Header
"psi-header.changes-tracking": { "psi-header.changes-tracking": {
"isActive": true "isActive": true
}, },
@@ -67,5 +73,4 @@
], ],
} }
], ],
"dart.runPubGetOnPubspecChanges": false, }
}
+98
View File
@@ -3,6 +3,104 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## 2022-11-16
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`wyatt_authentication_bloc` - `v0.4.0+1`](#wyatt_authentication_bloc---v0401)
---
#### `wyatt_authentication_bloc` - `v0.4.0+1`
- **FIX**: add sort of mutex on onAuthChanges callback trigger.
## 2022-11-16
### Changes
---
Packages with breaking changes:
- [`wyatt_authentication_bloc` - `v0.4.0`](#wyatt_authentication_bloc---v040)
- [`wyatt_form_bloc` - `v0.2.0`](#wyatt_form_bloc---v020)
Packages with other changes:
- There are no other changes in this release.
---
#### `wyatt_authentication_bloc` - `v0.4.0`
- **REFACTOR**: update example with new input builders.
- **FIX**: add initial null account event in stream in the mock authentication.
- **BREAKING** **FIX**: add signInWithEmailAndPassword and signInAnonymously methods in SignInCubit #26.
#### `wyatt_form_bloc` - `v0.2.0`
- **FIX**: disable status selection and rebuild SubmitBuilder on every state changes #28.
- **FIX**: fix set operations behaviors.
- **FIX**: add nullable validator on boolean value.
- **FEAT**: add metadata tag to bypass validation #29.
- **FEAT**: add shorthands to access validator status.
- **BREAKING** **REFACTOR**: input builders provides FormInput.
## 2022-11-13
### Changes
---
Packages with breaking changes:
- [`wyatt_crud_bloc` - `v0.1.0`](#wyatt_crud_bloc---v010)
Packages with other changes:
- There are no other changes in this release.
---
#### `wyatt_crud_bloc` - `v0.1.0`
- **REFACTOR**: add vscode config.
- **FIX**: update example.
- **BREAKING** **REFACTOR**: migrate to wyatt architecture.
## 2022-11-11
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`wyatt_authentication_bloc` - `v0.3.0+1`](#wyatt_authentication_bloc---v0301)
---
#### `wyatt_authentication_bloc` - `v0.3.0+1`
- **FIX**: fix some bugs to validate publishing.
## 2022-11-11 ## 2022-11-11
### Changes ### Changes
@@ -0,0 +1,2 @@
firebase_options.dart
.vscode
+7 -2
View File
@@ -1,4 +1,10 @@
{ {
// Dart
"dart.runPubGetOnPubspecChanges": "never",
// Gitea
"gitea.owner": "Wyatt-FOSS",
"gitea.repo": "wyatt-packages",
// Header
"psi-header.changes-tracking": { "psi-header.changes-tracking": {
"isActive": true "isActive": true
}, },
@@ -67,5 +73,4 @@
], ],
} }
], ],
"dart.runPubGetOnPubspecChanges": false, }
}
@@ -1,3 +1,19 @@
## 0.4.0+1
- **FIX**: add sort of mutex on onAuthChanges callback trigger.
## 0.4.0
> Note: This release has breaking changes.
- **REFACTOR**: update example with new input builders.
- **FIX**: add initial null account event in stream in the mock authentication.
- **BREAKING** **FIX**: add signInWithEmailAndPassword and signInAnonymously methods in SignInCubit #26.
## 0.3.0+1
- **FIX**: fix some bugs to validate publishing.
## 0.3.0 ## 0.3.0
> Note: This release has breaking changes. > Note: This release has breaking changes.
@@ -1 +1,4 @@
include: package:wyatt_analysis/analysis_options.flutter.yaml include: package:wyatt_analysis/analysis_options.flutter.yaml
analyzer:
exclude: "!example/**"
@@ -8,6 +8,7 @@
.buildlog/ .buildlog/
.history .history
.svn/ .svn/
migrate_working_dir/
# IntelliJ related # IntelliJ related
*.iml *.iml
@@ -45,4 +46,4 @@ app.*.map.json
/android/app/profile /android/app/profile
/android/app/release /android/app/release
lib/firebase_options.dart firebase_options.dart
@@ -1,10 +1,30 @@
# This file tracks properties of this Flutter project. # This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc. # Used by Flutter tool to assess capabilities and perform upgrades etc.
# #
# This file should be version controlled and should not be manually edited. # This file should be version controlled.
version: version:
revision: 5464c5bac742001448fe4fc0597be939379f88ea revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
channel: stable channel: stable
project_type: app project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
base_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
- platform: ios
create_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
base_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
@@ -1,4 +0,0 @@
## 1.0.1
- Update a dependency to the latest release.
@@ -1,4 +1,4 @@
# authentication_bloc_example # example_router
A new Flutter project. A new Flutter project.
@@ -8,9 +8,9 @@ This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project: A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter, view our For help getting started with Flutter development, view the
[online documentation](https://flutter.dev/docs), which offers tutorials, [online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference. samples, guidance on mobile development, and a full API reference.
@@ -30,6 +30,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion flutter.compileSdkVersion compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@@ -46,7 +47,9 @@ android {
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.authentication_bloc_example" applicationId "com.example.example_router"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion 21 minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
@@ -1,91 +0,0 @@
{
"project_info": {
"project_number": "136771801992",
"firebase_url": "https://tchat-beta.firebaseio.com",
"project_id": "tchat-beta",
"storage_bucket": "tchat-beta.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:136771801992:android:4ff419f07afdad6097203d",
"android_client_info": {
"package_name": "com.example.authentication_bloc_example"
}
},
"oauth_client": [
{
"client_id": "136771801992-nps7ic22shstvgdv0c2ojvbkj3dkntum.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.example.authentication_bloc_example",
"certificate_hash": "5d8790309e13b68c35e5d4d8437c35c6d15e6131"
}
},
{
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.example.example"
}
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:136771801992:android:8482c9b90bc29de697203d",
"android_client_info": {
"package_name": "com.example.crud_bloc_example"
}
},
"oauth_client": [
{
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.example.example"
}
}
]
}
}
}
],
"configuration_version": "1"
}
@@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.authentication_bloc_example"> package="com.example.example_router">
<!-- Flutter needs it to communicate with the running application <!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.authentication_bloc_example"> package="com.example.example_router">
<application <application
android:label="authentication_bloc_example" android:label="example_router"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
@@ -3,7 +3,7 @@
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
@@ -3,7 +3,7 @@
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
@@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.authentication_bloc_example"> package="com.example.example_router">
<!-- Flutter needs it to communicate with the running application <!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
@@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.0' classpath 'com.android.tools.build:gradle:7.1.2'
// START: FlutterFire Configuration // START: FlutterFire Configuration
classpath 'com.google.gms:google-services:4.3.10' classpath 'com.google.gms:google-services:4.3.10'
// END: FlutterFire Configuration // END: FlutterFire Configuration
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
@@ -1,182 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:developer';
import 'package:authentication_bloc_example/constants.dart';
import 'package:authentication_bloc_example/home/home_page.dart';
import 'package:authentication_bloc_example/login/login_page.dart';
import 'package:authentication_bloc_example/model.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
static FormData getNormalFormData() {
return const FormData([
FormInput(formFieldName, Name.pure()),
FormInput(formFieldPhone, Phone.pure()),
FormInput(formFieldPro, Boolean.pure()),
FormInput(
formFieldConfirmedPassword,
ConfirmedPassword.pure(),
metadata: FormInputMetadata(export: false),
),
]);
}
static FormData getProFormData() {
return const FormData([
FormInput(formFieldName, Name.pure()),
FormInput(formFieldPhone, Phone.pure()),
FormInput(formFieldPro, Boolean.pure()),
FormInput(formFieldSiren, Siren.pure()),
FormInput(formFieldIban, Iban.pure()),
FormInput(
formFieldConfirmedPassword,
ConfirmedPassword.pure(),
metadata: FormInputMetadata(export: false),
),
]);
}
// On Authentication Success. (Authenticated or Anonymous)
// User callback.
Future<Map<String, dynamic>> onAuthSuccess(UserInterface user) async {
if (user.isNotEmpty && !user.isAnonymous) {
// Check if user is register in Firesore.
DocumentSnapshot firestoreUser = await FirebaseFirestore.instance
.collection(firestoreCollectionUsers)
.doc(user.uid)
.get();
if (!firestoreUser.exists) {
// Register user in Firestore when sign in with social account.
final uid = user.uid;
final u = {'uid': uid, 'email': user.email};
await FirebaseFirestore.instance
.collection(firestoreCollectionUsers)
.doc(uid)
.set(u);
return {
'user': UserFirestore(
uid: uid,
email: user.email ?? '',
name: user.displayName ?? '',
phone: user.phoneNumber ?? ''),
};
} else {
return {
'user': UserFirestore.fromMap(
firestoreUser.data() as Map<String, dynamic>),
...firestoreUser.data() as Map<String, dynamic>? ?? {}
};
}
} else {
return {};
}
}
// On Sign Up Success.
Future<void> onSignUpSuccess(SignUpState state, String? uid) async {
if (uid != null) {
final data = state.data.toMap();
final user = {'uid': uid, 'email': state.email.value, ...data};
log('onSignUpSuccess: $user');
await FirebaseFirestore.instance
.collection(firestoreCollectionUsers)
.doc(uid)
.set(user);
}
}
@override
Widget build(BuildContext context) {
AuthenticationRepositoryInterface authenticationRepository =
AuthenticationRepositoryFirebase();
AuthenticationCubit authenticationCubit = AuthenticationCubit(
authenticationRepository: authenticationRepository,
onAuthSuccess: onAuthSuccess,
);
SignUpCubit signUpCubit = SignUpCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
entries: getNormalFormData(),
onSignUpSuccess: onSignUpSuccess,
);
SignInCubit signInCubit = SignInCubit(
authenticationRepository: authenticationRepository,
authenticationCubit: authenticationCubit,
);
return MultiRepositoryProvider(
providers: [
RepositoryProvider<AuthenticationRepositoryInterface>(
create: (context) => authenticationRepository,
),
],
child: MultiBlocProvider(
providers: [
BlocProvider<AuthenticationCubit>(
create: (context) => authenticationCubit..init(),
),
BlocProvider<SignUpCubit>(
create: (context) => signUpCubit,
),
BlocProvider<SignInCubit>(
create: (context) => signInCubit,
),
],
child: const AppView(),
),
);
}
}
class AppView extends StatelessWidget {
const AppView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
buttonTheme: const ButtonThemeData(
buttonColor: Colors.blue,
textTheme: ButtonTextTheme.primary,
),
),
home: AuthenticationBuilder(
unknown: (context) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
},
authenticated: (context, user, userData) => const HomePage(),
unauthenticated: (context) => const LoginPage(),
),
);
}
}
@@ -3,7 +3,7 @@
// ----- // -----
// File: bootstrap.dart // File: bootstrap.dart
// Created Date: 19/08/2022 15:05:17 // Created Date: 19/08/2022 15:05:17
// Last Modified: Wed Nov 09 2022 // Last Modified: Fri Nov 11 2022
// ----- // -----
// Copyright (c) 2022 // Copyright (c) 2022
@@ -11,8 +11,6 @@ import 'dart:async';
import 'package:example_router/core/dependency_injection/get_it.dart'; import 'package:example_router/core/dependency_injection/get_it.dart';
import 'package:example_router/core/utils/app_bloc_observer.dart'; import 'package:example_router/core/utils/app_bloc_observer.dart';
import 'package:example_router/firebase_options.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@@ -25,11 +23,6 @@ Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
FlutterError.onError = (details) { FlutterError.onError = (details) {
debugPrint(details.toString()); debugPrint(details.toString());
}; };
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await GetItInitializer.init(); await GetItInitializer.init();
runApp(await builder()); runApp(await builder());
@@ -1,44 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:authentication_bloc_example/forgot_password/widgets/forgot_password_form.dart';
import 'package:flutter/material.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ForgotPasswordPage extends StatelessWidget {
const ForgotPasswordPage({Key? key}) : super(key: key);
static Route route() {
return MaterialPageRoute<void>(builder: (_) => const ForgotPasswordPage());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Forgot Password')),
body: Padding(
padding: const EdgeInsets.all(8),
child: BlocProvider(
create: (_) => PasswordResetCubit(
context.read<AuthenticationRepositoryInterface>(),
),
child: const ForgotPasswordForm(),
),
),
);
}
}
@@ -1,92 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class _EmailInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<PasswordResetCubit, PasswordResetState>(
buildWhen: (previous, current) => previous.email != current.email,
builder: (context, state) {
return TextField(
onChanged: (email) =>
context.read<PasswordResetCubit>().emailChanged(email),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'email',
helperText: '',
errorText: state.email.invalid ? 'invalid email' : null,
),
);
},
);
}
}
class _ResetButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<PasswordResetCubit, PasswordResetState>(
buildWhen: (previous, current) => previous.status != current.status,
builder: (context, state) {
return state.status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: state.status.isValidated
? () => context
.read<PasswordResetCubit>()
.sendPasswordResetEmail()
: null,
child: const Text('SEND EMAIL'),
);
},
);
}
}
class ForgotPasswordForm extends StatelessWidget {
const ForgotPasswordForm({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocListener<PasswordResetCubit, PasswordResetState>(
listener: (context, state) {
if (state.status.isSubmissionSuccess) {
Navigator.of(context).pop();
} else if (state.status.isSubmissionFailure) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Text(state.errorMessage ?? 'Password reset Failure'),
),
);
}
},
child: Align(
alignment: const Alignment(0, -1 / 3),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [_EmailInput(), const SizedBox(height: 8), _ResetButton()],
),
),
);
}
}
@@ -1,65 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:authentication_bloc_example/home/widgets/infos.dart';
import 'package:flutter/material.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:authentication_bloc_example/home/widgets/email_verification.dart';
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
static Route route() {
return MaterialPageRoute<void>(builder: (_) => const HomePage());
}
@override
Widget build(BuildContext context) {
final user = context.select((AuthenticationCubit cubit) => cubit.state.user);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.exit_to_app),
onPressed: () => context
.read<AuthenticationCubit>()
.logOut(),
)
],
),
body: Padding(
padding: const EdgeInsets.all(8),
child: BlocProvider(
create: (_) => EmailVerificationCubit(
context.read<AuthenticationRepositoryInterface>(),
)..checkEmailVerification(),
child: BlocBuilder<EmailVerificationCubit, EmailVerificationState>(
builder: (context, state) {
if (state.isVerified || user!.isAnonymous) {
return const UserInfo();
} else {
return const EmailVerification();
}
},
),
),
),
);
}
}
@@ -1,53 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class EmailVerification extends StatelessWidget {
const EmailVerification({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final user = context.select((AuthenticationCubit cubit) => cubit.state.user);
final userData = context.select((AuthenticationCubit cubit) => cubit.state.userData);
return Align(
alignment: const Alignment(0, -1 / 3),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Hello ${userData!['name'] ?? 'null'}!"),
const SizedBox(height: 4),
Text("Email '${user?.email ?? 'null'}' is not verified"),
const SizedBox(height: 4),
ElevatedButton(
onPressed: () {
context.read<EmailVerificationCubit>().sendEmailVerification();
},
child: const Text('(Re)send email'),
),
ElevatedButton(
onPressed: () {
context.read<EmailVerificationCubit>().checkEmailVerification();
},
child: const Text('Refresh'),
),
],
),
);
}
}
@@ -1,58 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:authentication_bloc_example/home/widgets/avatar.dart';
import 'package:flutter/material.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:authentication_bloc_example/constants.dart';
class UserInfo extends StatelessWidget {
const UserInfo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final user = context.select((AuthenticationCubit cubit) => cubit.state.user);
final userData = context.select((AuthenticationCubit cubit) => cubit.state.userData);
return Align(
alignment: const Alignment(0, -1 / 3),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Avatar(photo: user?.photoURL),
const SizedBox(height: 4),
Text("Email: ${user?.email ?? 'null'}", style: textTheme.headline6),
const SizedBox(height: 4),
Text("Name: ${userData![formFieldName] ?? 'null'}",
style: textTheme.headline6),
const SizedBox(height: 4),
Text("Phone: ${userData[formFieldPhone] ?? 'null'}",
style: textTheme.headline6),
const SizedBox(height: 4),
Text("IsPro: ${userData[formFieldPro] ?? 'null'}",
style: textTheme.headline6),
const SizedBox(height: 4),
Text("IsAnonymous: ${user?.isAnonymous.toString() ?? 'null'}",
style: textTheme.headline6),
const SizedBox(height: 4),
Text("IsEmailVerified: ${user?.emailVerified.toString() ?? 'null'}",
style: textTheme.headline6),
],
),
);
}
}
@@ -1,214 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:authentication_bloc_example/app/app.dart';
import 'package:authentication_bloc_example/constants.dart';
import 'package:authentication_bloc_example/forgot_password/forgot_password_page.dart';
import 'package:authentication_bloc_example/sign_up/sign_up_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class _EmailInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
buildWhen: (previous, current) => previous.email != current.email,
builder: (context, state) {
return TextField(
onChanged: (email) => context.read<SignInCubit>().emailChanged(email),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'email',
helperText: '',
errorText: state.email.invalid ? 'invalid email' : null,
),
);
},
);
}
}
class _PasswordInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
buildWhen: (previous, current) => previous.password != current.password,
builder: (context, state) {
return TextField(
onChanged: (password) =>
context.read<SignInCubit>().passwordChanged(password),
obscureText: true,
decoration: InputDecoration(
labelText: 'password',
helperText: '',
errorText: state.password.invalid ? 'invalid password' : null,
),
);
},
);
}
}
class _LoginAnonButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
buildWhen: (previous, current) => previous.status != current.status,
builder: (context, state) {
return state.status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () =>
context.read<SignInCubit>().signInAnonymously(),
child: const Text('LOGIN ANONYMOUSLY'),
);
},
);
}
}
class _LoginWithPasswordButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
buildWhen: (previous, current) => previous.status != current.status,
builder: (context, state) {
return state.status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: state.status.isValidated
? () =>
context.read<SignInCubit>().signInWithEmailAndPassword()
: null,
child: const Text('LOGIN WITH PASSWORD'),
);
},
);
}
}
class _LoginWithGoogleButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
buildWhen: (previous, current) => previous.status != current.status,
builder: (context, state) {
return state.status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () =>
context.read<SignInCubit>().signInWithGoogle(),
child: const Text('LOGIN GOOGLE'),
);
},
);
}
}
class _SignUpButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return TextButton(
onPressed: () {
context.read<SignUpCubit>().updateFormData(App.getNormalFormData());
Navigator.of(context).push<void>(SignUpPage.route());
},
child: Text(
'CREATE ACCOUNT',
style: TextStyle(color: theme.primaryColor),
),
);
}
}
class _SignUpAsProButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return TextButton(
onPressed: () {
context.read<SignUpCubit>().updateFormData(App.getProFormData());
context.read<SignUpCubit>().dataChanged(
formFieldPro,
const Boolean.dirty(value: true),
);
Navigator.of(context).push<void>(SignUpPage.route());
},
child: Text(
'CREATE PRO ACCOUNT',
style: TextStyle(color: theme.primaryColor),
),
);
}
}
class LoginForm extends StatelessWidget {
const LoginForm({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocListener<SignInCubit, SignInState>(
listener: (context, state) {
if (state.status.isSubmissionFailure) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Text(state.errorMessage ?? 'Authentication Failure'),
),
);
}
},
child: Align(
alignment: const Alignment(0, -1 / 3),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 120),
_EmailInput(),
const SizedBox(height: 8),
_PasswordInput(),
const SizedBox(height: 8),
GestureDetector(
onTap: () {
Navigator.of(context).push(ForgotPasswordPage.route());
},
child: Text(
'Forgot password ?',
style: Theme.of(context).textTheme.bodyText2,
),
),
const SizedBox(height: 8),
_LoginWithPasswordButton(),
const SizedBox(height: 8),
_LoginAnonButton(),
const SizedBox(height: 8),
_LoginWithGoogleButton(),
const SizedBox(height: 8),
_SignUpButton(),
const SizedBox(height: 8),
_SignUpAsProButton(),
],
),
),
),
);
}
}
@@ -1,35 +1,6 @@
// Copyright (C) 2022 WYATT GROUP import 'package:example_router/bootstrap.dart';
// Please see the AUTHORS file for details. import 'package:example_router/presentation/features/app/app.dart';
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:authentication_bloc_example/app/app.dart'; void main() {
import 'package:authentication_bloc_example/app/bloc_observer.dart'; bootstrap(App.new);
import 'package:authentication_bloc_example/firebase_options.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
BlocOverrides.runZoned(
() => runApp(
const App(),
),
blocObserver: AppBlocObserver(),
);
} }
@@ -3,7 +3,7 @@
// ----- // -----
// File: sign_in_form.dart // File: sign_in_form.dart
// Created Date: 19/08/2022 15:24:37 // Created Date: 19/08/2022 15:24:37
// Last Modified: Fri Nov 11 2022 // Last Modified: Wed Nov 16 2022
// ----- // -----
// Copyright (c) 2022 // Copyright (c) 2022
@@ -16,14 +16,14 @@ class _EmailInput extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InputBuilder<SignInCubit<int>>( return InputBuilder<SignInCubit<int>>(
field: AuthFormField.email, field: AuthFormField.email,
builder: ((context, cubit, state, field, inputValid) { builder: ((context, cubit, state, field, input) {
return TextField( return TextField(
onChanged: (email) => cubit.emailChanged(email), onChanged: (email) => cubit.emailChanged(email),
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Email', labelText: 'Email',
helperText: '', helperText: '',
errorText: !inputValid ? 'Invalid email' : null, errorText: input.validator.invalid ? 'Invalid email' : null,
), ),
); );
}), }),
@@ -36,14 +36,14 @@ class _PasswordInput extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InputBuilder<SignInCubit<int>>( return InputBuilder<SignInCubit<int>>(
field: AuthFormField.password, field: AuthFormField.password,
builder: ((context, cubit, state, field, inputValid) { builder: ((context, cubit, state, field, input) {
return TextField( return TextField(
onChanged: (pwd) => cubit.passwordChanged(pwd), onChanged: (pwd) => cubit.passwordChanged(pwd),
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Password', labelText: 'Password',
helperText: '', helperText: '',
errorText: !inputValid ? 'Invalid password' : null, errorText: input.validator.invalid ? 'Invalid password' : null,
), ),
); );
}), }),
@@ -59,8 +59,24 @@ class _SignInButton extends StatelessWidget {
return status.isSubmissionInProgress return status.isSubmissionInProgress
? const CircularProgressIndicator() ? const CircularProgressIndicator()
: ElevatedButton( : ElevatedButton(
onPressed: status.isValidated ? () => cubit.submit() : null, onPressed: status.isValidated ? () => cubit.signInWithEmailAndPassword() : null,
child: const Text('Sign in'), child: const Text('Sign in with credentials'),
);
}),
);
}
}
class _SignInAnonymouslyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SubmitBuilder<SignInCubit<int>>(
builder: ((context, cubit, status) {
return status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () => cubit.signInAnonymously(),
child: const Text('Sign in anonymously'),
); );
}), }),
); );
@@ -80,12 +96,15 @@ class SignInForm extends StatelessWidget {
), ),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
_EmailInput(), _EmailInput(),
const SizedBox(height: 8), const SizedBox(height: 8),
_PasswordInput(), _PasswordInput(),
const SizedBox(height: 16), const SizedBox(height: 16),
_SignInButton(), _SignInButton(),
const SizedBox(height: 16),
_SignInAnonymouslyButton(),
], ],
), ),
), ),
@@ -3,7 +3,7 @@
// ----- // -----
// File: sign_up_form.dart // File: sign_up_form.dart
// Created Date: 19/08/2022 14:41:08 // Created Date: 19/08/2022 14:41:08
// Last Modified: Fri Nov 11 2022 // Last Modified: Wed Nov 16 2022
// ----- // -----
// Copyright (c) 2022 // Copyright (c) 2022
@@ -17,14 +17,14 @@ class _EmailInput extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InputBuilder<SignUpCubit<int>>( return InputBuilder<SignUpCubit<int>>(
field: AuthFormField.email, field: AuthFormField.email,
builder: ((context, cubit, state, field, inputValid) { builder: ((context, cubit, state, field, input) {
return TextField( return TextField(
onChanged: (email) => cubit.emailChanged(email), onChanged: (email) => cubit.emailChanged(email),
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Email', labelText: 'Email',
helperText: '', helperText: '',
errorText: !inputValid ? 'Invalid email' : null, errorText: input.validator.invalid ? 'Invalid email' : null,
), ),
); );
}), }),
@@ -37,7 +37,7 @@ class _PasswordInput extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InputBuilder<SignUpCubit<int>>( return InputBuilder<SignUpCubit<int>>(
field: AuthFormField.password, field: AuthFormField.password,
builder: ((context, cubit, state, field, inputValid) { builder: ((context, cubit, state, field, input) {
return TextField( return TextField(
onChanged: (pwd) { onChanged: (pwd) {
cubit.passwordChanged(pwd); cubit.passwordChanged(pwd);
@@ -52,7 +52,7 @@ class _PasswordInput extends StatelessWidget {
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Password', labelText: 'Password',
helperText: '', helperText: '',
errorText: !inputValid ? 'Invalid password' : null, errorText: input.validator.invalid ? 'Invalid password' : null,
), ),
); );
}), }),
@@ -65,7 +65,7 @@ class _ConfirmPasswordInput extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InputBuilder<SignUpCubit<int>>( return InputBuilder<SignUpCubit<int>>(
field: AppFormField.confirmedPassword, field: AppFormField.confirmedPassword,
builder: ((context, cubit, state, field, inputValid) { builder: ((context, cubit, state, field, input) {
return TextField( return TextField(
onChanged: (pwd) { onChanged: (pwd) {
cubit.dataChanged( cubit.dataChanged(
@@ -80,7 +80,8 @@ class _ConfirmPasswordInput extends StatelessWidget {
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Confirm password', labelText: 'Confirm password',
helperText: '', helperText: '',
errorText: !inputValid ? 'Passwords do not match' : null, errorText:
input.validator.invalid ? 'Passwords do not match' : null,
), ),
); );
}), }),
@@ -1,325 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:developer';
import 'package:authentication_bloc_example/app/app.dart';
import 'package:authentication_bloc_example/constants.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class _NameInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (name) => context
.read<SignUpCubit>()
.dataChanged(formFieldName, Name.dirty(name)),
keyboardType: TextInputType.name,
decoration: InputDecoration(
labelText: 'name',
helperText: '',
errorText:
state.data.isNotValid(formFieldName) ? 'invalid name' : null,
),
);
},
);
}
}
class _PhoneInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (phone) => context
.read<SignUpCubit>()
.dataChanged(formFieldPhone, Phone.dirty(phone)),
keyboardType: TextInputType.phone,
decoration: InputDecoration(
labelText: 'phone',
helperText: '',
errorText: state.data.isNotValid(formFieldPhone)
? 'invalid phone'
: null,
),
);
},
);
}
}
class _SirenInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (siren) => context
.read<SignUpCubit>()
.dataChanged(formFieldSiren, Siren.dirty(siren)),
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'siren',
helperText: '',
errorText: state.data.isNotValid(formFieldSiren)
? 'invalid SIREN'
: null,
),
);
},
);
}
}
class _IbanInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (iban) => context
.read<SignUpCubit>()
.dataChanged(formFieldIban, Iban.dirty(iban)),
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: 'iban',
helperText: '',
errorText:
state.data.isNotValid(formFieldIban) ? 'invalid IBAN' : null,
),
);
},
);
}
}
class _EmailInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
buildWhen: (previous, current) => previous.email != current.email,
builder: (context, state) {
return TextField(
onChanged: (email) => context.read<SignUpCubit>().emailChanged(email),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'email',
helperText: '',
errorText: state.email.invalid ? 'invalid email' : null,
),
);
},
);
}
}
class _PasswordInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (password) {
context.read<SignUpCubit>().passwordChanged(password);
context.read<SignUpCubit>().dataChanged(
formFieldConfirmedPassword,
ConfirmedPassword.dirty(
password: password,
value: context
.read<SignUpCubit>()
.state
.data
.valueOf<String>(formFieldConfirmedPassword),
),
);
},
obscureText: true,
decoration: InputDecoration(
labelText: 'password',
helperText: '',
errorText: state.password.invalid ? 'invalid password' : null,
),
);
},
);
}
}
class _ConfirmPasswordInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (confirmPassword) => context
.read<SignUpCubit>()
.dataChanged(
formFieldConfirmedPassword,
ConfirmedPassword.dirty(
password: context.read<SignUpCubit>().state.password.value,
value: confirmPassword,
),
),
obscureText: true,
decoration: InputDecoration(
labelText: 'confirm password',
helperText: '',
errorText: state.data.isNotValid(formFieldConfirmedPassword)
? 'passwords do not match'
: null,
),
);
},
);
}
}
class _CheckIsProInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListTile(
title: const Text('Are you a pro?'),
trailing: BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return Checkbox(
value: state.data.valueOf<bool>(formFieldPro),
onChanged: (isPro) {
final value =
isPro!; // tristate is false, so value can't be null
context.read<SignUpCubit>().dataChanged(
formFieldPro,
Boolean.dirty(value: value),
);
if (value) {
context.read<SignUpCubit>().updateFormData(
App.getProFormData(),
operation: SetOperation.union);
} else {
context.read<SignUpCubit>().updateFormData(
App.getNormalFormData(),
operation: SetOperation.intersection);
}
});
},
),
);
}
}
class _SignUpButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
buildWhen: (previous, current) => previous.status != current.status,
builder: (context, state) {
return state.status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: state.status.isValidated
? () => context.read<SignUpCubit>().signUpFormSubmitted()
: null,
child: const Text('SIGN UP'),
);
},
);
}
}
class _DebugButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return ElevatedButton(
onPressed: () {
// log(state.toString());
log(state.data.toMap().toString());
},
child: const Text('DEBUG'),
);
},
);
}
}
class SignUpForm extends StatelessWidget {
const SignUpForm({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocListener<SignUpCubit, SignUpState>(
listener: (context, state) {
if (state.status.isSubmissionSuccess) {
Navigator.of(context).pop();
} else if (state.status.isSubmissionFailure) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'Sign Up Failure')),
);
}
},
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Align(
alignment: const Alignment(0, -1 / 3),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_NameInput(),
const SizedBox(height: 8),
_PhoneInput(),
const SizedBox(height: 8),
_CheckIsProInput(),
const SizedBox(height: 8),
BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
if (state.data.valueOf<bool>(formFieldPro)) {
return Column(children: [
_SirenInput(),
const SizedBox(height: 8),
_IbanInput(),
const SizedBox(height: 8),
]);
}
return const SizedBox.shrink();
},
),
_EmailInput(),
const SizedBox(height: 8),
_PasswordInput(),
const SizedBox(height: 8),
_ConfirmPasswordInput(),
const SizedBox(height: 8),
_SignUpButton(),
const SizedBox(height: 8),
_DebugButton(),
],
),
),
),
);
}
}
@@ -1,4 +1,4 @@
name: authentication_bloc_example name: example_router
description: A new Flutter project. description: A new Flutter project.
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
@@ -15,10 +15,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.1 version: 1.0.0+1
environment: environment:
sdk: ">=2.16.2 <3.0.0" sdk: ">=2.17.6 <3.0.0"
# Dependencies specify other packages that your package needs in order to work. # Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions # To automatically upgrade your package dependencies to the latest versions
@@ -29,22 +29,30 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
go_router: ^5.1.5
firebase_core: ^2.1.1
flutter_bloc: ^8.1.1
get_it: ^7.2.0
firebase_core: ^1.14.1
flutter_bloc: ^8.0.1
wyatt_authentication_bloc: wyatt_authentication_bloc:
path: "../" path: "../"
wyatt_form_bloc: wyatt_form_bloc:
git: hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages version: 0.2.0
ref: wyatt_form_bloc-v0.1.0+1
path: packages/wyatt_form_bloc wyatt_architecture:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.0.2
wyatt_type_utils:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.0.3+1
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.4 cupertino_icons: ^1.0.5
cloud_firestore: ^3.1.12
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -60,7 +68,7 @@ dev_dependencies:
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter. # The following section is specific to Flutter packages.
flutter: flutter:
# The following line ensures that the Material Icons font is # The following line ensures that the Material Icons font is
@@ -74,7 +82,7 @@ flutter:
# - images/a_dot_ham.jpeg # - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware. # https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see # For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages # https://flutter.dev/assets-and-images/#from-packages
@@ -1,66 +0,0 @@
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for web - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for android - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for macos - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.windows:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for windows - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyCDbbhjbFrQwLXuIANdJzjkDk8uOETnn7w',
appId: '1:136771801992:ios:bcdca68d2b7d227097203d',
messagingSenderId: '136771801992',
projectId: 'tchat-beta',
databaseURL: 'https://tchat-beta.firebaseio.com',
storageBucket: 'tchat-beta.appspot.com',
androidClientId: '136771801992-n2pq8oqutvrqj58e05hbavvc7n1jdfjb.apps.googleusercontent.com',
iosClientId: '136771801992-p629tpo9bk3hcm2955s5ahivdla57ln9.apps.googleusercontent.com',
iosBundleId: 'com.example.exampleRouter',
);
}
@@ -1,6 +0,0 @@
import 'package:example_router/bootstrap.dart';
import 'package:example_router/presentation/features/app/app.dart';
void main() {
bootstrap(App.new);
}
@@ -23,7 +23,8 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource {
Pair<Account, String>? _connectedMock; Pair<Account, String>? _connectedMock;
Pair<Account, String>? _registeredMock; Pair<Account, String>? _registeredMock;
final StreamController<Account?> _streamAccount = StreamController(); final StreamController<Account?> _streamAccount = StreamController()
..add(null);
final List<Pair<Account, String>>? registeredAccounts; final List<Pair<Account, String>>? registeredAccounts;
final String idToken; final String idToken;
@@ -44,6 +44,8 @@ class AuthenticationRepositoryImpl<T extends Object>
final OnAuthChange<T>? _onAccountChanges; final OnAuthChange<T>? _onAccountChanges;
bool _pause = false; // Semaphore
AuthenticationRepositoryImpl({ AuthenticationRepositoryImpl({
required AuthenticationCacheDataSource<T> authenticationCacheDataSource, required AuthenticationCacheDataSource<T> authenticationCacheDataSource,
required AuthenticationRemoteDataSource authenticationRemoteDataSource, required AuthenticationRemoteDataSource authenticationRemoteDataSource,
@@ -120,6 +122,7 @@ class AuthenticationRepositoryImpl<T extends Object>
}) => }) =>
Result.tryCatchAsync<Account, AppException, AppException>( Result.tryCatchAsync<Account, AppException, AppException>(
() async { () async {
_pause = true;
final account = await _authenticationRemoteDataSource.signUp( final account = await _authenticationRemoteDataSource.signUp(
email: email, email: email,
password: password, password: password,
@@ -135,9 +138,13 @@ class AuthenticationRepositoryImpl<T extends Object>
(error) async => error, (error) async => error,
); );
} }
_pause = false;
return account; return account;
}, },
(error) => error, (error) {
_pause = false;
return error;
},
); );
@override @override
@@ -200,7 +207,7 @@ class AuthenticationRepositoryImpl<T extends Object>
@override @override
Stream<FutureResult<AccountWrapper<T>>> streamAccount() => Stream<FutureResult<AccountWrapper<T>>> streamAccount() =>
_authenticationRemoteDataSource.streamAccount().map((account) async { _authenticationRemoteDataSource.streamAccount().map((account) async {
if (_onAccountChanges.isNotNull) { if (_onAccountChanges.isNotNull && !_pause) {
final dataResult = await _onAccountChanges!.call(account); final dataResult = await _onAccountChanges!.call(account);
return dataResult.map((data) => AccountWrapperModel(account, data)); return dataResult.map((data) => AccountWrapperModel(account, data));
} }
@@ -16,9 +16,7 @@
import 'dart:async'; import 'dart:async';
import 'package:wyatt_authentication_bloc/src/core/constants/form_field.dart'; import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_authentication_bloc/src/core/constants/form_name.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
@@ -82,6 +80,41 @@ class SignInCubit<Extra> extends FormDataCubit<SignInState> {
@override @override
FutureOr<void> submit() async { FutureOr<void> submit() async {
throw UnimplementedError('`submit()` is not implemented for SignInCubit, '
'please use `signInWithEmailAndPassword()` or `signInAnonymously()`');
}
@override
FutureOr<void> update(
WyattForm form, {
SetOperation operation = SetOperation.replace,
}) {
final WyattForm current = _formRepository.accessForm(formName).clone();
final WyattForm newForm = operation.operation.call(current, form);
_formRepository.updateForm(newForm);
emit(
state.copyWith(
form: newForm,
status: newForm.validate(),
),
);
}
@override
FutureOr<void> validate() {
emit(
state.copyWith(
status: _formRepository.accessForm(formName).validate(),
),
);
}
FutureOr<void> signInWithEmailAndPassword() async {
if (state.status.isSubmissionInProgress) {
return;
}
if (!state.status.isValidated) { if (!state.status.isValidated) {
return; return;
} }
@@ -117,28 +150,22 @@ class SignInCubit<Extra> extends FormDataCubit<SignInState> {
); );
} }
@override FutureOr<void> signInAnonymously() async {
FutureOr<void> update( if (state.status.isSubmissionInProgress) {
WyattForm form, { return;
SetOperation operation = SetOperation.replace, }
}) {
final WyattForm current = _formRepository.accessForm(formName).clone(); emit(state.copyWith(status: FormStatus.submissionInProgress));
final WyattForm newForm = operation.operation.call(current, form);
_formRepository.updateForm(newForm); final uid = await _authenticationRepository.signInAnonymously();
emit( emit(
state.copyWith( uid.fold(
form: newForm, (value) => state.copyWith(status: FormStatus.submissionSuccess),
status: newForm.validate(), (error) => state.copyWith(
), errorMessage: error.message,
); status: FormStatus.submissionFailure,
} ),
@override
FutureOr<void> validate() {
emit(
state.copyWith(
status: _formRepository.accessForm(formName).validate(),
), ),
); );
} }
@@ -1,7 +1,7 @@
name: wyatt_authentication_bloc name: wyatt_authentication_bloc
description: Authentication BLoC for Flutter description: Authentication BLoC for Flutter
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_authentication_bloc repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_authentication_bloc
version: 0.3.0 version: 0.4.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
@@ -24,15 +24,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.1.0+1 version: ^0.2.0
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.0.2
wyatt_type_utils: wyatt_type_utils:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.0.3+1 version: ^0.0.3+1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -65,6 +65,10 @@ void main() {
), ),
).thenAnswer((_) async => Ok(account)); ).thenAnswer((_) async => Ok(account));
when(
() => authenticationRepository.signInAnonymously(),
).thenAnswer((_) async => Ok(account));
when( when(
() => authenticationRepository.formRepository, () => authenticationRepository.formRepository,
).thenAnswer((_) => formRepository); ).thenAnswer((_) => formRepository);
@@ -258,13 +262,38 @@ void main() {
); );
}); });
group('submit', () { group('signInWithEmailAndPassword()', () {
blocTest<SignInCubit<int>, SignInState>( blocTest<SignInCubit<int>, SignInState>(
'does nothing when status is not validated', 'does nothing when status is not validated',
build: () => SignInCubit( build: () => SignInCubit(
authenticationRepository: authenticationRepository, authenticationRepository: authenticationRepository,
), ),
act: (cubit) => cubit.submit(), act: (cubit) => cubit.signInWithEmailAndPassword(),
expect: () => const <SignInState>[],
);
blocTest<SignInCubit<int>, SignInState>(
'does nothing when status is submitInProgress',
build: () => SignInCubit(
authenticationRepository: authenticationRepository,
),
seed: () => SignInState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.submissionInProgress,
),
act: (cubit) => cubit.signInWithEmailAndPassword(),
expect: () => const <SignInState>[], expect: () => const <SignInState>[],
); );
@@ -308,7 +337,7 @@ void main() {
), ),
status: FormStatus.valid, status: FormStatus.valid,
), ),
act: (cubit) => cubit.submit(), act: (cubit) => cubit.signInWithEmailAndPassword(),
verify: (_) { verify: (_) {
verify( verify(
() => authenticationRepository.signInWithEmailAndPassword( () => authenticationRepository.signInWithEmailAndPassword(
@@ -360,7 +389,7 @@ void main() {
), ),
status: FormStatus.valid, status: FormStatus.valid,
), ),
act: (cubit) => cubit.submit(), act: (cubit) => cubit.signInWithEmailAndPassword(),
expect: () => <SignInState>[ expect: () => <SignInState>[
SignInState( SignInState(
form: WyattFormImpl( form: WyattFormImpl(
@@ -444,7 +473,170 @@ void main() {
), ),
status: FormStatus.valid, status: FormStatus.valid,
), ),
act: (cubit) => cubit.submit(), act: (cubit) => cubit.signInWithEmailAndPassword(),
expect: () => <SignInState>[
SignInState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.submissionInProgress,
),
SignInState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.submissionFailure,
)
],
);
});
group('signInAnonymously()', () {
blocTest<SignInCubit<int>, SignInState>(
'does nothing when status is submitInProgress',
build: () => SignInCubit(
authenticationRepository: authenticationRepository,
),
seed: () => SignInState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.submissionInProgress,
),
act: (cubit) => cubit.signInAnonymously(),
expect: () => const <SignInState>[],
);
blocTest<SignInCubit<int>, SignInState>(
'calls signInAnonymously',
build: () => SignInCubit(
authenticationRepository: authenticationRepository,
),
act: (cubit) => cubit.signInAnonymously(),
verify: (_) {
verify(
() => authenticationRepository.signInAnonymously(),
).called(1);
},
);
blocTest<SignInCubit<int>, SignInState>(
'emits [submissionInProgress, submissionSuccess] '
'when signInAnonymously succeeds',
build: () => SignInCubit(
authenticationRepository: authenticationRepository,
),
seed: () => SignInState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.valid,
),
act: (cubit) => cubit.signInAnonymously(),
expect: () => <SignInState>[
SignInState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.submissionInProgress,
),
SignInState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.submissionSuccess,
)
],
);
blocTest<SignInCubit<int>, SignInState>(
'emits [submissionInProgress, submissionFailure] '
'when signInAnonymously fails',
setUp: () {
when(
() => authenticationRepository.signInAnonymously(),
).thenAnswer((_) async => Err(ServerException()));
},
build: () => SignInCubit(
authenticationRepository: authenticationRepository,
),
seed: () => SignInState(
form: WyattFormImpl(
[
FormInput(
AuthFormField.email,
const Email.dirty(validEmailString),
),
FormInput(
AuthFormField.password,
const Password.dirty(validPasswordString),
)
],
name: AuthFormName.signInForm,
),
status: FormStatus.valid,
),
act: (cubit) => cubit.signInAnonymously(),
expect: () => <SignInState>[ expect: () => <SignInState>[
SignInState( SignInState(
form: WyattFormImpl( form: WyattFormImpl(
@@ -481,5 +481,178 @@ void main() {
], ],
); );
}); });
group('update', () {
final inputsA = <
FormInput<dynamic, FormInputValidator<dynamic, ValidationError>,
dynamic>>[
FormInput(AuthFormField.email, const Email.pure()),
FormInput(AuthFormField.password, const Password.pure())
];
final inputsB = <
FormInput<dynamic, FormInputValidator<dynamic, ValidationError>,
dynamic>>[
FormInput('name', const Name.pure()),
FormInput('phone', const Phone.pure()),
];
final inputsC = <
FormInput<dynamic, FormInputValidator<dynamic, ValidationError>,
dynamic>>[
FormInput(AuthFormField.email, const Email.pure()),
FormInput(AuthFormField.password, const Password.pure()),
FormInput('name', const Name.pure()),
FormInput('phone', const Phone.pure()),
];
blocTest<SignUpCubit<int>, SignUpState>(
'emits no state when apply intersection with the same form',
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
),
seed: () => SignUpState(
form: WyattFormImpl(
inputsA,
name: AuthFormName.signUpForm,
),
),
act: (cubit) => cubit.update(
WyattFormImpl(inputsA, name: 'otherSignUpForm'),
operation: SetOperation.intersection,
),
expect: () => <SignUpState>[],
);
blocTest<SignUpCubit<int>, SignUpState>(
'emits no state when apply intersection with a form that '
'contains every inputs of actual form',
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
),
seed: () => SignUpState(
form: WyattFormImpl(
inputsA,
name: AuthFormName.signUpForm,
),
),
act: (cubit) => cubit.update(
WyattFormImpl(inputsC, name: 'otherSignUpForm'),
operation: SetOperation.intersection,
),
expect: () => <SignUpState>[],
);
blocTest<SignUpCubit<int>, SignUpState>(
'emits the form intersection when apply '
'intersection with a smaller form',
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
),
seed: () => SignUpState(
form: WyattFormImpl(
inputsC,
name: AuthFormName.signUpForm,
),
),
act: (cubit) => cubit.update(
WyattFormImpl(inputsA, name: 'otherSignUpForm'),
operation: SetOperation.intersection,
),
expect: () => <SignUpState>[
SignUpState(
form: WyattFormImpl(
inputsA,
name: AuthFormName.signUpForm,
),
),
],
);
blocTest<SignUpCubit<int>, SignUpState>(
'emits no state when apply union with the same form',
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
),
seed: () => SignUpState(
form: WyattFormImpl(
inputsA,
name: AuthFormName.signUpForm,
),
),
act: (cubit) => cubit.update(
WyattFormImpl(inputsA, name: 'otherSignUpForm'),
operation: SetOperation.union,
),
expect: () => <SignUpState>[],
);
blocTest<SignUpCubit<int>, SignUpState>(
'emits the form union when apply '
'union with an another form',
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
),
seed: () => SignUpState(
form: WyattFormImpl(
inputsA,
name: AuthFormName.signUpForm,
),
),
act: (cubit) => cubit.update(
WyattFormImpl(inputsB, name: 'otherSignUpForm'),
operation: SetOperation.union,
),
expect: () => <SignUpState>[
SignUpState(
form: WyattFormImpl(
inputsC,
name: AuthFormName.signUpForm,
),
),
],
);
blocTest<SignUpCubit<int>, SignUpState>(
'emits no state when apply replace with the same form',
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
),
seed: () => SignUpState(
form: WyattFormImpl(
inputsA,
name: AuthFormName.signUpForm,
),
),
act: (cubit) => cubit.update(
WyattFormImpl(inputsA, name: AuthFormName.signUpForm),
),
expect: () => <SignUpState>[],
);
blocTest<SignUpCubit<int>, SignUpState>(
'emits a state with a new form after the replacement',
build: () => SignUpCubit(
authenticationRepository: authenticationRepository,
),
seed: () => SignUpState(
form: WyattFormImpl(
inputsA,
name: AuthFormName.signUpForm,
),
),
act: (cubit) => cubit.update(
WyattFormImpl(inputsC, name: AuthFormName.signUpForm),
),
expect: () => <SignUpState>[
SignUpState(
form: WyattFormImpl(
inputsC,
name: AuthFormName.signUpForm,
),
),
],
);
});
}); });
} }
+71
View File
@@ -0,0 +1,71 @@
{
"psi-header.changes-tracking": {
"isActive": true
},
"psi-header.config": {
"blankLinesAfter": 1,
"forceToTop": true
},
"psi-header.lang-config": [
{
"beforeHeader": [
"# -*- coding:utf-8 -*-",
"#!/usr/bin/env python3"
],
"begin": "###",
"end": "###",
"language": "python",
"prefix": "# "
},
{
"beforeHeader": [
"#!/usr/bin/env sh",
""
],
"language": "shellscript",
"begin": "",
"end": "",
"prefix": "# "
},
{
"begin": "",
"end": "",
"language": "dart",
"prefix": "// "
},
{
"begin": "",
"end": "",
"language": "yaml",
"prefix": "# "
},
{
"begin": "<!--",
"end": "-->",
"language": "markdown",
},
],
"psi-header.templates": [
{
"language": "*",
"template": [
"Copyright (C) <<year>> WYATT GROUP",
"Please see the AUTHORS file for details.",
"",
"This program is free software: you can redistribute it and/or modify",
"it under the terms of the GNU General Public License as published by",
"the Free Software Foundation, either version 3 of the License, or",
"any later version.",
"",
"This program is distributed in the hope that it will be useful,",
"but WITHOUT ANY WARRANTY; without even the implied warranty of",
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
"GNU General Public License for more details.",
"",
"You should have received a copy of the GNU General Public License",
"along with this program. If not, see <https://www.gnu.org/licenses/>."
],
}
],
"dart.runPubGetOnPubspecChanges": false,
}
+8
View File
@@ -1,3 +1,11 @@
## 0.1.0
> Note: This release has breaking changes.
- **REFACTOR**: add vscode config.
- **FIX**: update example.
- **BREAKING** **REFACTOR**: migrate to wyatt architecture.
## 0.0.2 ## 0.0.2
- Migrate crud_package in monorepo - Migrate crud_package in monorepo
@@ -1 +1,4 @@
include: package:wyatt_analysis/analysis_options.flutter.yaml include: package:wyatt_analysis/analysis_options.flutter.yaml
analyzer:
exclude: "!example/**"
+22 -2
View File
@@ -1,10 +1,30 @@
# This file tracks properties of this Flutter project. # This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc. # Used by Flutter tool to assess capabilities and perform upgrades etc.
# #
# This file should be version controlled and should not be manually edited. # This file should be version controlled.
version: version:
revision: 5464c5bac742001448fe4fc0597be939379f88ea revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
channel: stable channel: stable
project_type: app project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: ios
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

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