diff --git a/lib/main.dart b/lib/main.dart index c8f0002..4bb19ff 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,10 @@ -import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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:mynotes/services/auth/bloc/auth_event.dart'; +import 'package:mynotes/services/auth/bloc/auth_state.dart'; +import 'package:mynotes/services/auth/firebase_auth_provider.dart'; import 'package:mynotes/views/login_view.dart'; import 'package:mynotes/views/notes/create_update_note_view.dart'; import 'package:mynotes/views/notes/notes_view.dart'; @@ -17,7 +19,10 @@ void main() { theme: ThemeData( primarySwatch: Colors.blue, ), - home: const HomePage(), + home: BlocProvider( + create: (context) => AuthBloc(FirebaseAuthProvider()), + child: const HomePage(), + ), routes: { loginRoute: (context) => const LoginView(), registerRoute: (context) => const RegisterView(), @@ -29,173 +34,26 @@ void main() { ); } -// class HomePage extends StatelessWidget { -// const HomePage({Key? key}) : super(key: key); - -// @override -// Widget build(BuildContext context) { -// return FutureBuilder( -// future: AuthService.firebase().initialize(), -// builder: (context, snapshot) { -// switch (snapshot.connectionState) { -// case ConnectionState.done: -// final user = AuthService.firebase().currentUser; -// if (user != null) { -// if (user.isEmailVerified) { -// return const NotesView(); -// } else { -// return const VerifyEmailView(); -// } -// } else { -// return const LoginView(); -// } -// default: -// return const CircularProgressIndicator(); -// } -// }, -// ); -// } -// } - -class HomePage extends StatefulWidget { +class HomePage extends StatelessWidget { const HomePage({Key? key}) : super(key: key); - @override - _HomePageState createState() => _HomePageState(); -} - -class _HomePageState extends State { - late final TextEditingController _controller; - - @override - void initState() { - _controller = TextEditingController(); - super.initState(); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => CounterBloc(), - child: Scaffold( - appBar: AppBar( - title: const Text('Testing bloc'), - ), - body: BlocConsumer( - listener: (context, state) { - _controller.clear(); - }, - builder: (context, state) { - final invalidValue = - (state is CounterStateInvalidNumber) ? state.invalidValue : ''; - return Column( - children: [ - Text('Current value => ${state.value}'), - Visibility( - child: Text('Invalid input: $invalidValue'), - visible: state is CounterStateInvalidNumber, - ), - TextField( - controller: _controller, - decoration: const InputDecoration( - hintText: 'Enter a number here', - ), - keyboardType: TextInputType.number, - ), - Row( - children: [ - TextButton( - onPressed: () { - context - .read() - .add(DecrementEvent(_controller.text)); - }, - child: const Text('-'), - ), - TextButton( - onPressed: () { - context - .read() - .add(IncrementEvent(_controller.text)); - }, - child: const Text('+'), - ), - ], - ) - ], - ); - }, - ), - ), + context.read().add(const AuthEventInitialize()); + return BlocBuilder( + builder: (context, state) { + if (state is AuthStateLoggedIn) { + return const NotesView(); + } else if (state is AuthStateNeedsVerification) { + return const VerifyEmailView(); + } else if (state is AuthStateLoggedOut) { + return const LoginView(); + } else { + return const Scaffold( + body: CircularProgressIndicator(), + ); + } + }, ); } } - -@immutable -abstract class CounterState { - final int value; - const CounterState(this.value); -} - -class CounterStateValid extends CounterState { - const CounterStateValid(int value) : super(value); -} - -class CounterStateInvalidNumber extends CounterState { - final String invalidValue; - const CounterStateInvalidNumber({ - required this.invalidValue, - required int previousValue, - }) : super(previousValue); -} - -@immutable -abstract class CounterEvent { - final String value; - const CounterEvent(this.value); -} - -class IncrementEvent extends CounterEvent { - const IncrementEvent(String value) : super(value); -} - -class DecrementEvent extends CounterEvent { - const DecrementEvent(String value) : super(value); -} - -class CounterBloc extends Bloc { - CounterBloc() : super(const CounterStateValid(0)) { - on((event, emit) { - final integer = int.tryParse(event.value); - if (integer == null) { - emit( - CounterStateInvalidNumber( - invalidValue: event.value, - previousValue: state.value, - ), - ); - } else { - emit(CounterStateValid(state.value + integer)); - } - }); - on((event, emit) { - final integer = int.tryParse(event.value); - if (integer == null) { - emit( - CounterStateInvalidNumber( - invalidValue: event.value, - previousValue: state.value, - ), - ); - } else { - emit(CounterStateValid(state.value - integer)); - } - }); - } -} diff --git a/lib/services/auth/bloc/auth_bloc.dart b/lib/services/auth/bloc/auth_bloc.dart new file mode 100644 index 0000000..4118fe0 --- /dev/null +++ b/lib/services/auth/bloc/auth_bloc.dart @@ -0,0 +1,46 @@ +import 'package:bloc/bloc.dart'; +import 'package:mynotes/services/auth/auth_provider.dart'; +import 'package:mynotes/services/auth/bloc/auth_event.dart'; +import 'package:mynotes/services/auth/bloc/auth_state.dart'; + +class AuthBloc extends Bloc { + AuthBloc(AuthProvider provider) : super(const AuthStateLoading()) { + // initialize + on((event, emit) async { + await provider.initialize(); + final user = provider.currentUser; + if (user == null) { + emit(const AuthStateLoggedOut()); + } else if (!user.isEmailVerified) { + emit(const AuthStateNeedsVerification()); + } else { + emit(AuthStateLoggedIn(user)); + } + }); + // log in + on((event, emit) async { + emit(const AuthStateLoading()); + final email = event.email; + final password = event.password; + try { + final user = await provider.logIn( + email: email, + password: password, + ); + emit(AuthStateLoggedIn(user)); + } on Exception catch (e) { + emit(AuthStateLoginFailure(e)); + } + }); + // log out + on((event, emit) async { + try { + emit(const AuthStateLoading()); + await provider.logOut(); + emit(const AuthStateLoggedOut()); + } on Exception catch (e) { + emit(AuthStateLogoutFailure(e)); + } + }); + } +} diff --git a/lib/services/auth/bloc/auth_event.dart b/lib/services/auth/bloc/auth_event.dart new file mode 100644 index 0000000..121e1e2 --- /dev/null +++ b/lib/services/auth/bloc/auth_event.dart @@ -0,0 +1,20 @@ +import 'package:flutter/foundation.dart' show immutable; + +@immutable +abstract class AuthEvent { + const AuthEvent(); +} + +class AuthEventInitialize extends AuthEvent { + const AuthEventInitialize(); +} + +class AuthEventLogIn extends AuthEvent { + final String email; + final String password; + const AuthEventLogIn(this.email, this.password); +} + +class AuthEventLogOut extends AuthEvent { + const AuthEventLogOut(); +} diff --git a/lib/services/auth/bloc/auth_state.dart b/lib/services/auth/bloc/auth_state.dart new file mode 100644 index 0000000..55082bf --- /dev/null +++ b/lib/services/auth/bloc/auth_state.dart @@ -0,0 +1,34 @@ +import 'package:flutter/foundation.dart' show immutable; +import 'package:mynotes/services/auth/auth_user.dart'; + +@immutable +abstract class AuthState { + const AuthState(); +} + +class AuthStateLoading extends AuthState { + const AuthStateLoading(); +} + +class AuthStateLoggedIn extends AuthState { + final AuthUser user; + const AuthStateLoggedIn(this.user); +} + +class AuthStateLoginFailure extends AuthState { + final Exception exception; + const AuthStateLoginFailure(this.exception); +} + +class AuthStateNeedsVerification extends AuthState { + const AuthStateNeedsVerification(); +} + +class AuthStateLoggedOut extends AuthState { + const AuthStateLoggedOut(); +} + +class AuthStateLogoutFailure extends AuthState { + final Exception exception; + const AuthStateLogoutFailure(this.exception); +} diff --git a/lib/views/login_view.dart b/lib/views/login_view.dart index 0b232af..5274fca 100644 --- a/lib/views/login_view.dart +++ b/lib/views/login_view.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.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/utilities/dialogs/error_dialog.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class LoginView extends StatefulWidget { const LoginView({Key? key}) : super(key: key); @@ -60,24 +62,12 @@ class _LoginViewState extends State { final email = _email.text; final password = _password.text; try { - await AuthService.firebase().logIn( - email: email, - password: password, - ); - final user = AuthService.firebase().currentUser; - if (user?.isEmailVerified ?? false) { - // user's email is verified - Navigator.of(context).pushNamedAndRemoveUntil( - notesRoute, - (route) => false, - ); - } else { - // user's email is NOT verified - Navigator.of(context).pushNamedAndRemoveUntil( - verifyEmailRoute, - (route) => false, - ); - } + context.read().add( + AuthEventLogIn( + email, + password, + ), + ); } on UserNotFoundAuthException { await showErrorDialog( context, diff --git a/lib/views/notes/notes_view.dart b/lib/views/notes/notes_view.dart index 737bd4f..aeb11f4 100644 --- a/lib/views/notes/notes_view.dart +++ b/lib/views/notes/notes_view.dart @@ -2,10 +2,13 @@ import 'package:flutter/material.dart'; import 'package:mynotes/constants/routes.dart'; import 'package:mynotes/enums/menu_action.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/cloud/cloud_note.dart'; import 'package:mynotes/services/cloud/firebase_cloud_storage.dart'; import 'package:mynotes/utilities/dialogs/logout_dialog.dart'; import 'package:mynotes/views/notes/notes_list_view.dart'; +import 'package:flutter_bloc/flutter_bloc.dart' show ReadContext; class NotesView extends StatefulWidget { const NotesView({Key? key}) : super(key: key); @@ -42,11 +45,9 @@ class _NotesViewState extends State { case MenuAction.logout: final shouldLogout = await showLogOutDialog(context); if (shouldLogout) { - await AuthService.firebase().logOut(); - Navigator.of(context).pushNamedAndRemoveUntil( - loginRoute, - (_) => false, - ); + context.read().add( + const AuthEventLogOut(), + ); } } },