Skip to content

Commit

Permalink
feat: add forgot password
Browse files Browse the repository at this point in the history
  • Loading branch information
zalviandyr committed Mar 18, 2022
1 parent a3e2348 commit dcb30fb
Show file tree
Hide file tree
Showing 13 changed files with 435 additions and 23 deletions.
4 changes: 4 additions & 0 deletions lib/blocs/blocs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ export 'favorite_manga_bloc.dart';
export 'history_manga_bloc.dart';
export 'user_bloc.dart';
export 'feedback_bloc.dart';

// Cubit
export 'email_verification_cubit.dart';
export 'forgot_password_cubit.dart';
1 change: 1 addition & 0 deletions lib/blocs/event_states/event_states.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export 'chapter_image_state.dart';
export 'user_event.dart';
export 'user_state.dart';
export 'user_email_state.dart';
export 'user_password_state.dart';

// Manga
export 'manga/popular_manga_event.dart';
Expand Down
5 changes: 5 additions & 0 deletions lib/blocs/event_states/user_password_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'package:manga_nih/blocs/event_states/user_state.dart';

class UserForgotPasswordSend extends UserState {}

class UserForgotPasswordSuccess extends UserState {}
79 changes: 79 additions & 0 deletions lib/blocs/forgot_password_cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'dart:developer';

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:manga_nih/blocs/event_states/event_states.dart';
import 'package:manga_nih/core/core.dart';
import 'package:manga_nih/models/models.dart';

class ForgotPasswordCubit extends Cubit<UserState> {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;

ForgotPasswordCubit() : super(UserUninitialized());

Future<void> sendForgotPassword(String email) async {
try {
emit(UserLoading());

Uri uri = Uri.https(Constants.webDomain, '', {
'time': DateTime.now().millisecondsSinceEpoch.toString(),
});

await _firebaseAuth.sendPasswordResetEmail(
email: email,
actionCodeSettings: ActionCodeSettings(
url: uri.toString(),
androidPackageName: Constants.androidPackage,
dynamicLinkDomain: Constants.dynamicLink,
handleCodeInApp: true,
androidInstallApp: true,
iOSBundleId: Constants.iosPackage,
),
);

emit(UserForgotPasswordSend());
} on FirebaseAuthException catch (e) {
log(e.toString(), name: 'ForgotPasswordCubit - sendForgotPassword');

if (e.code == 'user-not-found') {
SnackbarModel.custom(true, 'User not found, try again');
}

emit(UserError());
} catch (e) {
log(e.toString(), name: 'ForgotPasswordCubit - sendForgotPassword');

SnackbarModel.globalError();

emit(UserError());
}
}

Future<void> verifyCode(String code, String password) async {
try {
emit(UserLoading());

await _firebaseAuth.confirmPasswordReset(
code: code,
newPassword: password,
);

emit(UserForgotPasswordSuccess());
} on FirebaseAuthException catch (e) {
log(e.toString(), name: 'ForgotPasswordCubit - verifyCode');

if (e.code == 'invalid-action-code') {
SnackbarModel.custom(
true, 'Invalid code, please resend verification email');
}

emit(UserError());
} catch (e) {
log(e.toString(), name: 'ForgotPasswordCubit - verifyCode');

SnackbarModel.globalError();

emit(UserError());
}
}
}
5 changes: 4 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:manga_nih/blocs/blocs.dart';
import 'package:manga_nih/blocs/email_verification_cubit.dart';
import 'package:manga_nih/core/core.dart';
import 'package:manga_nih/ui/screens/screens.dart';

Expand All @@ -22,6 +21,10 @@ void _foregroundDynamicLink(PendingDynamicLinkData? onData) {

emailCubit.verifyCode(code);
}

if (code != null && mode == 'resetPassword') {
Get.to(() => ResetPasswordScreen(code: code));
}
}

void main() async {
Expand Down
133 changes: 133 additions & 0 deletions lib/ui/screens/auth/forgot_password_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import 'package:email_validator/email_validator.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:manga_nih/blocs/blocs.dart';
import 'package:manga_nih/models/models.dart';
import 'package:manga_nih/ui/configs/pallette.dart';
import 'package:manga_nih/blocs/event_states/event_states.dart';
import 'package:manga_nih/ui/widgets/widgets.dart';

class ForgotPasswordScreen extends StatefulWidget {
const ForgotPasswordScreen({Key? key}) : super(key: key);

@override
_ForgotPasswordScreenState createState() => _ForgotPasswordScreenState();
}

class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
final GlobalKey<FormState> _key = GlobalKey();
final TextEditingController _emailController = TextEditingController();
final ForgotPasswordCubit _forgotPasswordCubit = ForgotPasswordCubit();
String? _emailErrorText;

@override
void initState() {
// init bloc

super.initState();
}

@override
void dispose() {
_emailController.dispose();

super.dispose();
}

void _sendCodeAction() {
if (_key.currentState!.validate()) {
// re-init error
setState(() {
_emailErrorText = null;
});

String email = _emailController.text.trim();

if (!EmailValidator.validate(email)) {
setState(() => _emailErrorText = 'Email invalid');
} else {
_forgotPasswordCubit.sendForgotPassword(email);
}
}
}

void _blocListener(BuildContext context, UserState userState) {
if (userState is UserForgotPasswordSend) {
SnackbarModel.custom(false, 'Check your email');
}
}

@override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;

return BlocProvider<ForgotPasswordCubit>(
create: (_) => _forgotPasswordCubit,
child: SafeArea(
child: Scaffold(
body: Center(
child: ListView(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
children: [
Center(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 20.0, horizontal: 10.0),
width: screenSize.width * 0.9,
child: Form(
key: _key,
child: Column(
children: [
Align(
alignment: Alignment.topLeft,
child: Text(
'Forgot Password',
style: Theme.of(context)
.textTheme
.headline5!
.copyWith(color: Pallette.gradientEndColor),
),
),
const SizedBox(height: 5.0),
Align(
alignment: Alignment.topLeft,
child: Text(
'Input your valid email',
style: TextStyle(color: Colors.grey.shade700),
),
),
const SizedBox(height: 25.0),
InputField(
icon: Icons.email,
hintText: 'Email',
controller: _emailController,
errorText: _emailErrorText,
),
const SizedBox(height: 25.0),
BlocConsumer<ForgotPasswordCubit, UserState>(
listener: _blocListener,
builder: (context, state) {
if (state is UserLoading) {
return const PrimaryButton.loading();
}

return PrimaryButton(
label: 'Send code',
onTap: _sendCodeAction,
);
},
),
],
),
),
),
),
],
),
),
),
),
);
}
}
33 changes: 23 additions & 10 deletions lib/ui/screens/auth/login_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ class _LoginScreenState extends State<LoginScreen> {
}
}

void _forgotPasswordAction() {
Get.to(() => ForgotPasswordCubit());
}

void _registerAction() {
Get.to(() => const RegisterScreen());
}
Expand All @@ -89,7 +93,9 @@ class _LoginScreenState extends State<LoginScreen> {
Center(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 20.0, horizontal: 10.0),
vertical: 20.0,
horizontal: 10.0,
),
width: screenSize.width * 0.9,
child: Form(
key: _key,
Expand All @@ -110,10 +116,7 @@ class _LoginScreenState extends State<LoginScreen> {
alignment: Alignment.topLeft,
child: Text(
'Hello, welcome back to Manga nih',
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.grey.shade700),
style: TextStyle(color: Colors.grey.shade700),
),
),
const SizedBox(height: 25.0),
Expand All @@ -131,6 +134,19 @@ class _LoginScreenState extends State<LoginScreen> {
controller: _passwordController,
errorText: _passwordErrorText,
),
const SizedBox(height: 10.0),
Align(
alignment: Alignment.centerLeft,
child: TextButton(
onPressed: _forgotPasswordAction,
child: Text(
'Forgot password',
style: TextStyle(
color: Pallette.gradientStartColor,
),
),
),
),
const SizedBox(height: 25.0),
BlocConsumer<UserBloc, UserState>(
listener: _blocListener,
Expand Down Expand Up @@ -159,11 +175,8 @@ class _LoginScreenState extends State<LoginScreen> {
onPressed: _registerAction,
child: Text(
'Create an account',
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(
color: Pallette.gradientStartColor),
style: TextStyle(
color: Pallette.gradientStartColor),
),
),
],
Expand Down
12 changes: 3 additions & 9 deletions lib/ui/screens/auth/register_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
alignment: Alignment.topLeft,
child: Text(
'Register a new account to read manga',
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.grey.shade700),
style: TextStyle(color: Colors.grey.shade700),
),
),
const SizedBox(height: 25.0),
Expand Down Expand Up @@ -184,11 +181,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
onPressed: _loginAction,
child: Text(
'Login an account',
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(
color: Pallette.gradientStartColor),
style: TextStyle(
color: Pallette.gradientStartColor),
),
),
],
Expand Down
Loading

0 comments on commit dcb30fb

Please sign in to comment.