Skip to content

Commit 9b8a58e

Browse files
committed
Added email verification to cubit and repo
1 parent 914efe4 commit 9b8a58e

File tree

14 files changed

+303
-26
lines changed

14 files changed

+303
-26
lines changed

lib/features/auth/data/firebase_auth_repo.dart

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import 'package:azt/features/auth/domain/repos/auth_repo.dart';
33
import 'package:firebase_auth/firebase_auth.dart';
44
import 'package:google_sign_in/google_sign_in.dart';
55
import 'package:flutter/foundation.dart' show kIsWeb;
6-
6+
import 'package:cloud_firestore/cloud_firestore.dart';
77

88
class FirebaseAuthRepo implements AuthRepo{
99
//firebase access
@@ -19,13 +19,29 @@ class FirebaseAuthRepo implements AuthRepo{
1919
.signInWithEmailAndPassword(email: email, password: password);
2020

2121
//create user
22-
AppUser user = AppUser(uid: userCredential.user!.uid, email: email);
22+
AppUser user = AppUser(uid: userCredential.user!.uid, email: email,emailVerified: userCredential.user!.emailVerified);
2323

2424
//return user
2525
return user;
2626

27+
} on FirebaseAuthException catch (e){
28+
switch (e.code) {
29+
case 'invalid-email':
30+
throw Exception('The email address is not valid.');
31+
case 'user-disabled':
32+
throw Exception('This user has been disabled.');
33+
case 'user-not-found':
34+
throw Exception('No user found for that email.');
35+
case 'wrong-password':
36+
throw Exception('Wrong password provided for that user.');
37+
case 'too-many-requests':
38+
throw Exception('Too many attempts. Try again later.');
39+
case 'network-request-failed':
40+
throw Exception('Please connect to internet and try again later');
41+
default:
42+
throw Exception('Login failed. ${e.message ?? 'Please try again.'}');
43+
}
2744
}
28-
2945
//catch errors
3046
catch(e){
3147
throw Exception('Login failed: $e');
@@ -42,10 +58,23 @@ class FirebaseAuthRepo implements AuthRepo{
4258
.createUserWithEmailAndPassword(email: email, password: password);
4359

4460
//create user
45-
AppUser user = AppUser(uid: userCredential.user!.uid, email: email);
61+
AppUser user = AppUser(uid: userCredential.user!.uid, email: email,emailVerified: userCredential.user!.emailVerified);
4662

4763
//return user
4864
return user;
65+
} on FirebaseAuthException catch(e){
66+
switch (e.code) {
67+
case 'invalid-email':
68+
throw Exception('The email address is not valid.');
69+
case 'email-already-in-use':
70+
throw Exception('Email address is already in use');
71+
case 'too-many-requests':
72+
throw Exception('Too many attempts. Try again later.');
73+
case 'network-request-failed':
74+
throw Exception('Please connect to internet and try again later');
75+
default:
76+
throw Exception('Registration failed. ${e.message ?? 'Please try again.'}');
77+
}
4978
}
5079
catch(e){
5180
throw Exception('Registration failed: $e');
@@ -80,12 +109,11 @@ class FirebaseAuthRepo implements AuthRepo{
80109
Future<AppUser?> getCurrentUser() async{
81110
//get current logged in user from db
82111
final firebaseUser = firebaseAuth.currentUser;
83-
84112
//no logged in user
85113
if (firebaseUser == null) return null;
86114

87115
//logged in user
88-
return AppUser(uid: firebaseUser.uid, email: firebaseUser.email!);
116+
return AppUser(uid: firebaseUser.uid, email: firebaseUser.email!,emailVerified: firebaseUser.emailVerified);
89117
}
90118

91119

@@ -135,12 +163,44 @@ class FirebaseAuthRepo implements AuthRepo{
135163
AppUser appUser = AppUser(
136164
uid: firebaseUser.uid,
137165
email: firebaseUser.email ?? '',
166+
emailVerified: firebaseUser.emailVerified, //Should be set to true by default after gsignin
138167
);
139168
return appUser;
140169
}
141170
catch (e) {
142171
print(e);
143172
return null;
144173
}
174+
}
175+
Future<void> sendEmailVerification() async {
176+
try {
177+
final user = firebaseAuth.currentUser;
178+
if (user != null && !user.emailVerified) {
179+
await user.sendEmailVerification();
180+
}
181+
} catch (e) {
182+
throw Exception('Failed to send verification email: $e');
183+
}
184+
}
185+
Future<AppUser?> reloadUser() async {
186+
try {
187+
final user = firebaseAuth.currentUser;
188+
if (user == null) return null;
189+
190+
// Reload user data from Firebase
191+
await user.reload();
192+
193+
// Get the updated user
194+
final updatedUser = firebaseAuth.currentUser;
195+
if (updatedUser == null) return null;
196+
197+
return AppUser(
198+
uid: updatedUser.uid,
199+
email: updatedUser.email!,
200+
emailVerified: updatedUser.emailVerified,
201+
);
202+
} catch (e) {
203+
throw Exception('Failed to reload user: $e');
204+
}
145205
}
146206
}

lib/features/auth/data/tempCodeRunnerFile.dart

Whitespace-only changes.
Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
class AppUser {
22
final String uid;
33
final String email;
4+
final bool emailVerified;
5+
final String name; // Add this field
46

57
AppUser({
68
required this.uid,
79
required this.email,
10+
this.emailVerified = false,
11+
this.name = '', // Default to empty string
812
});
913

10-
//app user to json converter
14+
// toJson converter
1115
Map<String,dynamic> toJson(){
1216
return{
13-
'uid':uid,
14-
'email':email,
17+
'uid': uid,
18+
'email': email,
19+
'emailVerified': emailVerified,
20+
'name': name,
1521
};
1622
}
1723

18-
//json to app user converter
24+
// fromJson converter
1925
factory AppUser.fromJson(Map<String,dynamic> jsonUser){
20-
return AppUser(
21-
uid: jsonUser['uid'],
22-
email:jsonUser['email'],
23-
);
26+
return AppUser(
27+
uid: jsonUser['uid'],
28+
email: jsonUser['email'],
29+
emailVerified: jsonUser['emailVerified'] ?? false,
30+
name: jsonUser['name'] ?? '',
31+
);
2432
}
2533
}

lib/features/auth/domain/repos/auth_repo.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ abstract class AuthRepo {
99
Future<String> sendPasswordResetEmail(String email);
1010
Future<void> deleteAccount();
1111
Future<AppUser?> signInWithGoogle();
12+
Future<void> sendEmailVerification();
13+
Future<AppUser?> reloadUser();
1214
}

lib/features/auth/presentation/cubits/auth_cubit.dart

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ class AuthCubit extends Cubit<AuthState>{
2424

2525
if (user!= null){
2626
_currentUser = user;
27-
emit(Authenticated(user));
27+
if (user.emailVerified){
28+
emit(Authenticated(user));
29+
}
30+
else{
31+
emit(EmailNotVerified(user));
32+
}
2833
} else{
2934
emit(Unauthenticated());
3035
}
@@ -35,10 +40,15 @@ class AuthCubit extends Cubit<AuthState>{
3540
try{
3641
emit(AuthLoading());
3742
final user = await authRepo.loginWithEmailPassword(email, pw);
38-
43+
3944
if (user != null){
4045
_currentUser = user;
41-
emit(Authenticated(user));
46+
if(user.emailVerified){
47+
emit(Authenticated(user));
48+
}
49+
else{
50+
emit(EmailNotVerified(user));
51+
}
4252
} else{
4353
emit(Unauthenticated());
4454
}
@@ -54,10 +64,15 @@ class AuthCubit extends Cubit<AuthState>{
5464
try{
5565
emit(AuthLoading());
5666
final user = await authRepo.registerWithEmailPassword(name, email, pw);
57-
67+
authRepo.sendEmailVerification();
5868
if (user != null){
5969
_currentUser = user;
70+
if(user.emailVerified){
6071
emit(Authenticated(user));
72+
}
73+
else{
74+
emit(EmailNotVerified(user));
75+
}
6176
} else{
6277
emit(Unauthenticated());
6378
}
@@ -117,4 +132,45 @@ class AuthCubit extends Cubit<AuthState>{
117132
emit(Unauthenticated());
118133
}
119134
}
135+
// Send email verification
136+
Future<void> sendEmailVerification() async {
137+
try {
138+
await authRepo.sendEmailVerification();
139+
} catch (e) {
140+
emit(AuthError(e.toString()));
141+
}
142+
}
143+
144+
// Reload user to check email verification status
145+
Future<void> reloadUser() async {
146+
try {
147+
final user = await authRepo.reloadUser();
148+
if (user != null) {
149+
_currentUser = user;
150+
emit(Authenticated(user));
151+
}
152+
} catch (e) {
153+
emit(AuthError(e.toString()));
154+
}
155+
}
156+
Future<void> checkEmailVerification() async {
157+
158+
final user = await authRepo.reloadUser();
159+
if (user != null) {
160+
final updatedUser = await authRepo.getCurrentUser();
161+
162+
if (updatedUser?.emailVerified ?? false) {
163+
emit(Authenticated(updatedUser!));
164+
} else {
165+
emit(EmailNotVerified(updatedUser!));
166+
}
167+
}
168+
}
169+
Future<void> resendVerificationEmail() async {
170+
try {
171+
await authRepo.sendEmailVerification();
172+
} catch (e) {
173+
emit(AuthError(e.toString()));
174+
}
175+
}
120176
}

lib/features/auth/presentation/cubits/auth_states.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ class Authenticated extends AuthState{
1616

1717
//unauthenticated
1818
class Unauthenticated extends AuthState{}
19-
19+
class EmailNotVerified extends AuthState {
20+
final AppUser user;
21+
EmailNotVerified(this.user);
22+
}
2023
//errors
2124
class AuthError extends AuthState{
2225
final String message;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import 'package:flutter/material.dart';
2+
import 'dart:async';
3+
import 'package:azt/features/auth/presentation/cubits/auth_cubit.dart';
4+
import 'package:azt/features/auth/presentation/cubits/auth_states.dart';
5+
import 'package:flutter_bloc/flutter_bloc.dart';
6+
7+
8+
class EmailVerificationScreen extends StatefulWidget {
9+
const EmailVerificationScreen({Key? key}) : super(key: key);
10+
11+
@override
12+
State<EmailVerificationScreen> createState() => _EmailVerificationScreenState();
13+
}
14+
15+
class _EmailVerificationScreenState extends State<EmailVerificationScreen> {
16+
Timer? _timer;
17+
bool _isResending = false;
18+
19+
@override
20+
void initState() {
21+
super.initState();
22+
// Auto-check verification status every 3 seconds
23+
_timer = Timer.periodic(const Duration(seconds: 3), (timer) {
24+
BlocProvider.of<AuthCubit>(context).checkEmailVerification();
25+
});
26+
}
27+
28+
@override
29+
void dispose() {
30+
_timer?.cancel();
31+
super.dispose();
32+
}
33+
34+
Future<void> _resendEmail() async {
35+
setState(() => _isResending = true);
36+
await BlocProvider.of<AuthCubit>(context).resendVerificationEmail();
37+
38+
if (mounted) {
39+
ScaffoldMessenger.of(context).showSnackBar(
40+
const SnackBar(content: Text('Verification email sent!')),
41+
);
42+
setState(() => _isResending = false);
43+
}
44+
}
45+
46+
@override
47+
Widget build(BuildContext context) {
48+
final authState = BlocProvider.of<AuthCubit>(context).state;
49+
final userEmail = authState is EmailNotVerified ? authState.user.email : 'your email';
50+
51+
return Scaffold(
52+
appBar: AppBar(
53+
title: const Text('Verify Email'),
54+
actions: [
55+
IconButton(
56+
icon: const Icon(Icons.logout),
57+
onPressed: () => BlocProvider.of<AuthCubit>(context).logout(),
58+
),
59+
],
60+
),
61+
body: Center(
62+
child: Padding(
63+
padding: const EdgeInsets.all(24.0),
64+
child: Column(
65+
mainAxisAlignment: MainAxisAlignment.center,
66+
children: [
67+
const Icon(
68+
Icons.email_outlined,
69+
size: 100,
70+
color: Colors.blue,
71+
),
72+
const SizedBox(height: 32),
73+
const Text(
74+
'Verify Your Email',
75+
style: TextStyle(
76+
fontSize: 24,
77+
fontWeight: FontWeight.bold,
78+
),
79+
),
80+
const SizedBox(height: 16),
81+
Text(
82+
'We\'ve sent a verification email to\n$userEmail',
83+
textAlign: TextAlign.center,
84+
style: const TextStyle(fontSize: 16),
85+
),
86+
const SizedBox(height: 8),
87+
const Text(
88+
'Please check your inbox and click the verification link.',
89+
textAlign: TextAlign.center,
90+
style: TextStyle(color: Colors.grey),
91+
),
92+
const SizedBox(height: 32),
93+
ElevatedButton.icon(
94+
onPressed: () => BlocProvider.of<AuthCubit>(context).checkEmailVerification(),
95+
icon: const Icon(Icons.refresh),
96+
label: const Text('I\'ve verified my email'),
97+
),
98+
const SizedBox(height: 16),
99+
TextButton(
100+
onPressed: _isResending ? null : _resendEmail,
101+
child: Text(
102+
_isResending ? 'Sending...' : 'Resend verification email',
103+
),
104+
),
105+
],
106+
),
107+
),
108+
),
109+
);
110+
}
111+
}

0 commit comments

Comments
 (0)