Compare commits

..
77 changed files with 2087 additions and 130 deletions
@@ -1,16 +1,6 @@
# example_router
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
```sh
firebase emulators:start --only auth
flutter run --target lib/main_development.dart --dart-define="dev_mode=emulator"
```
@@ -10,11 +10,11 @@ PODS:
- FirebaseAuth (~> 10.3.0)
- Firebase/CoreOnly (10.3.0):
- FirebaseCore (= 10.3.0)
- firebase_auth (4.2.0):
- firebase_auth (4.2.9):
- Firebase/Auth (= 10.3.0)
- firebase_core
- Flutter
- firebase_core (2.4.0):
- firebase_core (2.7.0):
- Firebase/CoreOnly (= 10.3.0)
- Flutter
- FirebaseAuth (10.3.0):
@@ -29,6 +29,8 @@ PODS:
- FirebaseCoreInternal (10.3.0):
- "GoogleUtilities/NSData+zlib (~> 7.8)"
- Flutter (1.0.0)
- flutter_secure_storage (6.0.0):
- Flutter
- google_sign_in_ios (0.0.1):
- Flutter
- GoogleSignIn (~> 6.2)
@@ -61,6 +63,7 @@ DEPENDENCIES:
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/ios`)
SPEC REPOS:
@@ -83,19 +86,22 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/firebase_core/ios"
Flutter:
:path: Flutter
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
google_sign_in_ios:
:path: ".symlinks/plugins/google_sign_in_ios/ios"
SPEC CHECKSUMS:
AppAuth: 8fca6b5563a5baef2c04bee27538025e4ceb2add
Firebase: f92fc551ead69c94168d36c2b26188263860acd9
firebase_auth: 579a0dc15451491cc83fccaa5102296635f24938
firebase_core: 6f2f753e316765799d88568232ed59e300ff53db
firebase_auth: 4e8c693e848ed13b263de2d702d55fa82ed04a79
firebase_core: 128d8c43c3a453a4a67463314fc3761bedff860b
FirebaseAuth: 0e415d29d846c1dce2fb641e46f35e9888d9bec6
FirebaseCore: 988754646ab3bd4bdcb740f1bfe26b9f6c0d5f2a
FirebaseCoreInternal: 29b76f784d607df8b2a1259d73c3f04f1210137b
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
google_sign_in_ios: 4f85eb9f937450765c8573bb85fd8cd6a5af675c
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
google_sign_in_ios: 1256ff9d941db546373826966720b0c24804bcdd
GoogleSignIn: 5651ce3a61e56ca864160e79b484cd9ed3f49b7a
GoogleUtilities: bad72cb363809015b1f7f19beb1f1cd23c589f95
GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd
@@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
44F5B6790A35D9BA26574F6B /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F6E17622FCECE9BFC2543397 /* GoogleService-Info.plist */; };
69F3BBCD5DEB05A456F6B74F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B0A061B2E527F311149C3581 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@@ -46,6 +47,7 @@
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B0A061B2E527F311149C3581 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F0D7945BAE0BEA457137ED73 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
F6E17622FCECE9BFC2543397 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
F9340E3A859C31E59380BD0F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -91,6 +93,7 @@
97C146EF1CF9000F007C117D /* Products */,
66B357379C2757D2844F12BB /* Pods */,
BC1E25CE0DADDF7B7201CCF8 /* Frameworks */,
F6E17622FCECE9BFC2543397 /* GoogleService-Info.plist */,
);
sourceTree = "<group>";
};
@@ -192,6 +195,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
44F5B6790A35D9BA26574F6B /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>136771801992-p629tpo9bk3hcm2955s5ahivdla57ln9.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.136771801992-p629tpo9bk3hcm2955s5ahivdla57ln9</string>
<key>ANDROID_CLIENT_ID</key>
<string>136771801992-n2pq8oqutvrqj58e05hbavvc7n1jdfjb.apps.googleusercontent.com</string>
<key>API_KEY</key>
<string>AIzaSyCDbbhjbFrQwLXuIANdJzjkDk8uOETnn7w</string>
<key>GCM_SENDER_ID</key>
<string>136771801992</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.example.exampleRouter</string>
<key>PROJECT_ID</key>
<string>tchat-beta</string>
<key>STORAGE_BUCKET</key>
<string>tchat-beta.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:136771801992:ios:bcdca68d2b7d227097203d</string>
<key>DATABASE_URL</key>
<string>https://tchat-beta.firebaseio.com</string>
</dict>
</plist>
@@ -18,11 +18,8 @@ import 'dart:async';
import 'package:example_router/core/dependency_injection/get_it.dart';
import 'package:example_router/core/utils/app_bloc_observer.dart';
import 'package:example_router/firebase_options.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
class MockSettings {
static MockSettings? _instance;
@@ -70,12 +67,6 @@ Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
debugPrint(details.toString());
};
if (MockSettings.isDisable()) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
}
await GetItInitializer.init();
runApp(await builder());
@@ -14,20 +14,81 @@
// 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:example_router/core/enums/dev_mode.dart';
import 'package:example_router/core/flavors/flavor.dart';
import 'package:example_router/firebase_options.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:get_it/get_it.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
final getIt = GetIt.I;
/// Service and Data Source locator
abstract class GetItInitializer {
static Future<void> init() async {
getIt.registerLazySingleton<AuthenticationRemoteDataSource<int>>(
() => AuthenticationFirebaseDataSourceImpl<int>(
firebaseAuth: FirebaseAuth.instance,
googleSignIn:
GoogleSignIn(clientId: DefaultFirebaseOptions.ios.iosClientId)),
static FutureOr<void> _initCommon() async {
// Initialize common sources/services
getIt.registerLazySingleton<AuthenticationSessionDataSource<int>>(
() => AuthenticationSessionDataSourceImpl<int>(),
);
}
static FutureOr<void> _initEmulator() async {
// Initialize emulator sources/services
final firebaseAuth = FirebaseAuth.instance;
await firebaseAuth.useAuthEmulator('localhost', 9099);
getIt
..registerLazySingleton<AuthenticationRemoteDataSource<int>>(
() => AuthenticationFirebaseDataSourceImpl<int>(
firebaseAuth: firebaseAuth,
googleSignIn:
GoogleSignIn(clientId: DefaultFirebaseOptions.ios.iosClientId)),
)
..registerLazySingleton<AuthenticationCacheDataSource<int>>(
() => AuthenticationFirebaseCacheDataSourceImpl<int>(
firebaseAuth: firebaseAuth,
),
);
}
static FutureOr<void> _initFirebase() async {
// Initialize firebase sources/services.
getIt
..registerLazySingleton<AuthenticationRemoteDataSource<int>>(
() => AuthenticationFirebaseDataSourceImpl<int>(
firebaseAuth: FirebaseAuth.instance,
googleSignIn:
GoogleSignIn(clientId: DefaultFirebaseOptions.ios.iosClientId)),
)
..registerLazySingleton<AuthenticationCacheDataSource<int>>(
() => AuthenticationFirebaseCacheDataSourceImpl<int>(
firebaseAuth: FirebaseAuth.instance,
),
);
}
static FutureOr<void> _initRest() async {
// Initialize rest api sources/services
}
static FutureOr<void> init() async {
await _initCommon();
final flavor = Flavor.get();
if (flavor.devMode == DevMode.rest) {
await _initRest();
} else if (flavor.devMode == DevMode.emulator) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await _initEmulator();
} else {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await _initFirebase();
}
await getIt.allReady();
}
@@ -0,0 +1,44 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
enum DevMode {
/// Mocked values
mock,
/// Real values from REST API
rest,
/// Emulated values with Firebase Emulator
emulator,
/// Real values from Firebase
real;
@override
String toString() => name;
/// Tries to parse String and returns mode. Fallback is returned if there
/// is an error during parsing.
static DevMode fromString(String? mode, {DevMode fallback = DevMode.mock}) {
for (final m in values) {
if (m.name == mode) {
return m;
}
}
return fallback;
}
}
@@ -0,0 +1,72 @@
// Copyright (C) 2023 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/enums/dev_mode.dart';
import 'package:flutter/material.dart';
abstract class Flavor {
Flavor._({
this.banner,
this.bannerColor = Colors.red,
this.devMode,
}) {
_instance = this;
}
static Flavor? _instance;
final String? banner;
final Color bannerColor;
final DevMode? devMode;
/// Returns [Flavor] instance.
static Flavor get() {
if (_instance == null) {
throw Exception('Flavor not initialized!');
}
return _instance!;
}
@override
String toString() => runtimeType.toString().replaceAll('Flavor', '');
}
class DevelopmentFlavor extends Flavor {
factory DevelopmentFlavor() {
const modeString = String.fromEnvironment('dev_mode', defaultValue: 'mock');
final mode = DevMode.fromString(modeString);
return DevelopmentFlavor._(devMode: mode);
}
DevelopmentFlavor._({
required super.devMode,
}) : super._(
banner: 'Dev',
);
}
class StagingFlavor extends Flavor {
StagingFlavor()
: super._(
banner: 'Staging',
bannerColor: Colors.green,
);
}
class ProductionFlavor extends Flavor {
ProductionFlavor() : super._();
}
@@ -65,8 +65,10 @@ class DefaultFirebaseOptions {
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',
androidClientId:
'136771801992-n2pq8oqutvrqj58e05hbavvc7n1jdfjb.apps.googleusercontent.com',
iosClientId:
'136771801992-p629tpo9bk3hcm2955s5ahivdla57ln9.apps.googleusercontent.com',
iosBundleId: 'com.example.exampleRouter',
);
}
@@ -15,9 +15,13 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:example_router/bootstrap.dart';
import 'package:example_router/core/flavors/flavor.dart';
import 'package:example_router/presentation/features/app/app.dart';
void main() {
MockSettings.enable();
void main(List<String> args) {
// Define environment
ProductionFlavor();
// Initialize environment and variables
bootstrap(App.new);
}
@@ -15,9 +15,13 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:example_router/bootstrap.dart';
import 'package:example_router/core/flavors/flavor.dart';
import 'package:example_router/presentation/features/app/app.dart';
void main() {
MockSettings.disable();
void main(List<String> args) {
// Define environment
DevelopmentFlavor();
// Initialize environment and variables
bootstrap(App.new);
}
@@ -34,6 +34,9 @@ class App extends StatelessWidget {
AuthenticationRepositoryImpl(
authenticationRemoteDataSource:
getIt<AuthenticationRemoteDataSource<int>>(),
authenticationSessionDataSource:
getIt<AuthenticationSessionDataSource<int>>(),
authenticationCacheDataSource: getIt<AuthenticationCacheDataSource<int>>(),
customPasswordValidator: const CustomPassword.pure(),
extraSignUpInputs: [
FormInput(
@@ -45,7 +45,8 @@ class SubPage extends StatelessWidget {
children: [
const Text('Another page'),
ElevatedButton(
onPressed: () => context.read<AuthenticationCubit<int>>().delete(),
onPressed: () =>
context.read<AuthenticationCubit<int>>().delete(),
child: const Text('Delete account'),
),
],
@@ -18,8 +18,10 @@
abstract class AuthFormField {
/// Email field: `wyattEmailField`
static const email = 'wyattEmailField';
/// Password field: `wyattPasswordField`
static const password = 'wyattPasswordField';
/// Confirm Password field: `wyattConfirmPasswordField`
static const confirmPassword = 'wyattConfirmPasswordField';
}
@@ -18,10 +18,13 @@
abstract class AuthFormName {
/// Sign Up form: `wyattSignUpForm`
static const String signUpForm = 'wyattSignUpForm';
/// Sign In form: `wyattSignInForm`
static const String signInForm = 'wyattSignInForm';
/// Password reset form: `wyattPasswordResetForm`
static const String passwordResetForm = 'wyattPasswordResetForm';
/// Edit account form: `wyattEditAccountForm`
static const String editAccountForm = 'wyattEditAccountForm';
}
@@ -0,0 +1,22 @@
// Copyright (C) 2023 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 AuthStorage {
static const String refreshToken = 'wyattRefreshToken';
static const String accessToken = 'wyattAccessToken';
static const String email = 'wyattEmail';
static const String id = 'wyattId';
}
@@ -18,8 +18,10 @@
enum AuthenticationStatus {
/// At the application launch.
unknown,
/// When the user is logged
authenticated,
/// When the user is not logged
unauthenticated,
}
@@ -16,7 +16,9 @@
import 'package:wyatt_architecture/wyatt_architecture.dart';
part 'exceptions_base.dart';
part 'exceptions_firebase.dart';
part 'exceptions_mock.dart';
/// Base exception used in Wyatt Authentication
abstract class AuthenticationFailureInterface extends AppException
@@ -0,0 +1,382 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
part of 'exceptions.dart';
/// {@macro apply_action_code_failure}
class ApplyActionCodeFailureBase extends ApplyActionCodeFailureInterface {
ApplyActionCodeFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
ApplyActionCodeFailureBase.fromCode(String code) : super.fromCode(code) {
switch (code) {
case 'expired-action-code':
msg = 'Action code has expired.';
break;
case 'invalid-action-code':
msg = 'Action code is invalid.';
break;
case 'user-disabled':
msg = 'This user has been disabled. Please contact support for help.';
break;
case 'user-not-found':
msg = 'Email is not found, please create an account.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
/// {@macro sign_up_with_email_and_password_failure}
class SignUpWithEmailAndPasswordFailureBase
extends SignUpWithEmailAndPasswordFailureInterface {
SignUpWithEmailAndPasswordFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignUpWithEmailAndPasswordFailureBase.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'invalid-email':
msg = 'The email address is badly formatted.';
break;
case 'user-disabled':
msg = 'This user has been disabled. Please contact support for help.';
break;
case 'email-already-in-use':
msg = 'An account already exists for that email.';
break;
case 'operation-not-allowed':
msg = 'Operation is not allowed. Please contact support.';
break;
case 'weak-password':
msg = 'Please enter a stronger password.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
/// {@macro fetch_sign_in_methods_failure}
class FetchSignInMethodsForEmailFailureBase
extends FetchSignInMethodsForEmailFailureInterface {
FetchSignInMethodsForEmailFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
FetchSignInMethodsForEmailFailureBase.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'invalid-email':
msg = 'The email address is badly formatted.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
/// {@macro sign_in_anonymously_failure}
class SignInAnonymouslyFailureBase extends SignInAnonymouslyFailureInterface {
SignInAnonymouslyFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignInAnonymouslyFailureBase.fromCode(String code) : super.fromCode(code) {
switch (code) {
case 'operation-not-allowed':
msg = 'Operation is not allowed. Please contact support.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
/// {@macro sign_in_with_credential_failure}
class SignInWithCredentialFailureBase
extends SignInWithCredentialFailureInterface {
SignInWithCredentialFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignInWithCredentialFailureBase.fromCode(String code) : super.fromCode(code) {
switch (code) {
case 'account-exists-with-different-credential':
msg = 'Account exists with different credentials.';
break;
case 'invalid-credential':
msg = 'The credential received is malformed or has expired.';
break;
case 'operation-not-allowed':
msg = 'Operation is not allowed. Please contact support.';
break;
case 'user-disabled':
msg = 'This user has been disabled. Please contact support for help.';
break;
case 'user-not-found':
msg = 'Email is not found, please create an account.';
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.';
}
}
}
/// {@macro sign_in_with_google_failure}
class SignInWithGoogleFailureBase extends SignInWithCredentialFailureBase
implements SignInWithGoogleFailureInterface {
SignInWithGoogleFailureBase([super.code, super.msg]);
SignInWithGoogleFailureBase.fromCode(super.code) : super.fromCode();
}
/// {@macro sign_in_with_facebook_failure}
class SignInWithFacebookFailureBase extends SignInWithCredentialFailureBase
implements SignInWithFacebookFailureInterface {
SignInWithFacebookFailureBase([super.code, super.msg]);
SignInWithFacebookFailureBase.fromCode(super.code) : super.fromCode();
}
/// {@macro sign_in_with_apple_failure}
class SignInWithAppleFailureBase extends SignInWithCredentialFailureBase
implements SignInWithAppleFailureInterface {
SignInWithAppleFailureBase([super.code, super.msg]);
SignInWithAppleFailureBase.fromCode(super.code) : super.fromCode();
}
/// {@macro sign_in_with_twitter_failure}
class SignInWithTwitterFailureBase extends SignInWithCredentialFailureBase
implements SignInWithAppleFailureInterface {
SignInWithTwitterFailureBase([super.code, super.msg]);
SignInWithTwitterFailureBase.fromCode(super.code) : super.fromCode();
}
/// {@macro sign_in_with_email_link_failure}
class SignInWithEmailLinkFailureBase
extends SignInWithEmailLinkFailureInterface {
SignInWithEmailLinkFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignInWithEmailLinkFailureBase.fromCode(String code) : super.fromCode(code) {
switch (code) {
case 'expired-action-code':
msg = 'Action code has expired.';
break;
case 'invalid-email':
msg = 'Email is not valid or badly formatted.';
break;
case 'user-disabled':
msg = 'This user has been disabled. Please contact support for help.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
/// {@macro sign_in_with_email_and_password_failure}
class SignInWithEmailAndPasswordFailureBase
extends SignInWithEmailAndPasswordFailureInterface {
SignInWithEmailAndPasswordFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignInWithEmailAndPasswordFailureBase.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'invalid-email':
msg = 'Email is not valid or badly formatted.';
break;
case 'user-disabled':
msg = 'This user has been disabled. Please contact support for help.';
break;
case 'user-not-found':
msg = 'Email is not found, please create an account.';
break;
case 'wrong-password':
msg = 'Incorrect password, please try again.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
/// {@macro send_email_verification_failure}
class SendEmailVerificationFailureBase
extends SendEmailVerificationFailureInterface {
SendEmailVerificationFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SendEmailVerificationFailureBase.fromCode(super.code) : super.fromCode();
}
/// {@macro send_password_reset_email_failure}
class SendPasswordResetEmailFailureBase
extends SendPasswordResetEmailFailureInterface {
SendPasswordResetEmailFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SendPasswordResetEmailFailureBase.fromCode(super.code) : super.fromCode();
}
/// {@macro send_sign_in_link_email_failure}
class SendSignInLinkEmailFailureBase
extends SendSignInLinkEmailFailureInterface {
SendSignInLinkEmailFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SendSignInLinkEmailFailureBase.fromCode(super.code) : super.fromCode();
}
/// {@macro confirm_password_reset_failure}
class ConfirmPasswordResetFailureBase
extends ConfirmPasswordResetFailureInterface {
ConfirmPasswordResetFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
ConfirmPasswordResetFailureBase.fromCode(super.code) : super.fromCode();
}
/// {@macro verify_password_reset_code_failure}
class VerifyPasswordResetCodeFailureBase
extends VerifyPasswordResetCodeFailureInterface {
VerifyPasswordResetCodeFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
VerifyPasswordResetCodeFailureBase.fromCode(super.code) : super.fromCode();
}
/// {@macro refresh_failure}
class RefreshFailureBase extends RefreshFailureInterface {
RefreshFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
RefreshFailureBase.fromCode(super.code) : super.fromCode();
}
/// {@macro sign_out_failure}
class SignOutFailureBase extends SignOutFailureInterface {
SignOutFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignOutFailureBase.fromCode(super.code) : super.fromCode();
}
/// {@macro reauthenticate_failure}
class ReauthenticateFailureBase extends ReauthenticateFailureInterface {
ReauthenticateFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
ReauthenticateFailureBase.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.';
}
}
}
/// {@macro update_email_failure}
class UpdateEmailFailureBase extends UpdateEmailFailureInterface {
UpdateEmailFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
UpdateEmailFailureBase.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.';
}
}
}
/// {@macro update_password_failure}
class UpdatePasswordFailureBase extends UpdatePasswordFailureInterface {
UpdatePasswordFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
UpdatePasswordFailureBase.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.';
}
}
}
/// {@macro model_parsing_failure}
class ModelParsingFailureBase extends ModelParsingFailureInterface {
ModelParsingFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
ModelParsingFailureBase.fromCode(super.code) : super.fromCode();
}
/// {@macro delete_account_failure}
class DeleteAccountFailureBase extends DeleteAccountFailureInterface {
DeleteAccountFailureBase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
DeleteAccountFailureBase.fromCode(String code) : super.fromCode(code) {
switch (code) {
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.';
}
}
}
@@ -0,0 +1,390 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
part of 'exceptions.dart';
/// {@macro apply_action_code_failure}
class ApplyActionCodeFailureMock extends ApplyActionCodeFailureInterface {
ApplyActionCodeFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
ApplyActionCodeFailureMock.fromCode(String code) : super.fromCode(code) {
switch (code) {
case 'expired-action-code':
msg = 'Action code has expired.';
break;
case 'invalid-action-code':
msg = 'Action code is invalid.';
break;
case 'user-disabled':
msg = 'This user has been disabled. Please contact support for help.';
break;
case 'user-not-found':
msg = 'Email is not found, please create an account.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
/// {@macro sign_up_with_email_and_password_failure}
class SignUpWithEmailAndPasswordFailureMock
extends SignUpWithEmailAndPasswordFailureInterface {
SignUpWithEmailAndPasswordFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignUpWithEmailAndPasswordFailureMock.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'invalid-email':
msg = 'The email address is badly formatted.';
break;
case 'user-disabled':
msg = 'This user has been disabled. Please contact support for help.';
break;
case 'email-already-in-use':
msg = 'An account already exists for that email.';
break;
case 'operation-not-allowed':
msg = 'Operation is not allowed. Please contact support.';
break;
case 'weak-password':
msg = 'Please enter a stronger password.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
/// {@macro fetch_sign_in_methods_failure}
class FetchSignInMethodsForEmailFailureMock
extends FetchSignInMethodsForEmailFailureInterface {
FetchSignInMethodsForEmailFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
FetchSignInMethodsForEmailFailureMock.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'invalid-email':
msg = 'The email address is badly formatted.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
/// {@macro sign_in_anonymously_failure}
class SignInAnonymouslyFailureMock
extends SignInAnonymouslyFailureInterface {
SignInAnonymouslyFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignInAnonymouslyFailureMock.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'operation-not-allowed':
msg = 'Operation is not allowed. Please contact support.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
/// {@macro sign_in_with_credential_failure}
class SignInWithCredentialFailureMock
extends SignInWithCredentialFailureInterface {
SignInWithCredentialFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignInWithCredentialFailureMock.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'account-exists-with-different-credential':
msg = 'Account exists with different credentials.';
break;
case 'invalid-credential':
msg = 'The credential received is malformed or has expired.';
break;
case 'operation-not-allowed':
msg = 'Operation is not allowed. Please contact support.';
break;
case 'user-disabled':
msg = 'This user has been disabled. Please contact support for help.';
break;
case 'user-not-found':
msg = 'Email is not found, please create an account.';
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.';
}
}
}
/// {@macro sign_in_with_google_failure}
class SignInWithGoogleFailureMock
extends SignInWithCredentialFailureMock
implements SignInWithGoogleFailureInterface {
SignInWithGoogleFailureMock([super.code, super.msg]);
SignInWithGoogleFailureMock.fromCode(super.code) : super.fromCode();
}
/// {@macro sign_in_with_facebook_failure}
class SignInWithFacebookFailureMock
extends SignInWithCredentialFailureMock
implements SignInWithFacebookFailureInterface {
SignInWithFacebookFailureMock([super.code, super.msg]);
SignInWithFacebookFailureMock.fromCode(super.code) : super.fromCode();
}
/// {@macro sign_in_with_apple_failure}
class SignInWithAppleFailureMock extends SignInWithCredentialFailureMock
implements SignInWithAppleFailureInterface {
SignInWithAppleFailureMock([super.code, super.msg]);
SignInWithAppleFailureMock.fromCode(super.code) : super.fromCode();
}
/// {@macro sign_in_with_twitter_failure}
class SignInWithTwitterFailureMock
extends SignInWithCredentialFailureMock
implements SignInWithAppleFailureInterface {
SignInWithTwitterFailureMock([super.code, super.msg]);
SignInWithTwitterFailureMock.fromCode(super.code) : super.fromCode();
}
/// {@macro sign_in_with_email_link_failure}
class SignInWithEmailLinkFailureMock
extends SignInWithEmailLinkFailureInterface {
SignInWithEmailLinkFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignInWithEmailLinkFailureMock.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'expired-action-code':
msg = 'Action code has expired.';
break;
case 'invalid-email':
msg = 'Email is not valid or badly formatted.';
break;
case 'user-disabled':
msg = 'This user has been disabled. Please contact support for help.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
/// {@macro sign_in_with_email_and_password_failure}
class SignInWithEmailAndPasswordFailureMock
extends SignInWithEmailAndPasswordFailureInterface {
SignInWithEmailAndPasswordFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignInWithEmailAndPasswordFailureMock.fromCode(String code)
: super.fromCode(code) {
switch (code) {
case 'invalid-email':
msg = 'Email is not valid or badly formatted.';
break;
case 'user-disabled':
msg = 'This user has been disabled. Please contact support for help.';
break;
case 'user-not-found':
msg = 'Email is not found, please create an account.';
break;
case 'wrong-password':
msg = 'Incorrect password, please try again.';
break;
default:
this.code = 'unknown';
msg = 'An unknown error occurred.';
}
}
}
/// {@macro send_email_verification_failure}
class SendEmailVerificationFailureMock
extends SendEmailVerificationFailureInterface {
SendEmailVerificationFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SendEmailVerificationFailureMock.fromCode(super.code) : super.fromCode();
}
/// {@macro send_password_reset_email_failure}
class SendPasswordResetEmailFailureMock
extends SendPasswordResetEmailFailureInterface {
SendPasswordResetEmailFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SendPasswordResetEmailFailureMock.fromCode(super.code) : super.fromCode();
}
/// {@macro send_sign_in_link_email_failure}
class SendSignInLinkEmailFailureMock
extends SendSignInLinkEmailFailureInterface {
SendSignInLinkEmailFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SendSignInLinkEmailFailureMock.fromCode(super.code) : super.fromCode();
}
/// {@macro confirm_password_reset_failure}
class ConfirmPasswordResetFailureMock
extends ConfirmPasswordResetFailureInterface {
ConfirmPasswordResetFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
ConfirmPasswordResetFailureMock.fromCode(super.code) : super.fromCode();
}
/// {@macro verify_password_reset_code_failure}
class VerifyPasswordResetCodeFailureMock
extends VerifyPasswordResetCodeFailureInterface {
VerifyPasswordResetCodeFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
VerifyPasswordResetCodeFailureMock.fromCode(super.code)
: super.fromCode();
}
/// {@macro refresh_failure}
class RefreshFailureMock extends RefreshFailureInterface {
RefreshFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
RefreshFailureMock.fromCode(super.code) : super.fromCode();
}
/// {@macro sign_out_failure}
class SignOutFailureMock extends SignOutFailureInterface {
SignOutFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
SignOutFailureMock.fromCode(super.code) : super.fromCode();
}
/// {@macro reauthenticate_failure}
class ReauthenticateFailureMock extends ReauthenticateFailureInterface {
ReauthenticateFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
ReauthenticateFailureMock.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.';
}
}
}
/// {@macro update_email_failure}
class UpdateEmailFailureMock extends UpdateEmailFailureInterface {
UpdateEmailFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
UpdateEmailFailureMock.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.';
}
}
}
/// {@macro update_password_failure}
class UpdatePasswordFailureMock extends UpdatePasswordFailureInterface {
UpdatePasswordFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
UpdatePasswordFailureMock.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.';
}
}
}
/// {@macro model_parsing_failure}
class ModelParsingFailureMock extends ModelParsingFailureInterface {
ModelParsingFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
ModelParsingFailureMock.fromCode(super.code) : super.fromCode();
}
/// {@macro delete_account_failure}
class DeleteAccountFailureMock extends DeleteAccountFailureInterface {
DeleteAccountFailureMock([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
DeleteAccountFailureMock.fromCode(String code) : super.fromCode(code) {
switch (code) {
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.';
}
}
}
@@ -21,8 +21,8 @@ import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart'
import 'package:wyatt_authentication_bloc/src/domain/entities/authentication_change_event/authentication_change_event.dart';
import 'package:wyatt_authentication_bloc/src/features/authentication/cubit/authentication_cubit.dart';
/// Extension that helps to quickly access useful resources like wrapper,
/// session, account or data.
/// Extension that helps to quickly access useful resources
/// from the context.
extension BuildContextExtension on BuildContext {
/// Read session in context from a specific AuthenticationCubit type [T]
AuthenticationSession<Data>?
@@ -20,10 +20,12 @@ import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
/// Calls on each cubit action of this package.
///
/// Useful to register custom logic on pre-implemented logic.
/// {@template custom_routine}
/// A custom routine that can be used to call a routine and
/// attach custom logic to it.
/// {@endtemplate}
class CustomRoutine<R, Data> {
/// {@macro custom_routine}
const CustomRoutine({
required this.routine,
required this.attachedLogic,
@@ -31,13 +33,21 @@ class CustomRoutine<R, Data> {
required this.onSuccess,
});
/// The routine to be called
final FutureOr<Result<R, AppException>> Function() routine;
/// The custom logic to be attached to the routine
final FutureOr<Result<Data?, AppException>> Function(
Result<R, AppException> routineResult,
) attachedLogic;
/// The callback to be called when an error occurs
final void Function(AppException exception) onError;
/// The callback to be called when no error occurs
final void Function(R result, Data? data) onSuccess;
/// Calls the routine and calls the custom attached logic
FutureOr<void> call() async {
final result = await routine.call();
@@ -17,7 +17,9 @@
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
/// This class contains all the forms used in the authentication process.
abstract class Forms {
/// Builds a sign in form.
static WyattForm buildSignInForm(
FormInputValidator<String?, ValidationError>? customEmailValidator,
FormInputValidator<String?, ValidationError>? customPasswordValidator,
@@ -36,6 +38,7 @@ abstract class Forms {
name: AuthFormName.signInForm,
);
/// Builds a sign up form.
static WyattForm buildSignUpForm(
FormInputValidator<String?, ValidationError>? customEmailValidator,
FormInputValidator<String?, ValidationError>? customPasswordValidator,
@@ -57,6 +60,7 @@ abstract class Forms {
name: AuthFormName.signUpForm,
);
/// Builds a password reset form.
static WyattForm buildPasswordResetForm(
FormInputValidator<String?, ValidationError>? customEmailValidator,
) =>
@@ -70,6 +74,7 @@ abstract class Forms {
name: AuthFormName.passwordResetForm,
);
/// Builds an edit account form.
static WyattForm buildEditAccountForm(
FormInputValidator<String?, ValidationError>? customEmailValidator,
FormInputValidator<String?, ValidationError>? customPasswordValidator,
@@ -14,4 +14,5 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'remote/authentication_firebase_data_source_impl.dart';
export 'local/local.dart';
export 'remote/remote.dart';
@@ -0,0 +1,56 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_authentication_bloc/src/domain/data_sources/local/authentication_cache_data_source.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
/// {@template authentication_firebase_cache_data_source_impl}
/// A data source that manages the cache strategy.
/// This implementation uses Firebase.
/// {@endtemplate}
class AuthenticationFirebaseCacheDataSourceImpl<Data>
extends AuthenticationCacheDataSource<Data> {
/// {@macro authentication_firebase_cache_data_source_impl}
AuthenticationFirebaseCacheDataSourceImpl({
FirebaseAuth? firebaseAuth,
}) : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance;
final FirebaseAuth _firebaseAuth;
// Already done by Firebase
@override
Future<void> cacheAccount(Account account) => Future.value();
@override
Future<Account?> getCachedAccount() async {
final currentUser = _firebaseAuth.currentUser;
if (currentUser == null) {
return null;
}
final jwt = await currentUser.getIdToken(true);
final currentAccount = AccountModel.fromFirebaseUser(
currentUser,
accessToken: jwt,
);
return currentAccount;
}
// Already done by Firebase
@override
Future<void> removeCachedAccount() => Future.value();
}
@@ -0,0 +1,75 @@
// Copyright (C) 2023 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_secure_storage/flutter_secure_storage.dart';
import 'package:wyatt_authentication_bloc/src/core/constants/storage.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
/// {@template authentication_secure_storage_cache_data_source_impl}
/// A data source that manages the cache strategy.
/// This implementation uses Secure Storage.
/// {@endtemplate}
class AuthenticationSecureStorageCacheDataSourceImpl<Data>
extends AuthenticationCacheDataSource<Data> {
/// {@macro authentication_secure_storage_cache_data_source_impl}
const AuthenticationSecureStorageCacheDataSourceImpl({
FlutterSecureStorage? secureStorage,
}) : _secureStorage = secureStorage ?? const FlutterSecureStorage();
final FlutterSecureStorage _secureStorage;
@override
Future<void> cacheAccount(Account account) async {
await _secureStorage.write(
key: AuthStorage.id,
value: account.id,
);
await _secureStorage.write(
key: AuthStorage.email,
value: account.email,
);
await _secureStorage.write(
key: AuthStorage.accessToken,
value: account.accessToken,
);
}
@override
Future<Account?> getCachedAccount() async {
final id = await _secureStorage.read(key: AuthStorage.id);
final email = await _secureStorage.read(key: AuthStorage.email);
final accessToken = await _secureStorage.read(key: AuthStorage.accessToken);
if (id == null || email == null || accessToken == null) {
return null;
}
final currentAccount = AccountModel.fromSecureStorage(
id: id,
email: email,
accessToken: accessToken,
);
return currentAccount;
}
@override
Future<void> removeCachedAccount() async {
await _secureStorage.delete(key: AuthStorage.id);
await _secureStorage.delete(key: AuthStorage.email);
await _secureStorage.delete(key: AuthStorage.accessToken);
}
}
@@ -0,0 +1,45 @@
// Copyright (C) 2023 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:rxdart/subjects.dart';
import 'package:wyatt_authentication_bloc/src/domain/data_sources/local/authentication_session_data_source.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
/// {@template authentication_session_data_source_impl}
/// A data source that manages the current session.
/// {@endtemplate}
class AuthenticationSessionDataSourceImpl<Data>
extends AuthenticationSessionDataSource<Data> {
/// {@macro authentication_session_data_source_impl}
AuthenticationSessionDataSourceImpl();
final StreamController<AuthenticationSession<Data>> _sessionStream =
BehaviorSubject();
@override
void addSession(AuthenticationSession<Data> session) {
_sessionStream.add(session);
}
@override
Future<AuthenticationSession<Data>> currentSession() => sessionStream().last;
@override
Stream<AuthenticationSession<Data>> sessionStream() =>
_sessionStream.stream.asBroadcastStream();
}
@@ -0,0 +1,19 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'authentication_firebase_cache_data_source_impl.dart';
export 'authentication_secure_storage_cache_data_source_impl.dart';
export 'authentication_session_data_source_impl.dart';
@@ -0,0 +1,106 @@
// Copyright (C) 2023 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';
/// {@template authentication_base_data_source}
/// Base class for custom [AuthenticationRemoteDataSource] implementations.
/// It implements all methods as throwing [UnimplementedError]s.
/// {@endtemplate}
class AuthenticationBaseDataSource<Data>
extends AuthenticationRemoteDataSource<Data> {
/// {@macro authentication_base_data_source}
const AuthenticationBaseDataSource();
@override
Future<void> confirmPasswordReset({
required String code,
required String newPassword,
}) {
throw UnimplementedError();
}
@override
Future<void> delete() {
throw UnimplementedError();
}
@override
Future<Account> reauthenticate() {
throw UnimplementedError();
}
@override
Future<Account> refresh() {
throw UnimplementedError();
}
@override
Future<void> sendEmailVerification() {
throw UnimplementedError();
}
@override
Future<void> sendPasswordResetEmail({required String email}) {
throw UnimplementedError();
}
@override
Future<Account> signInAnonymously() {
throw UnimplementedError();
}
@override
Future<Account> signInWithEmailAndPassword({
required String email,
required String password,
}) {
throw UnimplementedError();
}
@override
Future<Account> signInWithGoogle() {
throw UnimplementedError();
}
@override
Future<void> signOut() {
throw UnimplementedError();
}
@override
Future<Account> signUpWithEmailAndPassword({
required String email,
required String password,
}) {
throw UnimplementedError();
}
@override
Future<Account> updateEmail({required String email}) {
throw UnimplementedError();
}
@override
Future<Account> updatePassword({required String password}) {
throw UnimplementedError();
}
@override
Future<bool> verifyPasswordResetCode({required String code}) {
throw UnimplementedError();
}
}
@@ -23,55 +23,27 @@ import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart';
import 'package:wyatt_authentication_bloc/src/data/models/models.dart';
import 'package:wyatt_authentication_bloc/src/domain/data_sources/remote/authentication_remote_data_source.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/authentication_change_event/authentication_change_event.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
/// {@template authentication_firebase_data_source_impl}
/// Implementation of [AuthenticationRemoteDataSource] using Firebase.
/// {@endtemplate}
class AuthenticationFirebaseDataSourceImpl<Data>
extends AuthenticationRemoteDataSource<Data> {
/// {@macro authentication_firebase_data_source_impl}
AuthenticationFirebaseDataSourceImpl({
FirebaseAuth? firebaseAuth,
GoogleSignIn? googleSignIn,
}) : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance,
_googleSignIn = googleSignIn ?? GoogleSignIn() {
_latestCredentials = BehaviorSubject();
_sessionStream = BehaviorSubject();
// Check for account in memory (persistence)
_checkForCachedAccount();
}
late StreamController<AuthenticationSession<Data>> _sessionStream;
late StreamController<UserCredential?> _latestCredentials;
final FirebaseAuth _firebaseAuth;
final GoogleSignIn _googleSignIn;
Future<void> _checkForCachedAccount() async {
final currentUser = _firebaseAuth.currentUser;
if (currentUser == null) {
_sessionStream.add(
const AuthenticationSession(
latestEvent: UnknownAuthenticationEvent(),
),
);
return;
}
final jwt = await currentUser.getIdToken(true);
final currentAccount = AccountModel.fromFirebaseUser(
currentUser,
accessToken: jwt,
);
_sessionStream.add(
AuthenticationSession.fromEvent(
SignedInFromCacheEvent(account: currentAccount),
),
);
return;
}
Future<Account> _addToCredentialStream(
UserCredential userCredential,
) async {
@@ -87,23 +59,6 @@ class AuthenticationFirebaseDataSourceImpl<Data>
return account;
}
// Session related methods ===================================================
/// {@macro add_session}
@override
void addSession(AuthenticationSession<Data> session) {
_sessionStream.add(session);
}
/// {@macro session_stream}
@override
Stream<AuthenticationSession<Data>> sessionStream() =>
_sessionStream.stream.asBroadcastStream();
/// {@macro current_session}
@override
Future<AuthenticationSession<Data>> currentSession() => sessionStream().last;
// SignUp/SignIn methods ====================================================
/// {@macro signup_pwd}
@@ -0,0 +1,201 @@
// Copyright (C) 2023 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:math';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
/// {@template authentication_mock_data_source_impl}
/// Implementation of [AuthenticationRemoteDataSource] using Mocks.
/// {@endtemplate}
class AuthenticationMockDataSourceImpl<Data>
extends AuthenticationRemoteDataSource<Data> {
/// {@macro authentication_mock_data_source_impl}
AuthenticationMockDataSourceImpl({
this.mockData = const {
'test@test.fr': Pair('test12', '1'),
'alice@test.fr': Pair('alice12', '2'),
'bob@test.fr': Pair('bob1234', '3'),
},
this.accessToken = 'mock_access_token',
});
/// Mock data.
/// Format: <email, <password, id>>
final Map<String, Pair<String, String>> mockData;
/// Current user.
Account? _currentUser;
/// Access token.
final String accessToken;
/// A random delay to simulate network latency.
Future<void> _randomDelay() async {
await Future<void>.delayed(
Duration(milliseconds: Random().nextInt(400) + 200),
);
return;
}
// SignUp/SignIn methods ====================================================
/// {@macro signup_pwd}
@override
Future<Account> signUpWithEmailAndPassword({
required String email,
required String password,
}) async {
await _randomDelay();
mockData[email] = Pair(password, '4');
return _currentUser = Account(
id: '4',
isAnonymous: false,
emailVerified: false,
providerId: 'mock',
email: email,
accessToken: accessToken,
);
}
/// {@macro signin_pwd}
@override
Future<Account> signInWithEmailAndPassword({
required String email,
required String password,
}) async {
await _randomDelay();
final pair = mockData[email];
if (pair == null || pair.left != password) {
throw SignInWithEmailAndPasswordFailureMock();
}
return _currentUser = Account(
id: pair.right!,
isAnonymous: false,
emailVerified: true,
providerId: 'mock',
email: email,
accessToken: accessToken,
);
}
/// {@macro signin_anom}
@override
Future<Account> signInAnonymously() async {
await _randomDelay();
return _currentUser = Account(
id: '5',
isAnonymous: true,
emailVerified: false,
providerId: 'mock',
accessToken: accessToken,
);
}
/// {@macro signin_google}
@override
Future<Account> signInWithGoogle() async {
await _randomDelay();
return _currentUser = Account(
id: '6',
isAnonymous: false,
emailVerified: true,
providerId: 'google.com',
email: 'mock@gmail.com',
accessToken: accessToken,
);
}
/// {@macro signout}
@override
Future<void> signOut() async {
await _randomDelay();
_currentUser = null;
}
// Account management methods ===============================================
/// {@macro refresh}
@override
Future<Account> refresh() async {
await _randomDelay();
if (_currentUser == null) {
throw RefreshFailureMock();
}
return _currentUser!;
}
/// {@macro reauthenticate}
@override
Future<Account> reauthenticate() async {
await _randomDelay();
if (_currentUser == null) {
throw ReauthenticateFailureMock();
}
return _currentUser!;
}
/// {@macro update_email}
@override
Future<Account> updateEmail({required String email}) async {
throw UnimplementedError();
}
/// {@macro update_password}
@override
Future<Account> updatePassword({required String password}) async {
throw UnimplementedError();
}
/// {@macro delete}
@override
Future<void> delete() async {
throw UnimplementedError();
}
// Email related stuff ======================================================
/// {@macro send_email_verification}
@override
Future<void> sendEmailVerification() async {
throw UnimplementedError();
}
/// {@macro send_password_reset_email}
@override
Future<void> sendPasswordResetEmail({required String email}) async {
throw UnimplementedError();
}
/// {@macro confirm_password_reset}
@override
Future<void> confirmPasswordReset({
required String code,
required String newPassword,
}) async {
throw UnimplementedError();
}
/// {@macro verify_password_reset_code}
@override
Future<bool> verifyPasswordResetCode({required String code}) async {
throw UnimplementedError();
}
}
@@ -0,0 +1,19 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'authentication_base_data_source.dart';
export 'authentication_firebase_data_source_impl.dart';
export 'authentication_mock_data_source_impl.dart';
@@ -18,8 +18,11 @@ import 'package:firebase_auth/firebase_auth.dart';
import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
/// {@template account_model}
/// Account Model to parse Firebase User data
/// {@endtemplate}
class AccountModel extends Account {
/// {@macro account_model}
factory AccountModel.fromFirebaseUserCredential(
UserCredential? userCredential, {
required String? accessToken,
@@ -47,6 +50,7 @@ class AccountModel extends Account {
}
}
/// {@macro account_model}
factory AccountModel.fromFirebaseUser(
User? user, {
required String? accessToken,
@@ -72,6 +76,31 @@ class AccountModel extends Account {
throw ModelParsingFailureFirebase('null-user', 'User cannot be null');
}
}
/// {@macro account_model}
factory AccountModel.fromSecureStorage({
required String? id,
required String? email,
required String? accessToken,
}) {
if (id != null && email != null && accessToken != null) {
return AccountModel._(
user: null,
id: id,
emailVerified: false,
isAnonymous: false,
providerId: '',
email: email,
accessToken: accessToken,
);
} else {
throw Exception(
'null-id-email-access-token'
'id, email, and accessToken cannot be null',
);
}
}
const AccountModel._({
required this.user,
required super.id,
@@ -87,6 +116,7 @@ class AccountModel extends Account {
super.accessToken,
});
/// The Firebase User
final User? user;
@override
@@ -16,17 +16,20 @@
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/core/utils/forms.dart';
import 'package:wyatt_authentication_bloc/src/domain/data_sources/remote/authentication_remote_data_source.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
import 'package:wyatt_authentication_bloc/src/domain/domain.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
/// {@template authentication_repository_impl}
/// The default implementation of [AuthenticationRepository].
/// {@endtemplate}
class AuthenticationRepositoryImpl<Data extends Object>
extends AuthenticationRepository<Data> {
/// {@macro authentication_repository_impl}
AuthenticationRepositoryImpl({
required this.authenticationRemoteDataSource,
required this.authenticationCacheDataSource,
required this.authenticationSessionDataSource,
FormRepository? formRepository,
// ignore: strict_raw_type
List<FormInput>? extraSignUpInputs,
@@ -66,24 +69,57 @@ class AuthenticationRepositoryImpl<Data extends Object>
);
}
/// The remote data source used to perform the authentication process.
final AuthenticationRemoteDataSource<Data> authenticationRemoteDataSource;
/// The cache data source used to cache the current account.
final AuthenticationCacheDataSource<Data> authenticationCacheDataSource;
/// The session data source used to manage the current session.
final AuthenticationSessionDataSource<Data> authenticationSessionDataSource;
late FormRepository _formRepository;
/// {@macro form_repo}
@override
FormRepository get formRepository => _formRepository;
// Cache related methods ====================================================
/// {@macro check_cache_account}
@override
Future<void> checkForCachedAccount() async {
final cachedAccount =
await authenticationCacheDataSource.getCachedAccount();
if (cachedAccount == null) {
addSession(
const AuthenticationSession(
latestEvent: UnknownAuthenticationEvent(),
),
);
return;
}
addSession(
AuthenticationSession.fromEvent(
SignedInFromCacheEvent(account: cachedAccount),
),
);
return;
}
// Session related methods ===================================================
/// {@macro add_session}
@override
void addSession(AuthenticationSession<Data> session) =>
authenticationRemoteDataSource.addSession(session);
authenticationSessionDataSource.addSession(session);
/// {@macro session_stream}
@override
Stream<AuthenticationSession<Data>> sessionStream() =>
authenticationRemoteDataSource.sessionStream();
authenticationSessionDataSource.sessionStream();
/// {@macro current_session}
@override
@@ -91,7 +127,9 @@ class AuthenticationRepositoryImpl<Data extends Object>
Result.tryCatchAsync<AuthenticationSession<Data>, AppException,
AppException>(
() async {
final session = await authenticationRemoteDataSource.currentSession();
final session =
await authenticationSessionDataSource.currentSession();
return session;
},
(error) => error,
@@ -112,6 +150,8 @@ class AuthenticationRepositoryImpl<Data extends Object>
email: email,
password: password,
);
// Cache the account
await authenticationCacheDataSource.cacheAccount(account);
return account;
},
(error) => error,
@@ -130,6 +170,8 @@ class AuthenticationRepositoryImpl<Data extends Object>
email: email,
password: password,
);
// Cache the account
await authenticationCacheDataSource.cacheAccount(account);
return account;
},
(error) => error,
@@ -154,6 +196,8 @@ class AuthenticationRepositoryImpl<Data extends Object>
() async {
final account =
await authenticationRemoteDataSource.signInWithGoogle();
// Cache the account
await authenticationCacheDataSource.cacheAccount(account);
return account;
},
(error) => error,
@@ -165,6 +209,8 @@ class AuthenticationRepositoryImpl<Data extends Object>
Result.tryCatchAsync<void, AppException, AppException>(
() async {
await authenticationRemoteDataSource.signOut();
// Remove the cached account
await authenticationCacheDataSource.removeCachedAccount();
},
(error) => error,
);
@@ -177,6 +223,8 @@ class AuthenticationRepositoryImpl<Data extends Object>
Result.tryCatchAsync<Account, AppException, AppException>(
() async {
final account = await authenticationRemoteDataSource.refresh();
// Cache the account
await authenticationCacheDataSource.cacheAccount(account);
return account;
},
(error) => error,
@@ -188,6 +236,8 @@ class AuthenticationRepositoryImpl<Data extends Object>
Result.tryCatchAsync<Account, AppException, AppException>(
() async {
final account = await authenticationRemoteDataSource.reauthenticate();
// Cache the account
await authenticationCacheDataSource.cacheAccount(account);
return account;
},
(error) => error,
@@ -200,6 +250,8 @@ class AuthenticationRepositoryImpl<Data extends Object>
() async {
final account =
await authenticationRemoteDataSource.updateEmail(email: email);
// Cache the account
await authenticationCacheDataSource.cacheAccount(account);
return account;
},
(error) => error,
@@ -213,6 +265,8 @@ class AuthenticationRepositoryImpl<Data extends Object>
final account = await authenticationRemoteDataSource.updatePassword(
password: password,
);
// Cache the account
await authenticationCacheDataSource.cacheAccount(account);
return account;
},
(error) => error,
@@ -222,7 +276,11 @@ class AuthenticationRepositoryImpl<Data extends Object>
@override
FutureOrResult<void> delete() =>
Result.tryCatchAsync<void, AppException, AppException>(
() async => authenticationRemoteDataSource.delete(),
() async {
await authenticationRemoteDataSource.delete();
// Remove the cached account
await authenticationCacheDataSource.removeCachedAccount();
},
(error) => error,
);
@@ -14,4 +14,5 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'remote/authentication_remote_data_source.dart';
export 'local/local.dart';
export 'remote/remote.dart';
@@ -0,0 +1,40 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
/// {@template authentication_cache_data_source}
/// A data source that manages the cache strategy.
/// {@endtemplate}
abstract class AuthenticationCacheDataSource<Data> extends BaseLocalDataSource {
/// {@macro authentication_cache_data_source}
const AuthenticationCacheDataSource();
/// Returns the cached account if it exists.
Future<Account?> getCachedAccount();
/// Adds the current account to the cache.
///
/// If an account is already cached, it will be replaced.
Future<void> cacheAccount(Account account);
/// Removes the current account from the cache.
///
/// The cache will be empty after this operation.
/// If no account is cached, nothing will happen.
Future<void> removeCachedAccount();
}
@@ -0,0 +1,36 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
/// {@template authentication_session_data_source}
/// A data source that manages the current session.
/// {@endtemplate}
abstract class AuthenticationSessionDataSource<Data>
extends BaseLocalDataSource {
/// {@macro authentication_session_data_source}
const AuthenticationSessionDataSource();
/// Adds a new session to the data source.
void addSession(AuthenticationSession<Data> session);
/// Returns a stream of sessions.
Stream<AuthenticationSession<Data>> sessionStream();
/// Returns the current session.
Future<AuthenticationSession<Data>> currentSession();
}
@@ -0,0 +1,18 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'authentication_cache_data_source.dart';
export 'authentication_session_data_source.dart';
@@ -16,49 +16,71 @@
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
/// Is responsible for abstracting the provenance of the data.
/// {@template authentication_remote_data_source}
/// A remote data source for authentication.
/// It is responsible for all the external communication with the authentication
/// providers.
/// {@endtemplate}
abstract class AuthenticationRemoteDataSource<Data>
extends BaseRemoteDataSource {
// Session related methods ===================================================
void addSession(AuthenticationSession<Data> session);
Stream<AuthenticationSession<Data>> sessionStream();
Future<AuthenticationSession<Data>> currentSession();
/// {@macro authentication_remote_data_source}
const AuthenticationRemoteDataSource();
// SignUp/SignIn methods ====================================================
/// Sign up with email and password.
Future<Account> signUpWithEmailAndPassword({
required String email,
required String password,
});
/// Sign in with email and password.
Future<Account> signInWithEmailAndPassword({
required String email,
required String password,
});
/// Sign in anonymously.
Future<Account> signInAnonymously();
/// Sign in with Google.
Future<Account> signInWithGoogle();
/// Sign out.
Future<void> signOut();
// Account management methods ===============================================
// Future<void> linkCurrentUserWith(AuthenticationProvider anotherProvider);
/// Refresh the current account.
Future<Account> refresh();
/// Reauthenticate the current account.
Future<Account> reauthenticate();
/// Update the current account's email.
Future<Account> updateEmail({required String email});
/// Update the current account's password.
Future<Account> updatePassword({required String password});
/// Delete the current account.
Future<void> delete();
// Email related stuff ======================================================
/// Send an email verification.
Future<void> sendEmailVerification();
/// Send a password reset email.
Future<void> sendPasswordResetEmail({required String email});
/// Confirm password reset.
Future<void> confirmPasswordReset({
required String code,
required String newPassword,
});
/// Verify password reset code.
Future<bool> verifyPasswordResetCode({required String code});
}
@@ -0,0 +1,17 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'authentication_remote_data_source.dart';
@@ -17,9 +17,12 @@
import 'package:equatable/equatable.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
/// Represents a user [Account] in the
/// {@template account}
/// Represents a user [Account] in the
/// various identity provisioning systems.
/// {@endtemplate}
class Account extends Equatable implements Entity {
/// {@macro account}
const Account({
required this.id,
required this.isAnonymous,
@@ -17,16 +17,20 @@
import 'package:equatable/equatable.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
/// {@template authentication_session}
/// The [AuthenticationSession] object is used to transport and propagate
/// the last event issued by an authentication state change, a user account
/// if connected, and the associated data.
/// {@endtemplate}
class AuthenticationSession<Data> extends Equatable {
/// {@macro authentication_session}
const AuthenticationSession({
required this.latestEvent,
this.account,
this.data,
});
/// Creates a new [AuthenticationSession] from an [AuthenticationChangeEvent].
factory AuthenticationSession.fromEvent(
AuthenticationChangeEvent latestEvent, {
Data? data,
@@ -44,8 +48,13 @@ class AuthenticationSession<Data> extends Equatable {
);
}
/// The last event issued by an authentication state change.
final AuthenticationChangeEvent latestEvent;
/// The user account if connected.
final Account? account;
/// The associated data.
final Data? data;
@override
@@ -16,11 +16,14 @@
part of 'authentication_change_event.dart';
/// {@template authenticated_change_event}
/// Represents every event where user is authenticated.
/// {@endtemplate}
abstract class AuthenticatedChangeEvent extends AuthenticationChangeEvent {
/// {@macro authenticated_change_event}
const AuthenticatedChangeEvent({required this.account});
/// The user's account.
final Account account;
@override
@@ -29,9 +29,12 @@ part 'signed_up_event.dart';
part 'unknown_authentication_event.dart';
part 'updated_event.dart';
/// {@template authentication_change_event}
/// Represents an event initiated by a change in
/// the user's authentication status.
/// {@endtemplate}
abstract class AuthenticationChangeEvent extends Equatable implements Entity {
/// {@macro authentication_change_event}
const AuthenticationChangeEvent();
@override
@@ -16,7 +16,10 @@
part of 'authentication_change_event.dart';
/// {@template deleted_event}
/// When a user deleted his account.
/// {@endtemplate}
class DeletedEvent extends AuthenticationChangeEvent {
/// {@macro deleted_event}
const DeletedEvent();
}
@@ -16,9 +16,12 @@
part of 'authentication_change_event.dart';
/// {@template reauthenticated_event}
/// When a user re-authenticates (from the logged in state to the
/// logged in state with a different and fresh access
/// token and a different login time)
/// {@endtemplate}
class ReauthenticatedEvent extends AuthenticatedChangeEvent {
/// {@macro reauthenticated_event}
const ReauthenticatedEvent({required super.account});
}
@@ -16,8 +16,11 @@
part of 'authentication_change_event.dart';
/// {@template refreshed_event}
/// When a user access token is refreshed (from the logged in state to the
/// logged in state with a different access token)
/// {@endtemplate}
class RefreshedEvent extends AuthenticatedChangeEvent {
/// {@macro refreshed_event}
const RefreshedEvent({required super.account});
}
@@ -16,7 +16,10 @@
part of 'authentication_change_event.dart';
/// {@template signed_in_event}
/// When a user authenticates (from not logged in to logged in).
/// {@endtemplate}
class SignedInEvent extends AuthenticatedChangeEvent {
/// {@macro signed_in_event}
const SignedInEvent({required super.account});
}
@@ -16,7 +16,10 @@
part of 'authentication_change_event.dart';
/// {@template signed_in_from_cache_event}
/// When a user authenticates automatically (from not logged in to logged in).
/// {@endtemplate}
class SignedInFromCacheEvent extends AuthenticatedChangeEvent {
/// {@macro signed_in_from_cache_event}
const SignedInFromCacheEvent({required super.account});
}
@@ -16,7 +16,10 @@
part of 'authentication_change_event.dart';
/// {@template signed_out_event}
/// When a user logs out.
/// {@endtemplate}
class SignedOutEvent extends AuthenticationChangeEvent {
/// {@macro signed_out_event}
const SignedOutEvent();
}
@@ -16,7 +16,10 @@
part of 'authentication_change_event.dart';
/// {@template signed_up_event}
/// When a user creates an account.
/// {@endtemplate}
class SignedUpEvent extends AuthenticatedChangeEvent {
/// {@macro signed_up_event}
const SignedUpEvent({required super.account});
}
@@ -16,7 +16,10 @@
part of 'authentication_change_event.dart';
/// {@template unknown_authentication_event}
/// When a user's login status is unknown.
/// {@endtemplate}
class UnknownAuthenticationEvent extends AuthenticationChangeEvent {
/// {@macro unknown_authentication_event}
const UnknownAuthenticationEvent();
}
@@ -16,7 +16,10 @@
part of 'authentication_change_event.dart';
/// {@template updated_event}
/// When the user's account has been updated.
/// {@endtemplate}
class UpdatedEvent extends AuthenticatedChangeEvent {
/// {@macro updated_event}
const UpdatedEvent({required super.account});
}
@@ -19,13 +19,25 @@ import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
/// {@template auth_repo}
/// Authentication repository interface.
/// {@endtemplate}
abstract class AuthenticationRepository<Data> extends BaseRepository {
/// {@template form_repo}
/// Form repository used in different authentication cubits/blocs
/// {@endtemplate}
FormRepository get formRepository;
// Stream related methods ===================================================
// Cache related methods ====================================================
/// {@template check_cache_account}
/// Checks if there is a cached account.
/// And if there is, it will sign in the user automatically by
/// emitting an event.
/// {@endtemplate}
Future<void> checkForCachedAccount();
// Session related methods ===================================================
/// {@template add_session}
/// Add a new authentication event.
@@ -20,7 +20,11 @@ import 'package:wyatt_authentication_bloc/src/core/enums/authentication_status.d
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_authentication_bloc/src/features/authentication/cubit/authentication_cubit.dart';
/// {@template authentication_builder}
/// A widget that builds itself based on the current authentication state.
/// {@endtemplate}
class AuthenticationBuilder<Data> extends StatelessWidget {
/// {@macro authentication_builder}
const AuthenticationBuilder({
required this.authenticated,
required this.unauthenticated,
@@ -28,11 +32,16 @@ class AuthenticationBuilder<Data> extends StatelessWidget {
super.key,
});
/// Widget to show when the user is authenticated.
final Widget Function(
BuildContext context,
AuthenticationSession<Data> session,
) authenticated;
/// Widget to show when the user is unauthenticated.
final Widget Function(BuildContext context) unauthenticated;
/// Widget to show when the authentication status is unknown.
final Widget Function(BuildContext context) unknown;
@override
@@ -27,27 +27,45 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'authentication_state.dart';
/// {@template authentication_cubit}
/// Abstract authentication cubit class needs to be implemented in application.
///
/// This cubit is in charge of managing the global authentication state of
/// the application.
///
/// Its here you can override every callbacks and add your custom logic.
/// {@endtemplate}
abstract class AuthenticationCubit<Data>
extends Cubit<AuthenticationState<Data>> {
/// {@macro authentication_cubit}
AuthenticationCubit({
required this.authenticationRepository,
}) : super(const AuthenticationState.unknown()) {
_listenForAuthenticationChanges();
_init();
}
/// The authentication repository.
final AuthenticationRepository<Data> authenticationRepository;
/// The latest session.
AuthenticationSession<Data>? _latestSession;
/// Method that is called when the cubit is initialized.
Future<void> _init() async {
/// Setup listeners.
_listenForAuthenticationChanges();
/// Check if there is a cached account.
await authenticationRepository.checkForCachedAccount();
}
void _listenForAuthenticationChanges() {
authenticationRepository.sessionStream().asyncMap((session) async {
final event = session.latestEvent;
/// If the session is signed in from cache.
if (event is SignedInFromCacheEvent) {
/// Call the custom routine.
final customRoutineResult = await onSignInFromCache(session);
if (customRoutineResult.isOk) {
@@ -64,12 +82,18 @@ abstract class AuthenticationCubit<Data>
}
return session;
}).listen((session) async {
/// Save the latest session.
_latestSession = session;
/// If there is an account: emit authenticated state.
if (session.account != null) {
emit(AuthenticationState<Data>.authenticated(session));
return;
}
/// If there is no account: emit unauthenticated state.
emit(AuthenticationState<Data>.unauthenticated());
return;
});
}
@@ -16,22 +16,32 @@
part of 'authentication_cubit.dart';
/// {@template authentication_status}
/// The status of the authentication cubit.
/// {@endtemplate}
class AuthenticationState<Data> extends Equatable {
/// {@macro authentication_status}
const AuthenticationState._(this.status, this.session);
/// The user is not authenticated.
const AuthenticationState.unauthenticated()
: this._(AuthenticationStatus.unauthenticated, null);
/// The user is authenticated.
const AuthenticationState.authenticated(AuthenticationSession<Data> session)
: this._(
AuthenticationStatus.authenticated,
session,
);
/// The user's authentication status is unknown.
const AuthenticationState.unknown()
: this._(AuthenticationStatus.unknown, null);
/// The status of the authentication cubit.
final AuthenticationStatus status;
/// The session of the authentication cubit.
final AuthenticationSession<Data>? session;
@override
@@ -16,10 +16,13 @@
part of 'edit_account_cubit.dart';
/// {@template edit_account_cubit}
/// Abstract edit account cubit useful for implementing a cubit with fine
/// granularity by adding only the required mixins.
/// {@endtemplate}
abstract class BaseEditAccountCubit<Data>
extends FormDataCubit<EditAccountState> {
/// {@macro edit_account_cubit}
BaseEditAccountCubit({
required this.authenticationRepository,
}) : super(
@@ -28,6 +31,8 @@ abstract class BaseEditAccountCubit<Data>
.accessForm(AuthFormName.signInForm),
),
);
/// The authentication repository.
final AuthenticationRepository<Data> authenticationRepository;
FormRepository get formRepository => authenticationRepository.formRepository;
@@ -28,11 +28,14 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'base_edit_account_cubit.dart';
part 'edit_account_state.dart';
/// {@template edit_account_cubit}
/// Fully featured edit account cubit.
///
/// Sufficient in most cases. (Where fine granularity is not required.)
/// {@endtemplate}
class EditAccountCubit<Data> extends BaseEditAccountCubit<Data>
with UpdateEmail<Data>, UpdatePassword<Data> {
/// {@macro edit_account_cubit}
EditAccountCubit({required super.authenticationRepository});
@override
@@ -16,15 +16,22 @@
part of 'edit_account_cubit.dart';
/// {@template edit_account_state}
/// Edit account cubit state to manage the form.
/// {@endtemplate}
class EditAccountState extends FormDataState {
/// {@macro edit_account_state}
const EditAccountState({
required super.form,
super.status = FormStatus.pure,
super.errorMessage,
});
/// Email validator of the form
FormInputValidator<String?, ValidationError> get email =>
form.validatorOf(AuthFormField.email);
/// Password validator of the form
FormInputValidator<String?, ValidationError> get password =>
form.validatorOf(AuthFormField.password);
@@ -19,9 +19,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/features/edit_account/cubit/edit_account_cubit.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
/// {@template edit_account_listener}
/// Widget that listens and builds a child based on the state of
/// the edit account cubit
/// {@endtemplate}
class EditAccountListener<Data> extends StatelessWidget {
/// {@macro edit_account_listener}
const EditAccountListener({
required this.child,
this.onProgress,
@@ -31,15 +34,25 @@ class EditAccountListener<Data> extends StatelessWidget {
super.key,
});
/// Callback to show when the edit account is in progress
final void Function(BuildContext context)? onProgress;
/// Callback to show when the edit account is successful
final void Function(BuildContext context)? onSuccess;
/// Callback to show when the edit account is unsuccessful
final void Function(
BuildContext context,
FormStatus status,
String? errorMessage,
)? onError;
/// Custom builder to show when the edit account is in progress, successful,
/// or unsuccessful
final void Function(BuildContext context, EditAccountState state)?
customBuilder;
/// Child of the widget
final Widget child;
@override
@@ -19,7 +19,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/features/email_verification/cubit/email_verification_cubit.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
/// {@template email_verification_builder}
/// A widget that builds itself based on the latest [EmailVerificationState].
/// {@endtemplate}
class EmailVerificationBuilder<Extra> extends StatelessWidget {
/// {@macro email_verification_builder}
const EmailVerificationBuilder({
required this.verified,
required this.notVerified,
@@ -28,14 +32,21 @@ class EmailVerificationBuilder<Extra> extends StatelessWidget {
super.key,
});
/// Widget to show when the email is verified
final Widget Function(BuildContext context) verified;
/// Widget to show when the email is not verified
final Widget Function(BuildContext context) notVerified;
/// Widget to show when the email verification is unsuccessful
final Widget Function(
BuildContext context,
FormStatus status,
String? errorMessage,
) onError;
/// Custom builder to show when the email is verified, not verified, or
/// unsuccessful
final Widget Function(BuildContext context, EmailVerificationState)?
customBuilder;
@@ -23,11 +23,16 @@ import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
part 'email_verification_state.dart';
/// {@template email_verification_cubit}
/// Cubit for sending email verification.
/// {@endtemplate}
class EmailVerificationCubit<Data> extends Cubit<EmailVerificationState> {
/// {@macro email_verification_cubit}
EmailVerificationCubit({
required this.authenticationRepository,
}) : super(const EmailVerificationState());
/// The [AuthenticationRepository] used to send email verification.
final AuthenticationRepository<Data> authenticationRepository;
FutureOr<void> sendEmailVerification() async {
@@ -16,15 +16,24 @@
part of 'email_verification_cubit.dart';
/// {@template email_verification_state}
/// The state of the [EmailVerificationCubit].
/// {@endtemplate}
class EmailVerificationState extends Equatable {
/// {@macro email_verification_state}
const EmailVerificationState({
this.isVerified = false,
this.status = FormStatus.pure,
this.errorMessage,
});
/// The status of the form.
final FormStatus status;
/// Whether the email is verified.
final bool isVerified;
/// The error message if any.
final String? errorMessage;
EmailVerificationState copyWith({
@@ -24,8 +24,11 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'password_reset_state.dart';
/// {@template password_reset_cubit}
/// Cubit that allows user to reset his password
/// {@endtemplate}
class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> {
/// {@macro password_reset_cubit}
PasswordResetCubit({
required this.authenticationRepository,
}) : super(
@@ -34,6 +37,8 @@ class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> {
.accessForm(AuthFormName.passwordResetForm),
),
);
/// The repository that handles the authentication
final AuthenticationRepository<Extra> authenticationRepository;
FormRepository get formRepository => authenticationRepository.formRepository;
@@ -16,12 +16,18 @@
part of 'password_reset_cubit.dart';
/// {@template password_reset_state}
/// The state of the [PasswordResetCubit].
/// {@endtemplate}
class PasswordResetState extends FormDataState {
/// {@macro password_reset_state}
const PasswordResetState({
required super.form,
super.status = FormStatus.pure,
super.errorMessage,
});
/// The email validator of the form.
Email get email => form.validatorOf(AuthFormField.email);
PasswordResetState copyWith({
@@ -16,9 +16,12 @@
part of 'sign_in_cubit.dart';
/// Abstract sign in cubit useful for implementing a cubit with fine
/// {@template sign_in_cubit}
/// Abstract sign in cubit useful for implementing a cubit with fine
/// granularity by adding only the required mixins.
/// {@endtemplate}
abstract class BaseSignInCubit<Data> extends FormDataCubit<SignInState> {
/// {@macro sign_in_cubit}
BaseSignInCubit({
required this.authenticationRepository,
}) : super(
@@ -27,6 +30,8 @@ abstract class BaseSignInCubit<Data> extends FormDataCubit<SignInState> {
.accessForm(AuthFormName.signInForm),
),
);
/// The authentication repository.
final AuthenticationRepository<Data> authenticationRepository;
FormRepository get formRepository => authenticationRepository.formRepository;
@@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
@@ -31,14 +30,17 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'base_sign_in_cubit.dart';
part 'sign_in_state.dart';
/// {@template sign_in_cubit}
/// Fully featured sign in cubit.
///
///
/// Sufficient in most cases. (Where fine granularity is not required.)
/// {@endtemplate}
class SignInCubit<Data> extends BaseSignInCubit<Data>
with
SignInAnonymously<Data>,
SignInWithEmailPassword<Data>,
SignInWithGoogle<Data> {
/// {@macro sign_in_cubit}
SignInCubit({required super.authenticationRepository});
@override
@@ -16,15 +16,22 @@
part of 'sign_in_cubit.dart';
/// {@template sign_in_state}
/// Sign in cubit state to manage the form.
/// {@endtemplate}
class SignInState extends FormDataState {
/// {@macro sign_in_state}
const SignInState({
required super.form,
super.status = FormStatus.pure,
super.errorMessage,
});
/// Email validator of the form
FormInputValidator<String?, ValidationError> get email =>
form.validatorOf(AuthFormField.email);
/// Password validator of the form
FormInputValidator<String?, ValidationError> get password =>
form.validatorOf(AuthFormField.password);
@@ -19,9 +19,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/features/sign_in/sign_in.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
/// Widget that listens and builds a child based on the state of
/// {@template sign_in_listener}
/// Widget that listens and builds a child based on the state of
/// the sign in cubit
/// {@endtemplate}
class SignInListener<Data> extends StatelessWidget {
/// {@macro sign_in_listener}
const SignInListener({
required this.child,
this.onProgress,
@@ -31,14 +34,24 @@ class SignInListener<Data> extends StatelessWidget {
super.key,
});
/// Callback to show when the sign in is in progress
final void Function(BuildContext context)? onProgress;
/// Callback to show when the sign in is successful
final void Function(BuildContext context)? onSuccess;
/// Callback to show when the sign in is unsuccessful
final void Function(
BuildContext context,
FormStatus status,
String? errorMessage,
)? onError;
/// Custom builder to show when the sign in is in progress, successful, or
/// unsuccessful
final void Function(BuildContext context, SignInState state)? customBuilder;
/// Child of the widget
final Widget child;
@override
@@ -16,9 +16,12 @@
part of 'sign_up_cubit.dart';
/// Abstract sign up cubit useful for implementing a cubit with fine
/// {@template base_sign_up_cubit}
/// Abstract sign up cubit useful for implementing a cubit with fine
/// granularity by adding only the required mixins.
/// {@endtemplate}
abstract class BaseSignUpCubit<Data> extends FormDataCubit<SignUpState> {
/// {@macro base_sign_up_cubit}
BaseSignUpCubit({
required this.authenticationRepository,
}) : super(
@@ -27,6 +30,8 @@ abstract class BaseSignUpCubit<Data> extends FormDataCubit<SignUpState> {
.accessForm(AuthFormName.signUpForm),
),
);
/// The authentication repository.
final AuthenticationRepository<Data> authenticationRepository;
FormRepository get formRepository => authenticationRepository.formRepository;
@@ -28,11 +28,14 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'base_sign_up_cubit.dart';
part 'sign_up_state.dart';
/// {@template sign_up_cubit}
/// Fully featured sign up cubit.
///
///
/// Sufficient in most cases. (Where fine granularity is not required.)
/// {@endtemplate}
class SignUpCubit<Data> extends BaseSignUpCubit<Data>
with SignUpWithEmailPassword<Data> {
/// {@macro sign_up_cubit}
SignUpCubit({required super.authenticationRepository});
@override
@@ -16,15 +16,22 @@
part of 'sign_up_cubit.dart';
/// {@template sign_up_state}
/// Sign up cubit state to manage the form.
/// {@endtemplate}
class SignUpState extends FormDataState {
/// {@macro sign_up_state}
const SignUpState({
required super.form,
super.status = FormStatus.pure,
super.errorMessage,
});
/// Email validator of the form
FormInputValidator<String?, ValidationError> get email =>
form.validatorOf(AuthFormField.email);
/// Password validator of the form
FormInputValidator<String?, ValidationError> get password =>
form.validatorOf(AuthFormField.password);
@@ -42,7 +49,6 @@ class SignUpState extends FormDataState {
@override
List<Object?> get props => [email, password, status, form];
@override
@override
String toString() => 'SignUpState(status: ${status.name} '
'${(errorMessage != null) ? " [$errorMessage]" : ""}, $form)';
@@ -19,9 +19,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/features/sign_up/cubit/sign_up_cubit.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
/// Widget that listens and builds a child based on the state of
/// {@template sign_up_listener}
/// Widget that listens and builds a child based on the state of
/// the sign up cubit
/// {@endtemplate}
class SignUpListener<Data> extends StatelessWidget {
/// {@macro sign_up_listener}
const SignUpListener({
required this.child,
this.onProgress,
@@ -31,14 +34,24 @@ class SignUpListener<Data> extends StatelessWidget {
super.key,
});
/// Callback to show when the sign up is in progress
final void Function(BuildContext context)? onProgress;
/// Callback to show when the sign up is successful
final void Function(BuildContext context)? onSuccess;
/// Callback to show when the sign up is unsuccessful
final void Function(
BuildContext context,
FormStatus status,
String? errorMessage,
)? onError;
/// Custom builder to show when the sign up is in progress, successful, or
/// unsuccessful
final void Function(BuildContext context, SignUpState state)? customBuilder;
/// Child of the widget
final Widget child;
@override
@@ -29,6 +29,8 @@ dependencies:
wyatt_type_utils:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: ^0.0.4
flutter_secure_storage: ^8.0.0
http: ^0.13.5
dev_dependencies:
flutter_test: { sdk: flutter }
@@ -25,8 +25,8 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class MockAuthenticationRepository extends Mock
implements AuthenticationRepository<int> {}
class MockAuthenticationCubit extends Mock implements AuthenticationCubit<int> {
}
class MockAuthenticationCubit extends Mock
implements AuthenticationCubit<int> {}
class MockAccount extends Mock implements Account {}
@@ -25,8 +25,8 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class MockAuthenticationRepository extends Mock
implements AuthenticationRepository<int> {}
class MockAuthenticationCubit extends Mock implements AuthenticationCubit<int> {
}
class MockAuthenticationCubit extends Mock
implements AuthenticationCubit<int> {}
class MockAccount extends Mock implements Account {}