step-25
This commit is contained in:
parent
1e6da9da71
commit
dada50bb3c
|
@ -1,5 +1 @@
|
|||
const loginRoute = '/login/';
|
||||
const registerRoute = '/register/';
|
||||
const notesRoute = '/notes/';
|
||||
const verifyEmailRoute = '/verify-email/';
|
||||
const createOrUpdateNoteRoute = '/notes/new-note/';
|
||||
|
|
|
@ -24,10 +24,6 @@ void main() {
|
|||
child: const HomePage(),
|
||||
),
|
||||
routes: {
|
||||
loginRoute: (context) => const LoginView(),
|
||||
registerRoute: (context) => const RegisterView(),
|
||||
notesRoute: (context) => const NotesView(),
|
||||
verifyEmailRoute: (context) => const VerifyEmailView(),
|
||||
createOrUpdateNoteRoute: (context) => const CreateUpdateNoteView(),
|
||||
},
|
||||
),
|
||||
|
@ -48,6 +44,8 @@ class HomePage extends StatelessWidget {
|
|||
return const VerifyEmailView();
|
||||
} else if (state is AuthStateLoggedOut) {
|
||||
return const LoginView();
|
||||
} else if (state is AuthStateRegistering) {
|
||||
return const RegisterView();
|
||||
} else {
|
||||
return const Scaffold(
|
||||
body: CircularProgressIndicator(),
|
||||
|
|
|
@ -4,13 +4,37 @@ import 'package:mynotes/services/auth/bloc/auth_event.dart';
|
|||
import 'package:mynotes/services/auth/bloc/auth_state.dart';
|
||||
|
||||
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
AuthBloc(AuthProvider provider) : super(const AuthStateLoading()) {
|
||||
AuthBloc(AuthProvider provider) : super(const AuthStateUninitialized()) {
|
||||
// send email verification
|
||||
on<AuthEventSendEmailVerification>((event, emit) async {
|
||||
await provider.sendEmailVerification();
|
||||
emit(state);
|
||||
});
|
||||
on<AuthEventRegister>((event, emit) async {
|
||||
final email = event.email;
|
||||
final password = event.password;
|
||||
try {
|
||||
await provider.createUser(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
await provider.sendEmailVerification();
|
||||
emit(const AuthStateNeedsVerification());
|
||||
} on Exception catch (e) {
|
||||
emit(AuthStateRegistering(e));
|
||||
}
|
||||
});
|
||||
// initialize
|
||||
on<AuthEventInitialize>((event, emit) async {
|
||||
await provider.initialize();
|
||||
final user = provider.currentUser;
|
||||
if (user == null) {
|
||||
emit(const AuthStateLoggedOut(null));
|
||||
emit(
|
||||
const AuthStateLoggedOut(
|
||||
exception: null,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
} else if (!user.isEmailVerified) {
|
||||
emit(const AuthStateNeedsVerification());
|
||||
} else {
|
||||
|
@ -19,6 +43,12 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||
});
|
||||
// log in
|
||||
on<AuthEventLogIn>((event, emit) async {
|
||||
emit(
|
||||
const AuthStateLoggedOut(
|
||||
exception: null,
|
||||
isLoading: true,
|
||||
),
|
||||
);
|
||||
final email = event.email;
|
||||
final password = event.password;
|
||||
try {
|
||||
|
@ -26,19 +56,50 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||
email: email,
|
||||
password: password,
|
||||
);
|
||||
emit(AuthStateLoggedIn(user));
|
||||
|
||||
if (!user.isEmailVerified) {
|
||||
emit(
|
||||
const AuthStateLoggedOut(
|
||||
exception: null,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
emit(const AuthStateNeedsVerification());
|
||||
} else {
|
||||
emit(
|
||||
const AuthStateLoggedOut(
|
||||
exception: null,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
emit(AuthStateLoggedIn(user));
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
emit(AuthStateLoggedOut(e));
|
||||
emit(
|
||||
AuthStateLoggedOut(
|
||||
exception: e,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
// log out
|
||||
on<AuthEventLogOut>((event, emit) async {
|
||||
try {
|
||||
emit(const AuthStateLoading());
|
||||
await provider.logOut();
|
||||
emit(const AuthStateLoggedOut(null));
|
||||
emit(
|
||||
const AuthStateLoggedOut(
|
||||
exception: null,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
} on Exception catch (e) {
|
||||
emit(AuthStateLogoutFailure(e));
|
||||
emit(
|
||||
AuthStateLoggedOut(
|
||||
exception: e,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,12 +9,26 @@ class AuthEventInitialize extends AuthEvent {
|
|||
const AuthEventInitialize();
|
||||
}
|
||||
|
||||
class AuthEventSendEmailVerification extends AuthEvent {
|
||||
const AuthEventSendEmailVerification();
|
||||
}
|
||||
|
||||
class AuthEventLogIn extends AuthEvent {
|
||||
final String email;
|
||||
final String password;
|
||||
const AuthEventLogIn(this.email, this.password);
|
||||
}
|
||||
|
||||
class AuthEventRegister extends AuthEvent {
|
||||
final String email;
|
||||
final String password;
|
||||
const AuthEventRegister(this.email, this.password);
|
||||
}
|
||||
|
||||
class AuthEventShouldRegister extends AuthEvent {
|
||||
const AuthEventShouldRegister();
|
||||
}
|
||||
|
||||
class AuthEventLogOut extends AuthEvent {
|
||||
const AuthEventLogOut();
|
||||
}
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
import 'package:flutter/foundation.dart' show immutable;
|
||||
import 'package:mynotes/services/auth/auth_user.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
@immutable
|
||||
abstract class AuthState {
|
||||
const AuthState();
|
||||
}
|
||||
|
||||
class AuthStateLoading extends AuthState {
|
||||
const AuthStateLoading();
|
||||
class AuthStateUninitialized extends AuthState {
|
||||
const AuthStateUninitialized();
|
||||
}
|
||||
|
||||
class AuthStateRegistering extends AuthState {
|
||||
final Exception? exception;
|
||||
const AuthStateRegistering(this.exception);
|
||||
}
|
||||
|
||||
class AuthStateLoggedIn extends AuthState {
|
||||
|
@ -19,12 +25,14 @@ class AuthStateNeedsVerification extends AuthState {
|
|||
const AuthStateNeedsVerification();
|
||||
}
|
||||
|
||||
class AuthStateLoggedOut extends AuthState {
|
||||
class AuthStateLoggedOut extends AuthState with EquatableMixin {
|
||||
final Exception? exception;
|
||||
const AuthStateLoggedOut(this.exception);
|
||||
}
|
||||
final bool isLoading;
|
||||
const AuthStateLoggedOut({
|
||||
required this.exception,
|
||||
required this.isLoading,
|
||||
});
|
||||
|
||||
class AuthStateLogoutFailure extends AuthState {
|
||||
final Exception exception;
|
||||
const AuthStateLogoutFailure(this.exception);
|
||||
@override
|
||||
List<Object?> get props => [exception, isLoading];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef CloseDialog = void Function();
|
||||
|
||||
CloseDialog showLoadingDialog({
|
||||
required BuildContext context,
|
||||
required String text,
|
||||
}) {
|
||||
final dialog = AlertDialog(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 10.0),
|
||||
Text(text),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => dialog,
|
||||
);
|
||||
|
||||
return () => Navigator.of(context).pop();
|
||||
}
|
|
@ -6,6 +6,7 @@ import 'package:mynotes/services/auth/bloc/auth_event.dart';
|
|||
import 'package:mynotes/services/auth/bloc/auth_state.dart';
|
||||
import 'package:mynotes/utilities/dialogs/error_dialog.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:mynotes/utilities/dialogs/loading_dialog.dart';
|
||||
|
||||
class LoginView extends StatefulWidget {
|
||||
const LoginView({Key? key}) : super(key: key);
|
||||
|
@ -17,6 +18,7 @@ class LoginView extends StatefulWidget {
|
|||
class _LoginViewState extends State<LoginView> {
|
||||
late final TextEditingController _email;
|
||||
late final TextEditingController _password;
|
||||
CloseDialog? _closeDialogHandle;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -34,43 +36,54 @@ class _LoginViewState extends State<LoginView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Login'),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: _email,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter your email here',
|
||||
return BlocListener<AuthBloc, AuthState>(
|
||||
listener: (context, state) async {
|
||||
if (state is AuthStateLoggedOut) {
|
||||
final closeDialog = _closeDialogHandle;
|
||||
|
||||
if (!state.isLoading && closeDialog != null) {
|
||||
closeDialog();
|
||||
_closeDialogHandle = null;
|
||||
} else if (state.isLoading && closeDialog == null) {
|
||||
_closeDialogHandle = showLoadingDialog(
|
||||
context: context,
|
||||
text: 'Loading...',
|
||||
);
|
||||
}
|
||||
if (state.exception is UserNotFoundAuthException) {
|
||||
await showErrorDialog(context, 'User not found');
|
||||
} else if (state.exception is WrongPasswordAuthException) {
|
||||
await showErrorDialog(context, 'Wrong credentials');
|
||||
} else if (state.exception is GenericAuthException) {
|
||||
await showErrorDialog(context, 'Authentication error');
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Login'),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: _email,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter your email here',
|
||||
),
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: _password,
|
||||
obscureText: true,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter your password here',
|
||||
TextField(
|
||||
controller: _password,
|
||||
obscureText: true,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter your password here',
|
||||
),
|
||||
),
|
||||
),
|
||||
BlocListener<AuthBloc, AuthState>(
|
||||
listener: (context, state) async {
|
||||
if (state is AuthStateLoggedOut) {
|
||||
if (state.exception is UserNotFoundAuthException) {
|
||||
await showErrorDialog(context, 'User not found');
|
||||
} else if (state.exception is WrongPasswordAuthException) {
|
||||
await showErrorDialog(context, 'Wrong credentials');
|
||||
} else if (state.exception is GenericAuthException) {
|
||||
await showErrorDialog(context, 'Authentication error');
|
||||
}
|
||||
}
|
||||
},
|
||||
child: TextButton(
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final email = _email.text;
|
||||
final password = _password.text;
|
||||
|
@ -83,17 +96,16 @@ class _LoginViewState extends State<LoginView> {
|
|||
},
|
||||
child: const Text('Login'),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||
registerRoute,
|
||||
(route) => false,
|
||||
);
|
||||
},
|
||||
child: const Text('Not registered yet? Register here!'),
|
||||
)
|
||||
],
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.read<AuthBloc>().add(
|
||||
const AuthEventShouldRegister(),
|
||||
);
|
||||
},
|
||||
child: const Text('Not registered yet? Register here!'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:mynotes/constants/routes.dart';
|
||||
import 'package:mynotes/services/auth/auth_exceptions.dart';
|
||||
import 'package:mynotes/services/auth/auth_service.dart';
|
||||
import 'package:mynotes/services/auth/bloc/auth_bloc.dart';
|
||||
import 'package:mynotes/services/auth/bloc/auth_event.dart';
|
||||
import 'package:mynotes/services/auth/bloc/auth_state.dart';
|
||||
import 'package:mynotes/utilities/dialogs/error_dialog.dart';
|
||||
|
||||
class RegisterView extends StatefulWidget {
|
||||
|
@ -31,75 +35,67 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Register'),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: _email,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter your email here',
|
||||
return BlocListener<AuthBloc, AuthState>(
|
||||
listener: (context, state) async {
|
||||
if (state is AuthStateRegistering) {
|
||||
if (state.exception is WeakPasswordAuthException) {
|
||||
await showErrorDialog(context, 'Weak password');
|
||||
} else if (state.exception is EmailAlreadyInUseAuthException) {
|
||||
await showErrorDialog(context, 'Email is already in use');
|
||||
} else if (state.exception is GenericAuthException) {
|
||||
await showErrorDialog(context, 'Failed to register');
|
||||
} else if (state.exception is InvalidEmailAuthException) {
|
||||
await showErrorDialog(context, 'Invalid email');
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Register'),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: _email,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter your email here',
|
||||
),
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: _password,
|
||||
obscureText: true,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter your password here',
|
||||
TextField(
|
||||
controller: _password,
|
||||
obscureText: true,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter your password here',
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final email = _email.text;
|
||||
final password = _password.text;
|
||||
try {
|
||||
await AuthService.firebase().createUser(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
AuthService.firebase().sendEmailVerification();
|
||||
Navigator.of(context).pushNamed(verifyEmailRoute);
|
||||
} on WeakPasswordAuthException {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
'Weak password',
|
||||
);
|
||||
} on EmailAlreadyInUseAuthException {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
'Email is already in use',
|
||||
);
|
||||
} on InvalidEmailAuthException {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
'This is an invalid email address',
|
||||
);
|
||||
} on GenericAuthException {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
'Failed to register',
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('Register'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||
loginRoute,
|
||||
(route) => false,
|
||||
);
|
||||
},
|
||||
child: const Text('Already registered? Login here!'),
|
||||
)
|
||||
],
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final email = _email.text;
|
||||
final password = _password.text;
|
||||
context.read<AuthBloc>().add(
|
||||
AuthEventRegister(
|
||||
email,
|
||||
password,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Register'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.read<AuthBloc>().add(
|
||||
const AuthEventLogOut(),
|
||||
);
|
||||
},
|
||||
child: const Text('Already registered? Login here!'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mynotes/constants/routes.dart';
|
||||
import 'package:mynotes/services/auth/auth_service.dart';
|
||||
import 'package:mynotes/services/auth/bloc/auth_bloc.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:mynotes/services/auth/bloc/auth_event.dart';
|
||||
|
||||
class VerifyEmailView extends StatefulWidget {
|
||||
const VerifyEmailView({Key? key}) : super(key: key);
|
||||
|
@ -23,18 +26,18 @@ class _VerifyEmailViewState extends State<VerifyEmailView> {
|
|||
const Text(
|
||||
"If you haven't received a verification email yet, press the button below"),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await AuthService.firebase().sendEmailVerification();
|
||||
onPressed: () {
|
||||
context.read<AuthBloc>().add(
|
||||
const AuthEventSendEmailVerification(),
|
||||
);
|
||||
},
|
||||
child: const Text('Send email verification'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await AuthService.firebase().logOut();
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||
registerRoute,
|
||||
(route) => false,
|
||||
);
|
||||
context.read<AuthBloc>().add(
|
||||
const AuthEventLogOut(),
|
||||
);
|
||||
},
|
||||
child: const Text('Restart'),
|
||||
)
|
||||
|
|
|
@ -127,6 +127,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: equatable
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -44,6 +44,7 @@ dependencies:
|
|||
share_plus: ^3.0.4
|
||||
bloc: ^8.0.2
|
||||
flutter_bloc: ^8.0.1
|
||||
equatable: ^2.0.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in New Issue