step-27
This commit is contained in:
parent
cd3aaa46af
commit
b27fe5bbc8
|
@ -423,6 +423,9 @@ PODS:
|
|||
- GoogleUtilities/UserDefaults (~> 7.6)
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- Flutter (1.0.0)
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- GoogleAppMeasurement (8.9.1):
|
||||
- GoogleAppMeasurement/AdIdSupport (= 8.9.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 7.6)
|
||||
|
@ -497,7 +500,16 @@ PODS:
|
|||
- nanopb/encode (= 2.30908.0)
|
||||
- nanopb/decode (2.30908.0)
|
||||
- nanopb/encode (2.30908.0)
|
||||
- path_provider_ios (0.0.1):
|
||||
- Flutter
|
||||
- PromisesObjC (2.0.0)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- sqflite (0.0.2):
|
||||
- Flutter
|
||||
- FMDB (>= 2.7.5)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`)
|
||||
|
@ -505,6 +517,10 @@ DEPENDENCIES:
|
|||
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
|
@ -517,6 +533,7 @@ SPEC REPOS:
|
|||
- FirebaseCoreDiagnostics
|
||||
- FirebaseFirestore
|
||||
- FirebaseInstallations
|
||||
- FMDB
|
||||
- GoogleAppMeasurement
|
||||
- GoogleDataTransport
|
||||
- GoogleUtilities
|
||||
|
@ -538,6 +555,14 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/firebase_core/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
path_provider_ios:
|
||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
abseil: 6c8eb7892aefa08d929b39f9bb108e5367e3228f
|
||||
|
@ -554,6 +579,7 @@ SPEC CHECKSUMS:
|
|||
FirebaseFirestore: 15ae9648476436efed698a909e44c4737498f9b4
|
||||
FirebaseInstallations: 830327b45345ffc859eaa9c17bcd5ae893fd5425
|
||||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
GoogleAppMeasurement: 837649ad3987936c232f6717c5680216f6243d24
|
||||
GoogleDataTransport: 629c20a4d363167143f30ea78320d5a7eb8bd940
|
||||
GoogleUtilities: 684ee790a24f73ebb2d1d966e9711c203f2a4237
|
||||
|
@ -562,7 +588,11 @@ SPEC CHECKSUMS:
|
|||
GTMSessionFetcher: 43748f93435c2aa068b1cbe39655aaf600652e91
|
||||
leveldb-library: 50c7b45cbd7bf543c81a468fe557a16ae3db8729
|
||||
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
|
||||
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
|
||||
PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58
|
||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||
url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af
|
||||
|
||||
PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ 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/forgot_password_view.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';
|
||||
|
@ -55,6 +56,8 @@ class HomePage extends StatelessWidget {
|
|||
return const VerifyEmailView();
|
||||
} else if (state is AuthStateLoggedOut) {
|
||||
return const LoginView();
|
||||
} else if (state is AuthStateForgotPassword) {
|
||||
return const ForgotPasswordView();
|
||||
} else if (state is AuthStateRegistering) {
|
||||
return const RegisterView();
|
||||
} else {
|
||||
|
|
|
@ -13,4 +13,5 @@ abstract class AuthProvider {
|
|||
});
|
||||
Future<void> logOut();
|
||||
Future<void> sendEmailVerification();
|
||||
Future<void> sendPasswordReset({required String toEmail});
|
||||
}
|
||||
|
|
|
@ -39,4 +39,8 @@ class AuthService implements AuthProvider {
|
|||
|
||||
@override
|
||||
Future<void> initialize() => provider.initialize();
|
||||
|
||||
@override
|
||||
Future<void> sendPasswordReset({required String toEmail}) =>
|
||||
provider.sendPasswordReset(toEmail: toEmail);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,48 @@ import 'package:mynotes/services/auth/bloc/auth_state.dart';
|
|||
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
AuthBloc(AuthProvider provider)
|
||||
: super(const AuthStateUninitialized(isLoading: true)) {
|
||||
on<AuthEventShouldRegister>((event, emit) {
|
||||
emit(const AuthStateRegistering(
|
||||
exception: null,
|
||||
isLoading: false,
|
||||
));
|
||||
});
|
||||
//forgot password
|
||||
on<AuthEventForgotPassword>((event, emit) async {
|
||||
emit(const AuthStateForgotPassword(
|
||||
exception: null,
|
||||
hasSentEmail: false,
|
||||
isLoading: false,
|
||||
));
|
||||
final email = event.email;
|
||||
if (email == null) {
|
||||
return; // user just wants to go to forgot-password screen
|
||||
}
|
||||
|
||||
// user wants to actually send a forgot-password email
|
||||
emit(const AuthStateForgotPassword(
|
||||
exception: null,
|
||||
hasSentEmail: false,
|
||||
isLoading: true,
|
||||
));
|
||||
|
||||
bool didSendEmail;
|
||||
Exception? exception;
|
||||
try {
|
||||
await provider.sendPasswordReset(toEmail: email);
|
||||
didSendEmail = true;
|
||||
exception = null;
|
||||
} on Exception catch (e) {
|
||||
didSendEmail = false;
|
||||
exception = e;
|
||||
}
|
||||
|
||||
emit(AuthStateForgotPassword(
|
||||
exception: exception,
|
||||
hasSentEmail: didSendEmail,
|
||||
isLoading: false,
|
||||
));
|
||||
});
|
||||
// send email verification
|
||||
on<AuthEventSendEmailVerification>((event, emit) async {
|
||||
await provider.sendEmailVerification();
|
||||
|
|
|
@ -29,6 +29,11 @@ class AuthEventShouldRegister extends AuthEvent {
|
|||
const AuthEventShouldRegister();
|
||||
}
|
||||
|
||||
class AuthEventForgotPassword extends AuthEvent {
|
||||
final String? email;
|
||||
const AuthEventForgotPassword({this.email});
|
||||
}
|
||||
|
||||
class AuthEventLogOut extends AuthEvent {
|
||||
const AuthEventLogOut();
|
||||
}
|
||||
|
|
|
@ -25,6 +25,16 @@ class AuthStateRegistering extends AuthState {
|
|||
}) : super(isLoading: isLoading);
|
||||
}
|
||||
|
||||
class AuthStateForgotPassword extends AuthState {
|
||||
final Exception? exception;
|
||||
final bool hasSentEmail;
|
||||
const AuthStateForgotPassword({
|
||||
required this.exception,
|
||||
required this.hasSentEmail,
|
||||
required bool isLoading,
|
||||
}) : super(isLoading: isLoading);
|
||||
}
|
||||
|
||||
class AuthStateLoggedIn extends AuthState {
|
||||
final AuthUser user;
|
||||
const AuthStateLoggedIn({
|
||||
|
|
|
@ -104,4 +104,22 @@ class FirebaseAuthProvider implements AuthProvider {
|
|||
throw UserNotLoggedInAuthException();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> sendPasswordReset({required String toEmail}) async {
|
||||
try {
|
||||
await FirebaseAuth.instance.sendPasswordResetEmail(email: toEmail);
|
||||
} on FirebaseAuthException catch (e) {
|
||||
switch (e.code) {
|
||||
case 'firebase_auth/invalid-email':
|
||||
throw InvalidEmailAuthException();
|
||||
case 'firebase_auth/user-not-found':
|
||||
throw UserNotFoundAuthException();
|
||||
default:
|
||||
throw GenericAuthException();
|
||||
}
|
||||
} catch (_) {
|
||||
throw GenericAuthException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mynotes/utilities/dialogs/generic_dialog.dart';
|
||||
|
||||
Future<void> showPasswordResetSentDialog(BuildContext context) {
|
||||
return showGenericDialog<void>(
|
||||
context: context,
|
||||
title: 'Password Reset',
|
||||
content:
|
||||
'We have now sent you a password reset link. Please check your email for more information.',
|
||||
optionsBuilder: () => {
|
||||
'OK': null,
|
||||
},
|
||||
);
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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';
|
||||
import 'package:mynotes/utilities/dialogs/password_reset_email_sent_dialog.dart';
|
||||
|
||||
class ForgotPasswordView extends StatefulWidget {
|
||||
const ForgotPasswordView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ForgotPasswordViewState createState() => _ForgotPasswordViewState();
|
||||
}
|
||||
|
||||
class _ForgotPasswordViewState extends State<ForgotPasswordView> {
|
||||
late final TextEditingController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_controller = TextEditingController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<AuthBloc, AuthState>(
|
||||
listener: (context, state) async {
|
||||
if (state is AuthStateForgotPassword) {
|
||||
if (state.hasSentEmail) {
|
||||
_controller.clear();
|
||||
await showPasswordResetSentDialog(context);
|
||||
}
|
||||
if (state.exception != null) {
|
||||
await showErrorDialog(context,
|
||||
'We could not process your request. Please make sure that you are a registered user, or if not, register a user now by going back one step.');
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Forgot Password'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
'If you forgot your password, simply enter your email and we will send you a password reset link.'),
|
||||
TextField(
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
autocorrect: false,
|
||||
autofocus: true,
|
||||
controller: _controller,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Your email address....',
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final email = _controller.text;
|
||||
context
|
||||
.read<AuthBloc>()
|
||||
.add(AuthEventForgotPassword(email: email));
|
||||
},
|
||||
child: const Text('Send me password reset link'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.read<AuthBloc>().add(
|
||||
const AuthEventLogOut(),
|
||||
);
|
||||
},
|
||||
child: const Text('Back to login page'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -39,7 +39,10 @@ class _LoginViewState extends State<LoginView> {
|
|||
listener: (context, state) async {
|
||||
if (state is AuthStateLoggedOut) {
|
||||
if (state.exception is UserNotFoundAuthException) {
|
||||
await showErrorDialog(context, 'User not found');
|
||||
await showErrorDialog(
|
||||
context,
|
||||
'Cannot find a user with the entered credentials!',
|
||||
);
|
||||
} else if (state.exception is WrongPasswordAuthException) {
|
||||
await showErrorDialog(context, 'Wrong credentials');
|
||||
} else if (state.exception is GenericAuthException) {
|
||||
|
@ -51,48 +54,61 @@ class _LoginViewState extends State<LoginView> {
|
|||
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',
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
'Please log in to your account in order to interact with and create notes!'),
|
||||
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;
|
||||
context.read<AuthBloc>().add(
|
||||
AuthEventLogIn(
|
||||
email,
|
||||
password,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Login'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.read<AuthBloc>().add(
|
||||
const AuthEventShouldRegister(),
|
||||
);
|
||||
},
|
||||
child: const Text('Not registered yet? Register here!'),
|
||||
)
|
||||
],
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final email = _email.text;
|
||||
final password = _password.text;
|
||||
context.read<AuthBloc>().add(
|
||||
AuthEventLogIn(
|
||||
email,
|
||||
password,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Login'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.read<AuthBloc>().add(
|
||||
const AuthEventForgotPassword(),
|
||||
);
|
||||
},
|
||||
child: const Text('I forgot my password'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.read<AuthBloc>().add(
|
||||
const AuthEventShouldRegister(),
|
||||
);
|
||||
},
|
||||
child: const Text('Not registered yet? Register here!'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -53,48 +53,60 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
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',
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Enter your email and password to see your notes!'),
|
||||
TextField(
|
||||
controller: _email,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
autofocus: true,
|
||||
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;
|
||||
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!'),
|
||||
)
|
||||
],
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
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!'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -144,4 +144,9 @@ class MockAuthProvider implements AuthProvider {
|
|||
);
|
||||
_user = newUser;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> sendPasswordReset({required String toEmail}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue