Compare commits

...
Author SHA1 Message Date
hugo dffa91eb23 chore(release): publish packages
- wyatt_medium_feeds@1.0.1
2022-05-05 21:29:48 +02:00
hugo 164877bb0b fix(medium): remove useless example file 2022-05-05 21:29:25 +02:00
hugo c787b065ad chore(medium): add new package 2022-05-05 21:26:04 +02:00
hugo 12f9cf6aa5 feat(auth): add google, facebook, apple, twitter login 2022-05-04 22:50:07 +02:00
hugo 74db784973 chore(release): publish packages
- wyatt_form_bloc@0.0.2
 - wyatt_authentication_bloc@0.2.0+1
2022-05-02 14:44:17 +02:00
hugo fe5fa692f7 feat(form): add list option validator 2022-05-02 14:41:24 +02:00
hugo 5ea5c510cb chore(release): publish packages
- wyatt_crud_bloc@0.0.2
 - wyatt_analysis@2.0.1
2022-04-20 23:12:12 +02:00
hugo bb264b7362 docs: update readme with some instructions 2022-04-20 23:08:59 +02:00
hugo 17ae855ffb docs(crud): add example app 2022-04-20 23:08:37 +02:00
hugo b38fea130f feat(crud) : new package for crud operations 2022-04-20 22:57:14 +02:00
hugo e3776259df style(auth): update analysis options 2022-04-20 22:54:00 +02:00
110 changed files with 3651 additions and 122 deletions
+77
View File
@@ -3,6 +3,83 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## 2022-05-05
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`wyatt_medium_feeds` - `v1.0.1`](#wyatt_medium_feeds---v101)
---
#### `wyatt_medium_feeds` - `v1.0.1`
- **FIX**: remove useless example file.
## 2022-05-02
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`wyatt_form_bloc` - `v0.0.2`](#wyatt_form_bloc---v002)
- [`wyatt_authentication_bloc` - `v0.2.0+1`](#wyatt_authentication_bloc---v0201)
Packages with dependency updates only:
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
- `wyatt_authentication_bloc` - `v0.2.0+1`
---
#### `wyatt_form_bloc` - `v0.0.2`
- **FEAT**: add list option validator.
## 2022-04-20
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`wyatt_crud_bloc` - `v0.0.2`](#wyatt_crud_bloc---v002)
- [`wyatt_analysis` - `v2.0.1`](#wyatt_analysis---v201)
---
#### `wyatt_crud_bloc` - `v0.0.2`
- Migrate crud_package in monorepo
- **DOCS**: add example app.
#### `wyatt_analysis` - `v2.0.1`
- **DOCS**: Update readme with new version.
## 2022-04-20 ## 2022-04-20
### Changes ### Changes
+62 -12
View File
@@ -63,22 +63,55 @@ dart pub global activate melos
Then bootstrap with `melos bs`. Then bootstrap with `melos bs`.
--- ### Create a new package
## Usage Create a new package in `packages/` folder.
You can add any package of the `packages/` sub directory in your project. ```shell
dart create -t package-simple wyatt_<name>
```yaml
dependencies:
wyatt_analysis:
git:
url: ssh://git@git.wyatt-studio.fr:993/Wyatt-FOSS/wyatt-packages.git
ref: wyatt_analysis-v2.0.0
path: packages/wyatt_analysis
``` ```
Here you can change `package name` and `package version`. Remove any `example/` subfolder. Then create a new sample project.
```shell
flutter create --platforms android --project-name <name>_example example
```
Then bootstrap project with `melos bs` command.
### Convention
#### Naming
In the previous instructions `<name>` variable is important.
It have to be clear and intelligible.
You **MUST** use underscores.
You **MUST** use `wyatt` prefix for package.
You **MUST** name example with specific name.
For example, if name is CRUD BLOC
- name will be crud_bloc
- so the package will be: `wyatt_crud_bloc`
- and the example will be: `crud_bloc_example`
#### Commits
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
tl;dr : `type(scope): description` where type can be **feat, fix, docs, chores, ci, perf, refactor, style, test**, and scope is package name.
examples:
- `feat(auth): add AWS support.` = add a feature in authentication_bloc package.
- `docs: update readme.` = update **this** readme file.
- `fix(crud)!: fix bug in awesome() function.` = fix a bug, `!` is important and indicate `BREAKING CHANGES`.
Conventional commits are important for `melos version` command !
#### Badging
In the package `readme.md` file, please specify the supported SDK: In the package `readme.md` file, please specify the supported SDK:
@@ -97,6 +130,23 @@ or
--- ---
## Usage
You can add any package of the `packages/` sub directory in your project.
```yaml
dependencies:
wyatt_analysis:
git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_analysis-v2.0.0
path: packages/wyatt_analysis
```
Here you can change `package name` and `package version`.
---
## Status ## Status
![Status: Experimental](https://img.shields.io/badge/Status-WIP-red?style=flat-square) ![Status: Experimental](https://img.shields.io/badge/Status-WIP-red?style=flat-square)
+4
View File
@@ -1,3 +1,7 @@
## 2.0.1
- **DOCS**: Update readme with new version.
## 2.0.0 ## 2.0.0
> Note: This release has breaking changes. > Note: This release has breaking changes.
+1 -1
View File
@@ -1,7 +1,7 @@
name: wyatt_analysis name: wyatt_analysis
description: Lint rules for Dart and Flutter used internally at Wyatt and Wyatt Studio. description: Lint rules for Dart and Flutter used internally at Wyatt and Wyatt Studio.
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis
version: 2.0.0 version: 2.0.1
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
@@ -1,3 +1,7 @@
## 0.2.0+1
- Update a dependency to the latest release.
## 0.2.0 ## 0.2.0
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries. - Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
@@ -1,6 +1 @@
include: package:wyatt_analysis/analysis_options.flutter.1.1.1.yaml include: package:wyatt_analysis/analysis_options.flutter.yaml
linter:
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
@@ -42,6 +42,42 @@
} }
} }
}, },
{
"client_info": {
"mobilesdk_app_id": "1:136771801992:android:8482c9b90bc29de697203d",
"android_client_info": {
"package_name": "com.example.crud_bloc_example"
}
},
"oauth_client": [
{
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.example.example"
}
}
]
}
}
},
{ {
"client_info": { "client_info": {
"mobilesdk_app_id": "1:136771801992:android:d20e0361057e815197203d", "mobilesdk_app_id": "1:136771801992:android:d20e0361057e815197203d",
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:developer';
import 'package:authentication_bloc_example/constants.dart'; import 'package:authentication_bloc_example/constants.dart';
import 'package:authentication_bloc_example/home/home_page.dart'; import 'package:authentication_bloc_example/home/home_page.dart';
import 'package:authentication_bloc_example/login/login_page.dart'; import 'package:authentication_bloc_example/login/login_page.dart';
@@ -55,14 +57,32 @@ class App extends StatelessWidget {
if (user.isNotEmpty && !user.isAnonymous) { if (user.isNotEmpty && !user.isAnonymous) {
// Check if user is register in Firesore. // Check if user is register in Firesore.
DocumentSnapshot firestoreUser = await FirebaseFirestore.instance DocumentSnapshot firestoreUser = await FirebaseFirestore.instance
.collection('users') .collection(firestoreCollectionUsers)
.doc(user.uid) .doc(user.uid)
.get(); .get();
if (!firestoreUser.exists) {
// Register user in Firestore when sign in with social account.
final uid = user.uid;
final u = {'uid': uid, 'email': user.email};
await FirebaseFirestore.instance
.collection(firestoreCollectionUsers)
.doc(uid)
.set(u);
return { return {
'user': 'user': UserFirestore(
UserFirestore.fromMap(firestoreUser.data() as Map<String, dynamic>), uid: uid,
email: user.email ?? '',
name: user.displayName ?? '',
phone: user.phoneNumber ?? ''),
};
} else {
return {
'user': UserFirestore.fromMap(
firestoreUser.data() as Map<String, dynamic>),
...firestoreUser.data() as Map<String, dynamic>? ?? {} ...firestoreUser.data() as Map<String, dynamic>? ?? {}
}; };
}
} else { } else {
return {}; return {};
} }
@@ -73,7 +93,11 @@ class App extends StatelessWidget {
if (uid != null) { if (uid != null) {
final data = state.data.toMap(); final data = state.data.toMap();
final user = {'uid': uid, 'email': state.email.value, ...data}; final user = {'uid': uid, 'email': state.email.value, ...data};
await FirebaseFirestore.instance.collection('users').doc(uid).set(user); log('onSignUpSuccess: $user');
await FirebaseFirestore.instance
.collection(firestoreCollectionUsers)
.doc(uid)
.set(user);
} }
} }
@@ -20,3 +20,5 @@ const String formFieldPro = 'isPro';
const String formFieldConfirmedPassword = 'confirmedPassword'; const String formFieldConfirmedPassword = 'confirmedPassword';
const String formFieldSiren = 'siren'; const String formFieldSiren = 'siren';
const String formFieldIban = 'iban'; const String formFieldIban = 'iban';
const String firestoreCollectionUsers = 'authentication_bloc_users';
@@ -102,6 +102,24 @@ class _LoginWithPasswordButton extends StatelessWidget {
} }
} }
class _LoginWithGoogleButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
buildWhen: (previous, current) => previous.status != current.status,
builder: (context, state) {
return state.status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () =>
context.read<SignInCubit>().signInWithGoogle(),
child: const Text('LOGIN GOOGLE'),
);
},
);
}
}
class _SignUpButton extends StatelessWidget { class _SignUpButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -182,6 +200,8 @@ class LoginForm extends StatelessWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
_LoginAnonButton(), _LoginAnonButton(),
const SizedBox(height: 8), const SizedBox(height: 8),
_LoginWithGoogleButton(),
const SizedBox(height: 8),
_SignUpButton(), _SignUpButton(),
const SizedBox(height: 8), const SizedBox(height: 8),
_SignUpAsProButton(), _SignUpAsProButton(),
@@ -255,7 +255,8 @@ class _DebugButton extends StatelessWidget {
builder: (context, state) { builder: (context, state) {
return ElevatedButton( return ElevatedButton(
onPressed: () { onPressed: () {
log(state.toString()); // log(state.toString());
log(state.data.toMap().toString());
}, },
child: const Text('DEBUG'), child: const Text('DEBUG'),
); );
@@ -38,7 +38,7 @@ dependencies:
wyatt_form_bloc: wyatt_form_bloc:
git: git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_form_bloc-v0.0.1 ref: wyatt_form_bloc-v0.0.2
path: packages/wyatt_form_bloc path: packages/wyatt_form_bloc
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
@@ -106,10 +106,12 @@ class SignInAnonymouslyFailureFirebase
} }
} }
class SignInWithGoogleFailureFirebase extends SignInWithGoogleFailureInterface { class SignInWithCredentialFailureFirebase
SignInWithGoogleFailureFirebase([String? code, String? message]) extends SignInWithCredentialFailureInterface {
SignInWithCredentialFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.'); : super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SignInWithGoogleFailureFirebase.fromCode(String code) : super.fromCode(code) { SignInWithCredentialFailureFirebase.fromCode(String code)
: super.fromCode(code) {
switch (code) { switch (code) {
case 'account-exists-with-different-credential': case 'account-exists-with-different-credential':
message = 'Account exists with different credentials.'; message = 'Account exists with different credentials.';
@@ -143,6 +145,38 @@ class SignInWithGoogleFailureFirebase extends SignInWithGoogleFailureInterface {
} }
} }
class SignInWithGoogleFailureFirebase
extends SignInWithCredentialFailureFirebase
implements SignInWithGoogleFailureInterface {
SignInWithGoogleFailureFirebase([String? code, String? message])
: super(code, message);
SignInWithGoogleFailureFirebase.fromCode(String code) : super.fromCode(code);
}
class SignInWithFacebookFailureFirebase
extends SignInWithCredentialFailureFirebase
implements SignInWithFacebookFailureInterface {
SignInWithFacebookFailureFirebase([String? code, String? message])
: super(code, message);
SignInWithFacebookFailureFirebase.fromCode(String code)
: super.fromCode(code);
}
class SignInWithAppleFailureFirebase extends SignInWithCredentialFailureFirebase
implements SignInWithAppleFailureInterface {
SignInWithAppleFailureFirebase([String? code, String? message])
: super(code, message);
SignInWithAppleFailureFirebase.fromCode(String code) : super.fromCode(code);
}
class SignInWithTwitterFailureFirebase extends SignInWithCredentialFailureFirebase
implements SignInWithAppleFailureInterface {
SignInWithTwitterFailureFirebase([String? code, String? message])
: super(code, message);
SignInWithTwitterFailureFirebase.fromCode(String code) : super.fromCode(code);
}
class SignInWithEmailLinkFailureFirebase class SignInWithEmailLinkFailureFirebase
extends SignInWithEmailLinkFailureInterface { extends SignInWithEmailLinkFailureInterface {
SignInWithEmailLinkFailureFirebase([String? code, String? message]) SignInWithEmailLinkFailureFirebase([String? code, String? message])
@@ -64,6 +64,20 @@ abstract class FetchSignInMethodsForEmailFailureInterface
: super.fromCode(code); : super.fromCode(code);
} }
/// {@template sign_in_with_credential_failure}
/// Thrown during the sign in process if a failure occurs.
/// {@endtemplate}
abstract class SignInWithCredentialFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_in_with_credential_failure}
SignInWithCredentialFailureInterface(String code, String message)
: super(code, message);
/// {@macro sign_in_with_credential_failure}
SignInWithCredentialFailureInterface.fromCode(String code)
: super.fromCode(code);
}
/// {@template sign_in_anonymously_failure} /// {@template sign_in_anonymously_failure}
/// Thrown during the sign in process if a failure occurs. /// Thrown during the sign in process if a failure occurs.
/// {@endtemplate} /// {@endtemplate}
@@ -91,6 +105,47 @@ abstract class SignInWithGoogleFailureInterface
SignInWithGoogleFailureInterface.fromCode(String code) : super.fromCode(code); SignInWithGoogleFailureInterface.fromCode(String code) : super.fromCode(code);
} }
/// {@template sign_in_with_facebook_failure}
/// Thrown during the sign in process if a failure occurs.
/// {@endtemplate}
abstract class SignInWithFacebookFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_in_with_facebook_failure}
SignInWithFacebookFailureInterface(String code, String message)
: super(code, message);
/// {@macro sign_in_with_facebook_failure}
SignInWithFacebookFailureInterface.fromCode(String code)
: super.fromCode(code);
}
/// {@template sign_in_with_apple_failure}
/// Thrown during the sign in process if a failure occurs.
/// {@endtemplate}
abstract class SignInWithAppleFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_in_with_apple_failure}
SignInWithAppleFailureInterface(String code, String message)
: super(code, message);
/// {@macro sign_in_with_apple_failure}
SignInWithAppleFailureInterface.fromCode(String code) : super.fromCode(code);
}
/// {@template sign_in_with_twitter_failure}
/// Thrown during the sign in process if a failure occurs.
/// {@endtemplate}
abstract class SignInWithTwitterFailureInterface
extends AuthenticationFailureInterface {
/// {@macro sign_in_with_twitter_failure}
SignInWithTwitterFailureInterface(String code, String message)
: super(code, message);
/// {@macro sign_in_with_twitter_failure}
SignInWithTwitterFailureInterface.fromCode(String code)
: super.fromCode(code);
}
/// {@template sign_in_with_email_link_failure} /// {@template sign_in_with_email_link_failure}
/// Thrown during the sign in process if a failure occurs. /// Thrown during the sign in process if a failure occurs.
/// {@endtemplate} /// {@endtemplate}
@@ -64,6 +64,9 @@ class UserFirebase implements UserInterface {
@override @override
String get uid => _user?.uid ?? ''; String get uid => _user?.uid ?? '';
@override
String? get providerId => _user?.providerData.first.providerId;
@override @override
bool? get isNewUser { bool? get isNewUser {
if (_user?.metadata.lastSignInTime == null || if (_user?.metadata.lastSignInTime == null ||
@@ -71,6 +71,9 @@ abstract class UserInterface {
/// The user's unique ID. /// The user's unique ID.
String get uid; String get uid;
/// The provider ID for the user.
String? get providerId;
/// Whether the user account has been recently created. /// Whether the user account has been recently created.
bool? get isNewUser; bool? get isNewUser;
@@ -15,19 +15,28 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:twitter_login/twitter_login.dart';
import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_firebase.dart'; import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_firebase.dart';
import 'package:wyatt_authentication_bloc/src/models/user/user_firebase.dart'; import 'package:wyatt_authentication_bloc/src/models/user/user_firebase.dart';
import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart'; import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart';
import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart'; import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart';
import 'package:wyatt_authentication_bloc/src/utils/cryptography.dart';
class AuthenticationRepositoryFirebase class AuthenticationRepositoryFirebase
implements AuthenticationRepositoryInterface { implements AuthenticationRepositoryInterface {
final FirebaseAuth _firebaseAuth; final FirebaseAuth _firebaseAuth;
final TwitterLogin? _twitterLogin;
UserFirebase _userCache = const UserFirebase.empty(); UserFirebase _userCache = const UserFirebase.empty();
AuthenticationRepositoryFirebase({FirebaseAuth? firebaseAuth}) AuthenticationRepositoryFirebase({
: _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance; FirebaseAuth? firebaseAuth,
TwitterLogin? twitterLogin,
}) : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance,
_twitterLogin = twitterLogin;
@override @override
Stream<UserInterface> get user { Stream<UserInterface> get user {
@@ -99,9 +108,106 @@ class AuthenticationRepositoryFirebase
} }
@override @override
Future<void> signInWithGoogle() { Future<void> signInWithGoogle() async {
// TODO(hpcl): implement signInWithGoogle // Trigger the authentication flow
throw UnimplementedError(); final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication? googleAuth =
await googleUser?.authentication;
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
try {
await _firebaseAuth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw SignInWithGoogleFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithGoogleFailureFirebase();
}
}
@override
Future<void> signInWithFacebook() async {
// Trigger the sign-in flow
final LoginResult loginResult = await FacebookAuth.instance.login();
// Create a credential from the access token
final OAuthCredential credential =
FacebookAuthProvider.credential(loginResult.accessToken?.token ?? '');
try {
await _firebaseAuth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw SignInWithFacebookFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithFacebookFailureFirebase();
}
}
@override
Future<void> signInWithApple() async {
// To prevent replay attacks with the credential returned from Apple, we
// include a nonce in the credential request. When signing in with
// Firebase, the nonce in the id token returned by Apple, is expected to
// match the sha256 hash of `rawNonce`.
final rawNonce = Cryptography.generateNonce();
final nonce = Cryptography.sha256ofString(rawNonce);
// Request credential for the currently signed in Apple account.
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
// Create an `OAuthCredential` from the credential returned by Apple.
final credential = OAuthProvider('apple.com').credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
// Sign in the user with Firebase. If the nonce we generated earlier does
// not match the nonce in `appleCredential.identityToken`,
// sign in will fail.
try {
await _firebaseAuth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw SignInWithAppleFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithAppleFailureFirebase();
}
}
@override
Future<void> signInWithTwitter() async {
final twitterLogin = _twitterLogin;
if (twitterLogin == null) {
throw SignInWithTwitterFailureFirebase();
}
// Trigger the sign-in flow
final authResult = await twitterLogin.login();
// Create a credential from the access token
final credential = TwitterAuthProvider.credential(
accessToken: authResult.authToken!,
secret: authResult.authTokenSecret!,
);
try {
await _firebaseAuth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw SignInWithCredentialFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithCredentialFailureFirebase();
}
} }
@override @override
@@ -61,6 +61,21 @@ abstract class AuthenticationRepositoryInterface {
/// Throws a [SignInWithGoogleFailureInterface] if an exception occurs. /// Throws a [SignInWithGoogleFailureInterface] if an exception occurs.
Future<void> signInWithGoogle(); Future<void> signInWithGoogle();
/// Starts the Sign In with Facebook Flow.
///
/// Throws a [SignInWithFacebookFailureInterface] if an exception occurs.
Future<void> signInWithFacebook();
/// Starts the Sign In with Apple Flow.
///
/// Throws a [SignInWithAppleFailureInterface] if an exception occurs.
Future<void> signInWithApple();
/// Starts the Sign In with Twitter Flow.
///
/// Throws a [SignInWithTwitterFailureInterface] if an exception occurs.
Future<void> signInWithTwitter();
/// Signs in using an email address and email sign-in link. /// Signs in using an email address and email sign-in link.
/// ///
/// Throws a [SignInWithEmailLinkFailureInterface] if an exception occurs. /// Throws a [SignInWithEmailLinkFailureInterface] if an exception occurs.
@@ -76,6 +76,28 @@ class SignInCubit extends Cubit<SignInState> {
} }
} }
Future<void> signInWithGoogle() async {
if (state.status.isSubmissionInProgress) {
return;
}
emit(state.copyWith(status: FormStatus.submissionInProgress));
try {
await _authenticationRepository.signInWithGoogle();
_authenticationCubit.start();
emit(state.copyWith(status: FormStatus.submissionSuccess));
} on SignInWithGoogleFailureInterface catch (e) {
emit(
state.copyWith(
errorMessage: e.message,
status: FormStatus.submissionFailure,
),
);
} catch (_) {
emit(state.copyWith(status: FormStatus.submissionFailure));
}
}
Future<void> signInWithEmailAndPassword() async { Future<void> signInWithEmailAndPassword() async {
if (!state.status.isValidated) return; if (!state.status.isValidated) return;
emit(state.copyWith(status: FormStatus.submissionInProgress)); emit(state.copyWith(status: FormStatus.submissionInProgress));
@@ -45,4 +45,14 @@ class SignInState extends Equatable {
@override @override
List<Object> get props => [email, password, status]; List<Object> get props => [email, password, status];
@override
String toString() {
return '''
email: $email,
password: $password,
status: $status,
errorMessage: $errorMessage,
''';
}
} }
@@ -0,0 +1,39 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
class Cryptography {
/// Generates a cryptographically secure random nonce, to be included in a
/// credential request.
static String generateNonce([int length = 32]) {
const charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();
return List.generate(length, (_) => charset[random.nextInt(charset.length)])
.join();
}
/// Returns the sha256 hash of [input] in hex notation.
static String sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}
}
@@ -1,7 +1,7 @@
name: wyatt_authentication_bloc name: wyatt_authentication_bloc
description: Authentication BLoC for Flutter description: Authentication BLoC for Flutter
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_authentication_bloc repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_authentication_bloc
version: 0.2.0 version: 0.2.0+1
environment: environment:
sdk: ">=2.15.1 <3.0.0" sdk: ">=2.15.1 <3.0.0"
@@ -11,14 +11,19 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
crypto: ^3.0.2
flutter_bloc: ^8.0.1 flutter_bloc: ^8.0.1
equatable: ^2.0.3 equatable: ^2.0.3
firebase_auth: ^3.3.14 firebase_auth: ^3.3.17
google_sign_in: ^5.3.0
flutter_facebook_auth: ^4.3.0
sign_in_with_apple: ^3.3.0
twitter_login: ^4.2.3
wyatt_form_bloc: wyatt_form_bloc:
git: git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_form_bloc-v0.0.1 ref: wyatt_form_bloc-v0.0.2
path: packages/wyatt_form_bloc path: packages/wyatt_form_bloc
@@ -30,5 +35,5 @@ dev_dependencies:
wyatt_analysis: wyatt_analysis:
git: git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_analysis-v2.0.0 ref: wyatt_analysis-v2.0.1
path: packages/wyatt_analysis path: packages/wyatt_analysis
+10
View File
@@ -0,0 +1,10 @@
# Files and directories created by pub.
.dart_tool/
.packages
# Conventional directory for build outputs.
build/
# Omit committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock
+9
View File
@@ -0,0 +1,9 @@
## 0.0.2
- Migrate crud_package in monorepo
- **DOCS**: add example app.
## 1.0.0
- Initial version.
+39
View File
@@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
```dart
const like = 'sample';
```
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.
@@ -0,0 +1 @@
include: package:wyatt_analysis/analysis_options.flutter.yaml
@@ -0,0 +1,48 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
/lib/firebase_options.dart
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 5464c5bac742001448fe4fc0597be939379f88ea
channel: stable
project_type: app
@@ -0,0 +1,16 @@
# crud_bloc_example
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://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
@@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
@@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks
@@ -0,0 +1,71 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
// START: FlutterFire Configuration
apply plugin: 'com.google.gms.google-services'
// END: FlutterFire Configuration
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion flutter.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.crud_bloc_example"
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
@@ -0,0 +1,135 @@
{
"project_info": {
"project_number": "136771801992",
"firebase_url": "https://tchat-beta.firebaseio.com",
"project_id": "tchat-beta",
"storage_bucket": "tchat-beta.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:136771801992:android:4ff419f07afdad6097203d",
"android_client_info": {
"package_name": "com.example.authentication_bloc_example"
}
},
"oauth_client": [
{
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.example.example"
}
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:136771801992:android:8482c9b90bc29de697203d",
"android_client_info": {
"package_name": "com.example.crud_bloc_example"
}
},
"oauth_client": [
{
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.example.example"
}
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:136771801992:android:d20e0361057e815197203d",
"android_client_info": {
"package_name": "com.example.example"
}
},
"oauth_client": [
{
"client_id": "136771801992-n2pq8oqutvrqj58e05hbavvc7n1jdfjb.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.example.example",
"certificate_hash": "5d8790309e13b68c35e5d4d8437c35c6d15e6131"
}
},
{
"client_id": "136771801992-pj9s88ao7d99pnp4ace75nj1abtmoq7e.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.example.example",
"certificate_hash": "bc8faf31c0b0085dc358b2319684e10c93f42719"
}
},
{
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.example.example"
}
}
]
}
}
}
],
"configuration_version": "1"
}
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.crud_bloc_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.crud_bloc_example">
<application
android:label="crud_bloc_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
@@ -0,0 +1,6 @@
package com.example.crud_bloc_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.crud_bloc_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,34 @@
buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
// START: FlutterFire Configuration
classpath 'com.google.gms:google-services:4.3.10'
// END: FlutterFire Configuration
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
@@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
@@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
@@ -0,0 +1,168 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:math';
import 'package:crud_bloc_example/models.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_crud_bloc/wyatt_crud_bloc.dart';
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
final _userRepository = CrudRepositoryFirestore<UserFirestore>(
'users_crud',
UserFirestore.parser(),
);
final _userCubit = CrudCubit<UserFirestore>(_userRepository);
return RepositoryProvider<CrudRepositoryFirestore<UserFirestore>>(
create: (context) => _userRepository,
child: BlocProvider<CrudCubit<UserFirestore>>(
create: (context) => _userCubit,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 20),
const Text("Data:"),
CrudStreamBuilder<UserFirestore>(
onError: (context, state) => const SizedBox.shrink(),
onLoading: (context, state) => const Text("Loading..."),
onStream: (context, data) {
return ListView.builder(
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (context, index) {
final user = data.elementAt(index);
return ListTile(
title: Text(user?.name ?? 'Error'),
subtitle: Text(user?.id ?? 'Error'),
onTap: () {
context.read<CrudCubit<UserFirestore>>().get(
(user?.id)!,
);
},
onLongPress: () {
context.read<CrudCubit<UserFirestore>>().delete(
(user?.id)!,
);
},
);
},
);
},
),
const SizedBox(height: 20),
BlocBuilder<CrudCubit<UserFirestore>, CrudState<UserFirestore>>(
builder: (context, state) {
return Center(
child: Text(
state.toString(),
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 20,
color: Colors.grey,
),
),
);
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
final r = Random().nextInt(1000);
context.read<CrudCubit<UserFirestore>>().create(
UserFirestore(
name: 'Wyatt $r',
email: '$r@wyattapp.io',
phone: '06$r'),
);
},
child: const Text("Create"),
),
ElevatedButton(
onPressed: () {
context.read<CrudCubit<UserFirestore>>().deleteAll();
},
child: const Text("DeleteAll"),
),
ElevatedButton(
onPressed: () {
context.read<CrudCubit<UserFirestore>>().getAll();
},
child: const Text("GetAll"),
),
ElevatedButton(
onPressed: () {
context
.read<CrudCubit<UserFirestore>>()
.query([LimitQueryFirestore(1)]);
},
child: const Text("Query"),
),
ElevatedButton(
onPressed: () {
context.read<CrudCubit<UserFirestore>>().streamOf();
},
child: const Text("StreamOf"),
),
ElevatedButton(
onPressed: () {
context
.read<CrudCubit<UserFirestore>>()
.updateAll({'updated': DateTime.now()});
},
child: const Text("UpdateAll"),
),
ElevatedButton(
onPressed: () {
context.read<CrudCubit<UserFirestore>>().reset();
},
child: const Text("Reset"),
),
const SizedBox(height: 20),
],
),
),
);
}
}
@@ -0,0 +1,29 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:crud_bloc_example/app.dart';
import 'package:crud_bloc_example/firebase_options.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
@@ -0,0 +1,66 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:wyatt_crud_bloc/wyatt_crud_bloc.dart';
class UserFirestore extends Model<DocumentSnapshot, UserFirestore> {
@override
String? id;
String? name;
String? email;
String? phone;
UserFirestore({
required this.name,
required this.email,
required this.phone,
this.id,
});
UserFirestore._();
factory UserFirestore.parser() {
return UserFirestore._();
}
@override
UserFirestore? from(DocumentSnapshot? object) {
if (object == null) return null;
if (object.exists) {
return UserFirestore(
id: object.id,
name: (object.data() as Map<String, dynamic>?)!['name'] as String,
email: (object.data() as Map<String, dynamic>?)!['email'] as String,
phone: (object.data() as Map<String, dynamic>?)!['phone'] as String,
);
}
return null;
}
@override
Map<String, Object> toMap() {
return {
'name': name ?? '',
'email': email ?? '',
'phone': phone ?? '',
};
}
@override
String toString() =>
'UserFirestore(id: $id, name: $name, email: $email, phone: $phone)';
}
@@ -0,0 +1,94 @@
name: crud_bloc_example
description: A new Flutter project.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
environment:
sdk: ">=2.16.2 <3.0.0"
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
cloud_firestore: ^3.1.12
firebase_core: ^1.14.1
flutter_bloc: ^8.0.1
wyatt_crud_bloc:
path: "../"
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^1.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
@@ -0,0 +1,18 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'crud_builder.dart';
export 'crud_stream_builder.dart';
@@ -0,0 +1,53 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_crud_bloc/src/crud/cubit/crud_cubit.dart';
import 'package:wyatt_crud_bloc/src/models/model.dart';
class CrudBuilder<T extends Model<Object?, T>> extends StatelessWidget {
const CrudBuilder({
Key? key,
this.onIdle,
required this.onLoading,
required this.onError,
required this.onSuccess,
}) : super(key: key);
final Widget Function(BuildContext, CrudState<T>)? onIdle;
final Widget Function(BuildContext, CrudState<T>) onLoading;
final Widget Function(BuildContext, CrudState<T>) onError;
final Widget Function(BuildContext, CrudState<T>) onSuccess;
@override
Widget build(BuildContext context) {
return BlocBuilder<CrudCubit<T>, CrudState<T>>(
builder: (context, state) {
switch (state.status) {
case CrudStatus.idle:
return onIdle?.call(context, state) ?? onError.call(context, state);
case CrudStatus.loading:
return onLoading.call(context, state);
case CrudStatus.failure:
return onError.call(context, state);
case CrudStatus.success:
return onSuccess.call(context, state);
}
},
);
}
}
@@ -0,0 +1,55 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_crud_bloc/src/crud/cubit/crud_cubit.dart';
import 'package:wyatt_crud_bloc/src/models/model.dart';
class CrudStreamBuilder<T extends Model<Object?, T>> extends StatelessWidget {
const CrudStreamBuilder({
Key? key,
required this.onLoading,
required this.onError,
required this.onStream,
}) : super(key: key);
final Widget Function(BuildContext, List<T?>) onStream;
final Widget Function(BuildContext, CrudState<T>) onLoading;
final Widget Function(BuildContext, CrudState<T>) onError;
@override
Widget build(BuildContext context) {
return BlocBuilder<CrudCubit<T>, CrudState<T>>(
builder: (context, state) {
if (state.stream != null) {
return StreamBuilder<List<T?>>(
stream: state.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return onStream(context, snapshot.data!);
} else {
return onLoading(context, state);
}
},
);
} else {
return onError(context, state);
}
},
);
}
}
@@ -0,0 +1,18 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'builder/builder.dart';
export 'cubit/crud_cubit.dart';
@@ -0,0 +1,219 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:wyatt_crud_bloc/src/models/model.dart';
import 'package:wyatt_crud_bloc/src/models/queries/queries_interface.dart';
import 'package:wyatt_crud_bloc/src/repositories/crud_repository_interface.dart';
part 'crud_state.dart';
class CrudCubit<T extends Model<Object?, T>> extends Cubit<CrudState<T>> {
final CrudRepositoryInterface<T> _crudRepository;
// ignore: prefer_const_constructors
CrudCubit(this._crudRepository) : super(CrudState());
// Here we can't use `const` because we need the generic type T
void reset() {
// ignore: prefer_const_constructors
emit(CrudState());
// Same here, because of `const` we can't use T generic type
}
Future<void> create(Model object, {String? id}) async {
emit(state.copyWith(status: CrudStatus.loading));
try {
await _crudRepository.create(object, id: id);
final data = state.data..addAll([object].cast<T>());
emit(
state.copyWith(
data: data,
status: CrudStatus.success,
),
);
} catch (e) {
// TODO(hpcl): implement Exception
emit(
state.copyWith(
status: CrudStatus.failure,
errorMessage: e.toString(),
),
);
}
}
Future<void> delete(String id) async {
emit(state.copyWith(status: CrudStatus.loading));
try {
await _crudRepository.delete(id);
final data = state.data
..removeWhere((element) => element!.id != null && element.id == id);
emit(
state.copyWith(
data: data,
status: CrudStatus.success,
),
);
} catch (e) {
emit(
state.copyWith(
status: CrudStatus.failure,
errorMessage: e.toString(),
),
);
}
}
Future<void> deleteAll() async {
emit(state.copyWith(status: CrudStatus.loading));
try {
await _crudRepository.deleteAll();
emit(
state.copyWith(
data: [],
status: CrudStatus.success,
),
);
} catch (e) {
emit(
state.copyWith(
status: CrudStatus.failure,
errorMessage: e.toString(),
),
);
}
}
Future<void> get(String id) async {
emit(state.copyWith(status: CrudStatus.loading));
try {
final data = await _crudRepository.get(id);
emit(
state.copyWith(
data: [data].cast<T>(),
status: CrudStatus.success,
),
);
} catch (e) {
emit(
state.copyWith(
status: CrudStatus.failure,
errorMessage: e.toString(),
),
);
}
}
Future<void> getAll() async {
emit(state.copyWith(status: CrudStatus.loading));
try {
final data = await _crudRepository.getAll();
emit(
state.copyWith(
data: data.cast<T>(),
status: CrudStatus.success,
),
);
} catch (e) {
emit(
state.copyWith(
status: CrudStatus.failure,
errorMessage: e.toString(),
),
);
}
}
Future<void> query(List<QueryInterface> conditions) async {
emit(state.copyWith(status: CrudStatus.loading));
try {
final data = await _crudRepository.query(conditions);
emit(
state.copyWith(
data: data.cast<T>(),
status: CrudStatus.success,
),
);
} catch (e) {
emit(
state.copyWith(
status: CrudStatus.failure,
errorMessage: e.toString(),
),
);
}
}
Future<void> streamOf({String? id}) async {
emit(state.copyWith(status: CrudStatus.loading));
try {
final Stream<List<T?>> data = _crudRepository.stream(id: id);
emit(
state.copyWith(
stream: data,
status: CrudStatus.success,
),
);
} catch (e) {
emit(
state.copyWith(
status: CrudStatus.failure,
errorMessage: e.toString(),
),
);
}
}
Future<void> update(
String id, {
Model? object,
Map<String, dynamic>? raw,
}) async {
emit(state.copyWith(status: CrudStatus.loading));
try {
await _crudRepository.update(
id,
object: object,
raw: raw,
);
emit(state.copyWith(status: CrudStatus.success));
} catch (e) {
emit(
state.copyWith(
status: CrudStatus.failure,
errorMessage: e.toString(),
),
);
}
}
Future<void> updateAll(Map<String, Object?> raw) async {
emit(state.copyWith(status: CrudStatus.loading));
try {
await _crudRepository.updateAll(raw);
emit(state.copyWith(status: CrudStatus.success));
} catch (e) {
emit(
state.copyWith(
status: CrudStatus.failure,
errorMessage: e.toString(),
),
);
}
}
}
@@ -0,0 +1,60 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
part of 'crud_cubit.dart';
enum CrudStatus {
idle,
loading,
success,
failure,
}
class CrudState<T> extends Equatable {
final CrudStatus status;
final List<T?> data;
final Stream<List<T?>>? stream;
final String? errorMessage;
const CrudState({
this.status = CrudStatus.idle,
this.data = const [],
this.stream,
this.errorMessage,
});
@override
List<Object?> get props => [status, data];
CrudState<T> copyWith({
CrudStatus? status,
List<T?>? data,
Stream<List<T?>>? stream,
String? errorMessage,
}) {
return CrudState<T>(
status: status ?? this.status,
data: data ?? this.data,
stream: stream ?? this.stream,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
String toString() =>
// ignore: lines_longer_than_80_chars
'CrudState(status: $status, data: $data, stream: ${stream != null ? 'listening' : 'null'}, errorMessage: $errorMessage)';
}
@@ -0,0 +1,21 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
abstract class Model<O, T> {
String? get id;
Map<String, Object> toMap();
T? from(O? object);
}
@@ -0,0 +1,18 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'model.dart';
export 'queries/queries.dart';
@@ -0,0 +1,18 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'queries_firestore.dart';
export 'queries_interface.dart';
@@ -0,0 +1,88 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:wyatt_crud_bloc/src/models/queries/queries_interface.dart';
class QueryParserFirestore implements QueryParserInterface {
@override
Query parser(QueryInterface condition, Object query) {
query as Query;
if (condition is WhereQueryInterface) {
switch (condition.type) {
case WhereQueryType.isEqualTo:
return query.where(condition.field, isEqualTo: condition.value);
case WhereQueryType.isNotEqualTo:
return query.where(condition.field, isNotEqualTo: condition.value);
case WhereQueryType.isLessThan:
return query.where(condition.field, isLessThan: condition.value);
case WhereQueryType.isLessThanOrEqualTo:
return query.where(
condition.field,
isLessThanOrEqualTo: condition.value,
);
case WhereQueryType.isGreaterThan:
return query.where(condition.field, isGreaterThan: condition.value);
case WhereQueryType.isGreaterThanOrEqualTo:
return query.where(
condition.field,
isGreaterThanOrEqualTo: condition.value,
);
case WhereQueryType.arrayContains:
return query.where(condition.field, arrayContains: condition.value);
case WhereQueryType.arrayContainsAny:
return query.where(
condition.field,
arrayContainsAny: condition.value as List<Object>,
);
case WhereQueryType.whereIn:
return query.where(
condition.field,
whereIn: condition.value as List<Object>,
);
case WhereQueryType.whereNotIn:
return query.where(
condition.field,
whereNotIn: condition.value as List<Object>,
);
case WhereQueryType.isNull:
return query.where(condition.field, isNull: condition.value as bool);
}
} else if (condition is LimitQueryInterface) {
return query.limit(condition.limit);
} else if (condition is OrderByQueryInterface) {
return query.orderBy(
condition.field,
descending: !condition.ascending,
);
}
return query;
}
}
class WhereQueryFirestore extends WhereQueryInterface {
WhereQueryFirestore(WhereQueryType type, String field, Object value)
: super(type, field, value);
}
class LimitQueryFirestore extends LimitQueryInterface {
LimitQueryFirestore(int limit) : super(limit);
}
class OrderByQueryFirestore extends OrderByQueryInterface {
OrderByQueryFirestore(String field, {bool ascending = true})
: super(field, ascending: ascending);
}
@@ -0,0 +1,57 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// ignore: one_member_abstracts
abstract class QueryParserInterface {
Object parser(QueryInterface condition, Object query);
}
abstract class QueryInterface {}
enum WhereQueryType {
isEqualTo,
isNotEqualTo,
isLessThan,
isLessThanOrEqualTo,
isGreaterThan,
isGreaterThanOrEqualTo,
arrayContains,
arrayContainsAny,
whereIn,
whereNotIn,
isNull,
}
abstract class WhereQueryInterface extends QueryInterface {
final WhereQueryType type;
final String field;
final Object value;
WhereQueryInterface(this.type, this.field, this.value);
}
abstract class LimitQueryInterface extends QueryInterface {
final int limit;
LimitQueryInterface(this.limit);
}
abstract class OrderByQueryInterface extends QueryInterface {
final String field;
final bool ascending;
OrderByQueryInterface(this.field, {this.ascending = true});
}
@@ -0,0 +1,102 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:firebase_database/firebase_database.dart';
import 'package:wyatt_crud_bloc/src/models/model.dart';
import 'package:wyatt_crud_bloc/src/models/queries/queries_interface.dart';
import 'package:wyatt_crud_bloc/src/repositories/crud_repository_interface.dart';
class CrudRepositoryFirebaseDatabase<T> implements CrudRepositoryInterface<T> {
final FirebaseDatabase _firebaseDatabase;
final Model<Object, T> _parser;
late DatabaseReference _rootReference;
CrudRepositoryFirebaseDatabase(
String root,
Model<Object, T> parser, {
FirebaseDatabase? firebaseDatabase,
}) : _firebaseDatabase = firebaseDatabase ?? FirebaseDatabase.instance,
_parser = parser {
_rootReference = _firebaseDatabase.ref(root);
}
@override
Future<void> create(Model object, {String? id}) {
DatabaseReference _reference = _rootReference;
if (id != null) {
_reference = _reference.child(id);
}
return _reference.set(object.toMap());
}
@override
Future<void> delete(String id) {
final DatabaseReference _reference = _rootReference.child(id);
return _reference.remove();
}
@override
Future<void> deleteAll() {
return _rootReference.remove();
}
@override
Future<T?> get(String id) async {
final DatabaseEvent _event = await _rootReference.child(id).once();
return _parser.from(_event.snapshot.value);
}
@override
Future<List<T?>> getAll() async {
final DatabaseEvent _event = await _rootReference.once();
final List<T?> _objects = [];
_event.snapshot.children.map((e) => _objects.add(_parser.from(e.value)));
return _objects;
}
@override
Future<List<T?>> query(List<QueryInterface> conditions) {
// TODO(hpcl): implement query
throw UnimplementedError();
}
@override
Stream<List<T?>> stream({String? id, List<QueryInterface>? conditions}) {
DatabaseReference _reference = _rootReference;
if (id != null) {
_reference = _reference.child(id);
}
return _reference.onValue.map((e) {
final List<T?> _objects = [];
e.snapshot.children.map((e) => _objects.add(_parser.from(e.value)));
return _objects;
});
}
@override
Future<void> update(String id, {Model? object, Map<String, dynamic>? raw}) {
// TODO(hpcl): implement update
throw UnimplementedError();
}
@override
Future<void> updateAll(Map<String, Object?> raw) {
// TODO(hpcl): implement updateAll
throw UnimplementedError();
}
}
@@ -0,0 +1,146 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:wyatt_crud_bloc/src/models/model.dart';
import 'package:wyatt_crud_bloc/src/models/queries/queries_firestore.dart';
import 'package:wyatt_crud_bloc/src/models/queries/queries_interface.dart';
import 'package:wyatt_crud_bloc/src/repositories/crud_repository_interface.dart';
class CrudRepositoryFirestore<T> implements CrudRepositoryInterface<T> {
final FirebaseFirestore _firestore;
final Model<DocumentSnapshot, T> _parser;
late CollectionReference _collectionReference;
CrudRepositoryFirestore(
String collection,
Model<DocumentSnapshot, T> parser, {
FirebaseFirestore? firestore,
}) : _firestore = firestore ?? FirebaseFirestore.instance,
_parser = parser {
_collectionReference = _firestore.collection(collection);
}
@override
Future<void> create(Model object, {String? id}) {
if (id != null) {
return _collectionReference.doc(id).set(object.toMap());
} else {
return _collectionReference.add(object.toMap());
}
}
@override
Future<void> delete(String id) {
return _collectionReference.doc(id).delete();
}
@override
Future<void> deleteAll() async {
final _batch = _firestore.batch();
final QuerySnapshot snapshots = await _collectionReference.get();
for (final DocumentSnapshot snapshot in snapshots.docs) {
_batch.delete(snapshot.reference);
}
return _batch.commit();
}
@override
Future<T?> get(String id) async {
final DocumentSnapshot snapshot = await _collectionReference.doc(id).get();
return _parser.from(snapshot);
}
@override
Future<List<T?>> getAll() async {
final QuerySnapshot snapshots = await _collectionReference.get();
return snapshots.docs.map(_parser.from).toList();
}
@override
Future<List<T?>> query(List<QueryInterface> conditions) async {
Query query = _collectionReference;
for (final condition in conditions) {
query = QueryParserFirestore().parser(condition, query);
}
final QuerySnapshot snapshots = await query.get();
return snapshots.docs.map(_parser.from).toList();
}
@override
Stream<List<T?>> stream({
String? id,
List<QueryInterface>? conditions,
bool includeMetadataChanges = false,
}) {
if (id != null) {
return _collectionReference
.doc(id)
.snapshots(
includeMetadataChanges: includeMetadataChanges,
)
.map<List<T?>>(
(DocumentSnapshot snapshot) => [_parser.from(snapshot)],
);
} else {
if (conditions != null) {
Query query = _collectionReference;
for (final condition in conditions) {
query = QueryParserFirestore().parser(condition, query);
}
return query
.snapshots(
includeMetadataChanges: includeMetadataChanges,
)
.map((querySnapshot) {
return querySnapshot.docs.map(_parser.from).toList();
});
} else {
return _collectionReference
.snapshots(
includeMetadataChanges: includeMetadataChanges,
)
.map((querySnapshot) {
return querySnapshot.docs.map(_parser.from).toList();
});
}
}
}
@override
Future<void> update(String id, {Model? object, Map<String, Object?>? raw}) {
if (object != null) {
return _collectionReference.doc(id).update(object.toMap());
} else {
if (raw != null) {
return _collectionReference.doc(id).update(raw);
} else {
throw Exception('You must provide an object or a raw map');
}
}
}
@override
Future<void> updateAll(Map<String, Object?> raw) async {
final _batch = _firestore.batch();
final QuerySnapshot snapshots = await _collectionReference.get();
for (final DocumentSnapshot snapshot in snapshots.docs) {
_batch.update(snapshot.reference, raw);
}
return _batch.commit();
}
}
@@ -0,0 +1,30 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_crud_bloc/src/models/model.dart';
import 'package:wyatt_crud_bloc/src/models/queries/queries_interface.dart';
abstract class CrudRepositoryInterface<T> {
Future<void> create(Model object, {String? id});
Future<void> delete(String id);
Future<void> deleteAll();
Future<T?> get(String id);
Future<List<T?>> getAll();
Future<List<T?>> query(List<QueryInterface> conditions);
Stream<List<T?>> stream({String? id, List<QueryInterface>? conditions});
Future<void> update(String id, {Model? object, Map<String, dynamic>? raw});
Future<void> updateAll(Map<String, Object?> raw);
}
@@ -0,0 +1,18 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'crud_repository_firestore.dart';
export 'crud_repository_interface.dart';
@@ -0,0 +1,21 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
library wyatt_crud_bloc;
export 'src/crud/crud.dart';
export 'src/models/models.dart';
export 'src/repositories/repositories.dart';
+27
View File
@@ -0,0 +1,27 @@
name: wyatt_crud_bloc
description: Create/Read/Update/Delete BLoC for Flutter
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_crud_bloc
version: 0.0.2
environment:
sdk: '>=2.16.2 <3.0.0'
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.0.1
equatable: ^2.0.3
cloud_firestore: ^3.1.12
firebase_database: ^9.0.11
dev_dependencies:
flutter_test:
sdk: flutter
bloc_test: ^9.0.3
wyatt_analysis:
git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_analysis-v2.0.1
path: packages/wyatt_analysis
+4
View File
@@ -1,3 +1,7 @@
## 0.0.2
- **FEAT**: add list option validator.
## 0.0.1 ## 0.0.1
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries. - Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
@@ -14,50 +14,48 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:form_bloc_example/constants.dart'; import 'package:form_bloc_example/constants.dart';
import 'package:form_bloc_example/cubit/custom_form_cubit.dart';
import 'package:form_bloc_example/sign_up/sign_up_page.dart'; import 'package:form_bloc_example/sign_up/sign_up_page.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class App extends StatelessWidget { class App extends StatelessWidget {
const App({Key? key}) : super(key: key); const App({Key? key}) : super(key: key);
static FormData getNormalFormData() { static List<FormEntry> getNormalEntries() {
return const FormData([ return const [
FormEntry(formFieldName, Name.pure()), FormEntry(formFieldName, Name.pure()),
FormEntry(formFieldEmail, Email.pure()), FormEntry(formFieldEmail, Email.pure()),
FormEntry(formFieldPhone, Phone.pure()), FormEntry(formFieldPhone, Phone.pure()),
FormEntry(
formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])),
FormEntry(formFieldRadio, TextString.pure()),
FormEntry(formFieldPro, Boolean.pure(), name: 'business'), FormEntry(formFieldPro, Boolean.pure(), name: 'business'),
FormEntry(formFieldHidden, Boolean.pure(), export: false), FormEntry(formFieldHidden, Boolean.pure(), export: false),
]); ];
}
static List<FormEntry> getBusinessEntries() {
const entries = [
FormEntry(formFieldSiren, Siren.pure()),
FormEntry(formFieldIban, Iban.pure()),
];
return getNormalEntries() + entries;
}
static FormData getNormalFormData() {
return FormData(getNormalEntries());
} }
static FormData getProFormData() { static FormData getProFormData() {
return const FormData([ return FormData(getBusinessEntries());
FormEntry(formFieldName, Name.pure()),
FormEntry(formFieldEmail, Email.pure()),
FormEntry(formFieldPhone, Phone.pure()),
FormEntry(formFieldPro, Boolean.pure(), name: 'business'),
FormEntry(formFieldHidden, Boolean.pure(), export: false),
FormEntry(formFieldSiren, Siren.pure()),
FormEntry(formFieldIban, Iban.pure()),
]);
}
Future<bool> onSubmit(FormDataState state) async {
log(state.data.toMap().toString());
return true;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
FormDataCubit _formCubit = FormDataCubit( FormDataCubit _formCubit = CustomFormCubit(entries: getNormalFormData());
entries: getNormalFormData(),
onSubmit: onSubmit,
);
return BlocProvider( return BlocProvider(
create: (context) => _formCubit, create: (context) => _formCubit,
@@ -19,6 +19,8 @@ const String formFieldPhone = 'phone';
const String formFieldEmail = 'email'; const String formFieldEmail = 'email';
const String formFieldSiren = 'siren'; const String formFieldSiren = 'siren';
const String formFieldIban = 'iban'; const String formFieldIban = 'iban';
const String formFieldList = 'list';
const String formFieldRadio = 'radio';
const String formFieldHidden = 'hidden'; const String formFieldHidden = 'hidden';
const String formFieldPro = 'isPro'; const String formFieldPro = 'isPro';
@@ -0,0 +1,33 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:developer';
import 'package:meta/meta.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
part 'custom_form_state.dart';
class CustomFormCubit extends FormDataCubit {
CustomFormCubit({required FormData entries}) : super(entries: entries);
@override
Future<void> submitForm() {
log(state.data.toMap().toString());
return Future.value();
}
}
@@ -0,0 +1,22 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
part of 'custom_form_cubit.dart';
@immutable
abstract class CustomFormState {}
class CustomFormInitial extends CustomFormState {}
@@ -135,6 +135,112 @@ class _IbanInput extends StatelessWidget {
} }
} }
class _CheckListInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<FormDataCubit, FormDataState>(
builder: (context, state) {
final _input =
state.data.input<List<String>>(formFieldList) as ListOption<String>;
final _options = _input.value;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: const Text('Checkbox1'),
trailing: Checkbox(
value: _options.contains('checkbox1'),
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldList,
_input.select('checkbox1'),
);
}),
),
ListTile(
title: const Text('Checkbox2'),
trailing: Checkbox(
value: _options.contains('checkbox2'),
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldList,
_input.select('checkbox2'),
);
}),
),
ListTile(
title: const Text('Checkbox3 (default)'),
trailing: Checkbox(
value: _options.contains('checkbox3'),
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldList,
_input.select('checkbox3'),
);
}),
),
],
);
},
);
}
}
class _RadioListInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<FormDataCubit, FormDataState>(
builder: (context, state) {
final _input =
state.data.input<String>(formFieldRadio) as TextString;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: const Text('Radio1'),
trailing: Radio<bool>(
groupValue: true,
value: _input.value == 'radio1',
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldRadio,
const TextString.dirty('radio1'),
);
}),
),
ListTile(
title: const Text('Radio2'),
trailing: Radio<bool>(
groupValue: true,
value: _input.value == 'radio2',
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldRadio,
const TextString.dirty('radio2'),
);
}),
),
ListTile(
title: const Text('Radio3'),
trailing: Radio<bool>(
groupValue: true,
value: _input.value == 'radio3',
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldRadio,
const TextString.dirty('radio3'),
);
}),
),
],
);
},
);
}
}
class _CheckHiddenInput extends StatelessWidget { class _CheckHiddenInput extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -201,8 +307,7 @@ class _SignUpButton extends StatelessWidget {
? const CircularProgressIndicator() ? const CircularProgressIndicator()
: ElevatedButton( : ElevatedButton(
onPressed: state.status.isValidated onPressed: state.status.isValidated
? () => ? () => context.read<FormDataCubit>().submitForm()
context.read<FormDataCubit>().submitForm()
: null, : null,
child: const Text('SIGN UP'), child: const Text('SIGN UP'),
); );
@@ -244,8 +349,6 @@ class SignUpForm extends StatelessWidget {
); );
} }
}, },
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Align( child: Align(
alignment: const Alignment(0, -1 / 3), alignment: const Alignment(0, -1 / 3),
child: Column( child: Column(
@@ -257,6 +360,10 @@ class SignUpForm extends StatelessWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
_PhoneInput(), _PhoneInput(),
const SizedBox(height: 8), const SizedBox(height: 8),
_CheckListInput(),
const SizedBox(height: 8),
_RadioListInput(),
const SizedBox(height: 8),
_CheckHiddenInput(), _CheckHiddenInput(),
const SizedBox(height: 8), const SizedBox(height: 8),
_CheckIsProInput(), _CheckIsProInput(),
@@ -281,7 +388,6 @@ class SignUpForm extends StatelessWidget {
], ],
), ),
), ),
),
); );
} }
} }
@@ -23,16 +23,16 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
part 'form_data_state.dart'; part 'form_data_state.dart';
class FormDataCubit extends Cubit<FormDataState> { abstract class FormDataCubit extends Cubit<FormDataState> {
final Future<bool> Function(FormDataState state)? _onSubmit; FormDataCubit({required FormData entries})
: super(FormDataState(data: entries));
FormDataCubit({ /// Change value of a field.
required FormData entries, ///
Future<bool> Function(FormDataState state)? onSubmit, /// Inputs:
}) : _onSubmit = onSubmit, /// - `field`: The key of the field to change.
super(FormDataState(data: entries)); /// - `dirtyValue`: The new value of the field.
void dataChanged(String field, FormInput dirtyValue) {
void dataChanged<T>(String field, FormInput dirtyValue) {
final _form = state.data.clone(); final _form = state.data.clone();
if (_form.contains(field)) { if (_form.contains(field)) {
@@ -49,6 +49,11 @@ class FormDataCubit extends Cubit<FormDataState> {
); );
} }
/// Update entries list.
///
/// Inputs:
/// - `data`: The new entries list.
/// - `operation`: The operation to perform on the entries set.
void updateFormData( void updateFormData(
FormData data, { FormData data, {
SetOperation operation = SetOperation.replace, SetOperation operation = SetOperation.replace,
@@ -78,13 +83,6 @@ class FormDataCubit extends Cubit<FormDataState> {
); );
} }
Future<void> submitForm() async { /// Submit the form.
unawaited( Future<void> submitForm();
_onSubmit?.call(state).then((bool reemit) {
if (reemit) {
emit(state);
}
}),
);
}
} }
@@ -0,0 +1,48 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/enums/enums.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
/// {@template list_option}
/// Form input for a list input
/// {@endtemplate}
class ListOption<T> extends FormInput<List<T>, ValidationError> {
/// {@macro list_option}
const ListOption.pure({List<T>? defaultValue})
: super.pure(defaultValue ?? const []);
/// {@macro list_option}
const ListOption.dirty({List<T>? value}) : super.dirty(value ?? const []);
ListOption select(T? v) {
if (v == null) {
return this;
}
if (value.contains(v)) {
final List<T> newValue = List.from(value)..remove(v);
return ListOption<T>.dirty(value: newValue);
} else {
final List<T> newValue = List.from(value)..add(v);
return ListOption<T>.dirty(value: newValue);
}
}
@override
ValidationError? validator(List<T>? value) {
return value?.isNotEmpty == true ? null : ValidationError.invalid;
}
}
@@ -18,6 +18,7 @@ export 'boolean.dart';
export 'confirmed_password.dart'; export 'confirmed_password.dart';
export 'email.dart'; export 'email.dart';
export 'iban.dart'; export 'iban.dart';
export 'list_option.dart';
export 'name.dart'; export 'name.dart';
export 'password.dart'; export 'password.dart';
export 'phone.dart'; export 'phone.dart';
+2 -2
View File
@@ -1,7 +1,7 @@
name: wyatt_form_bloc name: wyatt_form_bloc
description: Manage forms in Dart & Flutter with Bloc description: Manage forms in Dart & Flutter with Bloc
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_form_bloc repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_form_bloc
version: 0.0.1 version: 0.0.2
environment: environment:
sdk: '>=2.16.2 <3.0.0' sdk: '>=2.16.2 <3.0.0'
@@ -16,5 +16,5 @@ dev_dependencies:
wyatt_analysis: wyatt_analysis:
git: git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_analysis-v2.0.0 ref: wyatt_analysis-v2.0.1
path: packages/wyatt_analysis path: packages/wyatt_analysis
+10
View File
@@ -0,0 +1,10 @@
# Files and directories created by pub.
.dart_tool/
.packages
# Conventional directory for build outputs.
build/
# Omit committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock
+24
View File
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2022 WYATT GROUP
* Please see the AUTHORS file for details.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
{
"recommendations": [
"psioniq.psi-header",
"blaugold.melos-code"
]
}
+71
View File
@@ -0,0 +1,71 @@
{
"psi-header.changes-tracking": {
"isActive": true
},
"psi-header.config": {
"blankLinesAfter": 1,
"forceToTop": true
},
"psi-header.lang-config": [
{
"beforeHeader": [
"# -*- coding:utf-8 -*-",
"#!/usr/bin/env python3"
],
"begin": "###",
"end": "###",
"language": "python",
"prefix": "# "
},
{
"beforeHeader": [
"#!/usr/bin/env sh",
""
],
"language": "shellscript",
"begin": "",
"end": "",
"prefix": "# "
},
{
"begin": "",
"end": "",
"language": "dart",
"prefix": "// "
},
{
"begin": "",
"end": "",
"language": "yaml",
"prefix": "# "
},
{
"begin": "<!--",
"end": "-->",
"language": "markdown",
},
],
"psi-header.templates": [
{
"language": "*",
"template": [
"Copyright (C) <<year>> WYATT GROUP",
"Please see the AUTHORS file for details.",
"",
"This program is free software: you can redistribute it and/or modify",
"it under the terms of the GNU General Public License as published by",
"the Free Software Foundation, either version 3 of the License, or",
"any later version.",
"",
"This program is distributed in the hope that it will be useful,",
"but WITHOUT ANY WARRANTY; without even the implied warranty of",
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
"GNU General Public License for more details.",
"",
"You should have received a copy of the GNU General Public License",
"along with this program. If not, see <https://www.gnu.org/licenses/>."
],
}
],
"dart.runPubGetOnPubspecChanges": false,
}
+7
View File
@@ -0,0 +1,7 @@
## 1.0.1
- **FIX**: remove useless example file.
## 1.0.0
- Initial version.
+39
View File
@@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
```dart
const like = 'sample';
```
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.
@@ -0,0 +1 @@
include: package:wyatt_analysis/analysis_options.flutter.yaml
@@ -0,0 +1,46 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 5464c5bac742001448fe4fc0597be939379f88ea
channel: stable
project_type: app
@@ -0,0 +1,16 @@
# medium_feeds_example
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://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
@@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
@@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
class ArticleTile extends StatelessWidget {
const ArticleTile({
Key? key,
required this.bannerUrl,
required this.title,
required this.summary,
required this.author,
required this.publishDate,
required this.readingTime,
}) : super(key: key);
final String bannerUrl;
final String title;
final String summary;
final String author;
final String publishDate;
final String readingTime;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Container(
height: 200,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(bannerUrl),
fit: BoxFit.cover,
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.headline6,
),
const SizedBox(height: 8),
Text(
summary,
style: Theme.of(context).textTheme.bodyText1,
),
const SizedBox(height: 8),
Row(
children: [
Text(
author,
style: Theme.of(context).textTheme.caption,
),
const SizedBox(width: 8),
Text(
publishDate,
style: Theme.of(context).textTheme.caption,
),
const SizedBox(width: 8),
Text(
readingTime,
style: Theme.of(context).textTheme.caption,
),
],
),
],
),
),
],
),
);
}
}
@@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
import 'package:medium_feeds_example/article_tile.dart';
import 'package:wyatt_medium_feeds/wyatt_medium_feeds.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const WidgetTree(),
);
}
}
class ArticleGrid extends StatelessWidget {
const ArticleGrid({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final mediumFeed = MediumFeed.fromPublicationName('flutter');
return FutureBuilder(
future: mediumFeed.parse(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final articles = mediumFeed.articles;
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1,
),
itemCount: articles.length,
itemBuilder: (context, index) {
final article = articles[index];
return ArticleTile(
bannerUrl: article.images.isNotEmpty ? article.images[0] : '',
author: article.author,
title: article.title,
summary: article.summary,
readingTime:
article.readingTime.inMinutes.toString() + ' min read',
publishDate: article.publishDate!.day.toString() +
'/' +
article.publishDate!.month.toString() +
'/' +
article.publishDate!.year.toString(),
);
},
);
} else {
return const Center(child: CircularProgressIndicator());
}
});
}
}
class WidgetTree extends StatelessWidget {
const WidgetTree({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Medium Feeds Example'),
),
body: const ArticleGrid(),
);
}
}
@@ -0,0 +1,93 @@
name: medium_feeds_example
description: A new Flutter project.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
environment:
sdk: ">=2.16.2 <3.0.0"
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
wyatt_medium_feeds:
path: ../
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^1.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
@@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:medium_feeds_example/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

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