feat(authentication)!: rename package

This commit is contained in:
2022-04-19 19:34:04 +02:00
parent 22e0aeeeed
commit c70446e2c4
148 changed files with 7788 additions and 0 deletions
@@ -0,0 +1,149 @@
// 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:example/constants.dart';
import 'package:example/home/home_page.dart';
import 'package:example/login/login_page.dart';
import 'package:example/model.dart';
import 'package:flutter/material.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
static FormData getNormalFormData() {
return const FormData([
FormEntry(formFieldName, Name.pure()),
FormEntry(formFieldPhone, Phone.pure()),
FormEntry(formFieldPro, Boolean.pure()),
FormEntry(formFieldConfirmedPassword, ConfirmedPassword.pure(), export: false),
]);
}
static FormData getProFormData() {
return const FormData([
FormEntry(formFieldName, Name.pure()),
FormEntry(formFieldPhone, Phone.pure()),
FormEntry(formFieldPro, Boolean.pure()),
FormEntry(formFieldSiren, Siren.pure()),
FormEntry(formFieldIban, Iban.pure()),
FormEntry(formFieldConfirmedPassword, ConfirmedPassword.pure(), export: false),
]);
}
// On Authentication Success. (Authenticated or Anonymous)
// User callback.
Future<Map<String, dynamic>> onAuthSuccess(UserInterface user) async {
if (user.isNotEmpty && !user.isAnonymous) {
// Check if user is register in Firesore.
DocumentSnapshot firestoreUser = await FirebaseFirestore.instance
.collection('users')
.doc(user.uid)
.get();
return {
'user':
UserFirestore.fromMap(firestoreUser.data() as Map<String, dynamic>),
...firestoreUser.data() as Map<String, dynamic>? ?? {}
};
} else {
return {};
}
}
// On Sign Up Success.
Future<void> onSignUpSuccess(SignUpState state, String? uid) async {
if (uid != null) {
final data = state.data.toMap();
final user = {'uid': uid, 'email': state.email.value, ...data};
await FirebaseFirestore.instance.collection('users').doc(uid).set(user);
}
}
@override
Widget build(BuildContext context) {
AuthenticationRepositoryInterface _authenticationRepository =
AuthenticationRepositoryFirebase();
AuthenticationCubit _authenticationCubit = AuthenticationCubit(
authenticationRepository: _authenticationRepository,
onAuthSuccess: onAuthSuccess,
);
SignUpCubit _signUpCubit = SignUpCubit(
authenticationRepository: _authenticationRepository,
authenticationCubit: _authenticationCubit,
entries: getNormalFormData(),
onSignUpSuccess: onSignUpSuccess,
);
SignInCubit _signInCubit = SignInCubit(
authenticationRepository: _authenticationRepository,
authenticationCubit: _authenticationCubit,
);
return MultiRepositoryProvider(
providers: [
RepositoryProvider<AuthenticationRepositoryInterface>(
create: (context) => _authenticationRepository,
),
],
child: MultiBlocProvider(
providers: [
BlocProvider<AuthenticationCubit>(
create: (context) => _authenticationCubit..init(),
),
BlocProvider<SignUpCubit>(
create: (context) => _signUpCubit,
),
BlocProvider<SignInCubit>(
create: (context) => _signInCubit,
),
],
child: const AppView(),
),
);
}
}
class AppView extends StatelessWidget {
const AppView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
buttonTheme: const ButtonThemeData(
buttonColor: Colors.blue,
textTheme: ButtonTextTheme.primary,
),
),
home: AuthenticationBuilder(
unknown: (context) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
},
authenticated: (context, user, userData) => const HomePage(),
unauthenticated: (context) => const LoginPage(),
),
);
}
}
@@ -0,0 +1,44 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:bloc/bloc.dart';
import 'package:flutter/foundation.dart';
class AppBlocObserver extends BlocObserver {
@override
void onEvent(Bloc bloc, Object? event) {
super.onEvent(bloc, event);
debugPrint(event.toString());
}
@override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
debugPrint(error.toString());
super.onError(bloc, error, stackTrace);
}
@override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
debugPrint('curr:\t${change.currentState}\nnext:\t${change.nextState}');
}
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
debugPrint(transition.toString());
}
}
@@ -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/>.
const String formFieldName = 'name';
const String formFieldPhone = 'phone';
const String formFieldPro = 'isPro';
const String formFieldConfirmedPassword = 'confirmedPassword';
const String formFieldSiren = 'siren';
const String formFieldIban = 'iban';
@@ -0,0 +1,70 @@
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
// ignore: missing_enum_constant_in_switch
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for macos - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
}
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
static const FirebaseOptions web = FirebaseOptions(
apiKey: 'AIzaSyCxdDUpkpW7onOicIcIDcIVvPC8ll1twCQ',
appId: '1:136771801992:web:e757595ae62e646297203d',
messagingSenderId: '136771801992',
projectId: 'tchat-beta',
authDomain: 'tchat-beta.firebaseapp.com',
databaseURL: 'https://tchat-beta.firebaseio.com',
storageBucket: 'tchat-beta.appspot.com',
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk',
appId: '1:136771801992:android:d20e0361057e815197203d',
messagingSenderId: '136771801992',
projectId: 'tchat-beta',
databaseURL: 'https://tchat-beta.firebaseio.com',
storageBucket: 'tchat-beta.appspot.com',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyCDbbhjbFrQwLXuIANdJzjkDk8uOETnn7w',
appId: '1:136771801992:ios:6fe69c0bea08b7b397203d',
messagingSenderId: '136771801992',
projectId: 'tchat-beta',
databaseURL: 'https://tchat-beta.firebaseio.com',
storageBucket: 'tchat-beta.appspot.com',
iosClientId:
'136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com',
iosBundleId: 'com.example.example',
);
}
@@ -0,0 +1,44 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:example/forgot_password/widgets/forgot_password_form.dart';
import 'package:flutter/material.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ForgotPasswordPage extends StatelessWidget {
const ForgotPasswordPage({Key? key}) : super(key: key);
static Route route() {
return MaterialPageRoute<void>(builder: (_) => const ForgotPasswordPage());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Forgot Password')),
body: Padding(
padding: const EdgeInsets.all(8),
child: BlocProvider(
create: (_) => PasswordResetCubit(
context.read<AuthenticationRepositoryInterface>(),
),
child: const ForgotPasswordForm(),
),
),
);
}
}
@@ -0,0 +1,91 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class _EmailInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<PasswordResetCubit, PasswordResetState>(
buildWhen: (previous, current) => previous.email != current.email,
builder: (context, state) {
return TextField(
onChanged: (email) =>
context.read<PasswordResetCubit>().emailChanged(email),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'email',
helperText: '',
errorText: state.email.invalid ? 'invalid email' : null,
),
);
},
);
}
}
class _ResetButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<PasswordResetCubit, PasswordResetState>(
buildWhen: (previous, current) => previous.status != current.status,
builder: (context, state) {
return state.status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: state.status.isValidated
? () => context
.read<PasswordResetCubit>()
.sendPasswordResetEmail()
: null,
child: const Text('SEND EMAIL'),
);
},
);
}
}
class ForgotPasswordForm extends StatelessWidget {
const ForgotPasswordForm({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocListener<PasswordResetCubit, PasswordResetState>(
listener: (context, state) {
if (state.status.isSubmissionSuccess) {
Navigator.of(context).pop();
} else if (state.status.isSubmissionFailure) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Text(state.errorMessage ?? 'Password reset Failure'),
),
);
}
},
child: Align(
alignment: const Alignment(0, -1 / 3),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [_EmailInput(), const SizedBox(height: 8), _ResetButton()],
),
),
);
}
}
@@ -0,0 +1,65 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:example/home/widgets/infos.dart';
import 'package:flutter/material.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:example/home/widgets/email_verification.dart';
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
static Route route() {
return MaterialPageRoute<void>(builder: (_) => const HomePage());
}
@override
Widget build(BuildContext context) {
final user = context.select((AuthenticationCubit cubit) => cubit.state.user);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.exit_to_app),
onPressed: () => context
.read<AuthenticationCubit>()
.logOut(),
)
],
),
body: Padding(
padding: const EdgeInsets.all(8),
child: BlocProvider(
create: (_) => EmailVerificationCubit(
context.read<AuthenticationRepositoryInterface>(),
)..checkEmailVerification(),
child: BlocBuilder<EmailVerificationCubit, EmailVerificationState>(
builder: (context, state) {
if (state.isVerified || user!.isAnonymous) {
return const UserInfo();
} else {
return const EmailVerification();
}
},
),
),
),
);
}
}
@@ -0,0 +1,37 @@
// 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';
const _avatarSize = 48.0;
class Avatar extends StatelessWidget {
const Avatar({Key? key, this.photo}) : super(key: key);
final String? photo;
@override
Widget build(BuildContext context) {
final photo = this.photo;
return CircleAvatar(
radius: _avatarSize,
backgroundImage: photo != null ? NetworkImage(photo) : null,
child: photo == null
? const Icon(Icons.person_outline, size: _avatarSize)
: null,
);
}
}
@@ -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:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class EmailVerification extends StatelessWidget {
const EmailVerification({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final user = context.select((AuthenticationCubit cubit) => cubit.state.user);
final userData = context.select((AuthenticationCubit cubit) => cubit.state.userData);
return Align(
alignment: const Alignment(0, -1 / 3),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Hello ${userData!['name'] ?? 'null'}!"),
const SizedBox(height: 4),
Text("Email '${user?.email ?? 'null'}' is not verified"),
const SizedBox(height: 4),
ElevatedButton(
onPressed: () {
context.read<EmailVerificationCubit>().sendEmailVerification();
},
child: const Text('(Re)send email'),
),
ElevatedButton(
onPressed: () {
context.read<EmailVerificationCubit>().checkEmailVerification();
},
child: const Text('Refresh'),
),
],
),
);
}
}
@@ -0,0 +1,58 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:example/home/widgets/avatar.dart';
import 'package:flutter/material.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:example/constants.dart';
class UserInfo extends StatelessWidget {
const UserInfo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final user = context.select((AuthenticationCubit cubit) => cubit.state.user);
final userData = context.select((AuthenticationCubit cubit) => cubit.state.userData);
return Align(
alignment: const Alignment(0, -1 / 3),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Avatar(photo: user?.photoURL),
const SizedBox(height: 4),
Text("Email: ${user?.email ?? 'null'}", style: textTheme.headline6),
const SizedBox(height: 4),
Text("Name: ${userData![formFieldName] ?? 'null'}",
style: textTheme.headline6),
const SizedBox(height: 4),
Text("Phone: ${userData[formFieldPhone] ?? 'null'}",
style: textTheme.headline6),
const SizedBox(height: 4),
Text("IsPro: ${userData[formFieldPro] ?? 'null'}",
style: textTheme.headline6),
const SizedBox(height: 4),
Text("IsAnonymous: ${user?.isAnonymous.toString() ?? 'null'}",
style: textTheme.headline6),
const SizedBox(height: 4),
Text("IsEmailVerified: ${user?.emailVerified.toString() ?? 'null'}",
style: textTheme.headline6),
],
),
);
}
}
@@ -0,0 +1,37 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:example/login/widgets/login_form.dart';
import 'package:flutter/material.dart';
class LoginPage extends StatelessWidget {
const LoginPage({Key? key}) : super(key: key);
static Route route() {
return MaterialPageRoute<void>(builder: (_) => const LoginPage());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: const Padding(
padding: EdgeInsets.all(8),
child: LoginForm(),
),
);
}
}
@@ -0,0 +1,193 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:example/app/app.dart';
import 'package:example/constants.dart';
import 'package:example/forgot_password/forgot_password_page.dart';
import 'package:example/sign_up/sign_up_page.dart';
import 'package:flutter/material.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class _EmailInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
buildWhen: (previous, current) => previous.email != current.email,
builder: (context, state) {
return TextField(
onChanged: (email) => context.read<SignInCubit>().emailChanged(email),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'email',
helperText: '',
errorText: state.email.invalid ? 'invalid email' : null,
),
);
},
);
}
}
class _PasswordInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
buildWhen: (previous, current) => previous.password != current.password,
builder: (context, state) {
return TextField(
onChanged: (password) =>
context.read<SignInCubit>().passwordChanged(password),
obscureText: true,
decoration: InputDecoration(
labelText: 'password',
helperText: '',
errorText: state.password.invalid ? 'invalid password' : null,
),
);
},
);
}
}
class _LoginAnonButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
buildWhen: (previous, current) => previous.status != current.status,
builder: (context, state) {
return state.status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () =>
context.read<SignInCubit>().signInAnonymously(),
child: const Text('LOGIN ANONYMOUSLY'),
);
},
);
}
}
class _LoginWithPasswordButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInCubit, SignInState>(
buildWhen: (previous, current) => previous.status != current.status,
builder: (context, state) {
return state.status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: state.status.isValidated
? () =>
context.read<SignInCubit>().signInWithEmailAndPassword()
: null,
child: const Text('LOGIN WITH PASSWORD'),
);
},
);
}
}
class _SignUpButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return TextButton(
onPressed: () {
context.read<SignUpCubit>().updateFormData(App.getNormalFormData());
Navigator.of(context).push<void>(SignUpPage.route());
},
child: Text(
'CREATE ACCOUNT',
style: TextStyle(color: theme.primaryColor),
),
);
}
}
class _SignUpAsProButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return TextButton(
onPressed: () {
context.read<SignUpCubit>().updateFormData(App.getProFormData());
context.read<SignUpCubit>().dataChanged(
formFieldPro,
const Boolean.dirty(value: true),
);
Navigator.of(context).push<void>(SignUpPage.route());
},
child: Text(
'CREATE PRO ACCOUNT',
style: TextStyle(color: theme.primaryColor),
),
);
}
}
class LoginForm extends StatelessWidget {
const LoginForm({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocListener<SignInCubit, SignInState>(
listener: (context, state) {
if (state.status.isSubmissionFailure) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Text(state.errorMessage ?? 'Authentication Failure'),
),
);
}
},
child: Align(
alignment: const Alignment(0, -1 / 3),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 120),
_EmailInput(),
const SizedBox(height: 8),
_PasswordInput(),
const SizedBox(height: 8),
GestureDetector(
onTap: () {
Navigator.of(context).push(ForgotPasswordPage.route());
},
child: Text(
'Forgot password ?',
style: Theme.of(context).textTheme.bodyText2,
),
),
const SizedBox(height: 8),
_LoginWithPasswordButton(),
const SizedBox(height: 8),
_LoginAnonButton(),
const SizedBox(height: 8),
_SignUpButton(),
const SizedBox(height: 8),
_SignUpAsProButton(),
],
),
),
),
);
}
}
@@ -0,0 +1,35 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:example/app/app.dart';
import 'package:example/app/bloc_observer.dart';
import 'package:example/firebase_options.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
BlocOverrides.runZoned(
() => runApp(
const App(),
),
blocObserver: AppBlocObserver(),
);
}
@@ -0,0 +1,52 @@
// 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/>.
class UserFirestore {
final String email;
final String name;
final String phone;
final String uid;
UserFirestore({
required this.email,
required this.name,
required this.phone,
required this.uid,
});
factory UserFirestore.fromMap(Map<String, dynamic> map) {
return UserFirestore(
uid: map['uid'],
email: map['email'],
name: map['name'],
phone: map['phone'],
);
}
Map<String, dynamic> toMap() {
return {
'uid': uid,
'email': email,
'name': name,
'phone': phone,
};
}
@override
String toString() {
return 'UserFirestore(email: $email, name: $name, phone: $phone, uid: $uid)';
}
}
@@ -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 'package:example/sign_up/widgets/sign_up_form.dart';
import 'package:flutter/material.dart';
class SignUpPage extends StatelessWidget {
const SignUpPage({Key? key}) : super(key: key);
static Route route() {
return MaterialPageRoute<void>(builder: (_) => const SignUpPage());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sign Up')),
body: const Padding(
padding: EdgeInsets.all(8),
child: SingleChildScrollView(
child: SignUpForm(),
),
),
);
}
}
@@ -0,0 +1,324 @@
// 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:example/app/app.dart';
import 'package:example/constants.dart';
import 'package:flutter/material.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class _NameInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (name) => context
.read<SignUpCubit>()
.dataChanged(formFieldName, Name.dirty(name)),
keyboardType: TextInputType.name,
decoration: InputDecoration(
labelText: 'name',
helperText: '',
errorText:
state.data.input(formFieldName).invalid ? 'invalid name' : null,
),
);
},
);
}
}
class _PhoneInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (phone) => context
.read<SignUpCubit>()
.dataChanged(formFieldPhone, Phone.dirty(phone)),
keyboardType: TextInputType.phone,
decoration: InputDecoration(
labelText: 'phone',
helperText: '',
errorText: state.data.input(formFieldPhone).invalid
? 'invalid phone'
: null,
),
);
},
);
}
}
class _SirenInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (siren) => context
.read<SignUpCubit>()
.dataChanged(formFieldSiren, Siren.dirty(siren)),
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'siren',
helperText: '',
errorText: state.data.input(formFieldSiren).invalid
? 'invalid SIREN'
: null,
),
);
},
);
}
}
class _IbanInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (iban) => context
.read<SignUpCubit>()
.dataChanged(formFieldIban, Iban.dirty(iban)),
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: 'iban',
helperText: '',
errorText:
state.data.input(formFieldIban).invalid ? 'invalid IBAN' : null,
),
);
},
);
}
}
class _EmailInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
buildWhen: (previous, current) => previous.email != current.email,
builder: (context, state) {
return TextField(
onChanged: (email) => context.read<SignUpCubit>().emailChanged(email),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'email',
helperText: '',
errorText: state.email.invalid ? 'invalid email' : null,
),
);
},
);
}
}
class _PasswordInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (password) {
context.read<SignUpCubit>().passwordChanged(password);
context.read<SignUpCubit>().dataChanged(
formFieldConfirmedPassword,
ConfirmedPassword.dirty(
password: password,
value: context
.read<SignUpCubit>()
.state
.data
.input(formFieldConfirmedPassword)
.value,
),
);
},
obscureText: true,
decoration: InputDecoration(
labelText: 'password',
helperText: '',
errorText: state.password.invalid ? 'invalid password' : null,
),
);
},
);
}
}
class _ConfirmPasswordInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (confirmPassword) => context
.read<SignUpCubit>()
.dataChanged(
formFieldConfirmedPassword,
ConfirmedPassword.dirty(
password: context.read<SignUpCubit>().state.password.value,
value: confirmPassword,
),
),
obscureText: true,
decoration: InputDecoration(
labelText: 'confirm password',
helperText: '',
errorText: state.data.input(formFieldConfirmedPassword).invalid
? 'passwords do not match'
: null,
),
);
},
);
}
}
class _CheckIsProInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListTile(
title: const Text('Are you a pro?'),
trailing: BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return Checkbox(
value: state.data.input<bool>(formFieldPro).value,
onChanged: (isPro) {
final value =
isPro!; // tristate is false, so value can't be null
context.read<SignUpCubit>().dataChanged(
formFieldPro,
Boolean.dirty(value: value),
);
if (value) {
context.read<SignUpCubit>().updateFormData(
App.getProFormData(),
operation: SetOperation.union);
} else {
context.read<SignUpCubit>().updateFormData(
App.getNormalFormData(),
operation: SetOperation.intersection);
}
});
},
),
);
}
}
class _SignUpButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
buildWhen: (previous, current) => previous.status != current.status,
builder: (context, state) {
return state.status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: state.status.isValidated
? () => context.read<SignUpCubit>().signUpFormSubmitted()
: null,
child: const Text('SIGN UP'),
);
},
);
}
}
class _DebugButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return ElevatedButton(
onPressed: () {
log(state.toString());
} ,
child: const Text('DEBUG'),
);
},
);
}
}
class SignUpForm extends StatelessWidget {
const SignUpForm({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocListener<SignUpCubit, SignUpState>(
listener: (context, state) {
if (state.status.isSubmissionSuccess) {
Navigator.of(context).pop();
} else if (state.status.isSubmissionFailure) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'Sign Up Failure')),
);
}
},
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Align(
alignment: const Alignment(0, -1 / 3),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_NameInput(),
const SizedBox(height: 8),
_PhoneInput(),
const SizedBox(height: 8),
_CheckIsProInput(),
const SizedBox(height: 8),
BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
if (state.data.input<bool>(formFieldPro).value) {
return Column(children: [
_SirenInput(),
const SizedBox(height: 8),
_IbanInput(),
const SizedBox(height: 8),
]);
}
return const SizedBox.shrink();
},
),
_EmailInput(),
const SizedBox(height: 8),
_PasswordInput(),
const SizedBox(height: 8),
_ConfirmPasswordInput(),
const SizedBox(height: 8),
_SignUpButton(),
const SizedBox(height: 8),
_DebugButton(),
],
),
),
),
);
}
}