Skip to content

Commit af336f3

Browse files
committed
feat(auth): add authentication bloc and page
- Implemented AuthenticationBloc - Added AuthenticationEvent and State - Created AuthenticationPage
1 parent 803ed52 commit af336f3

14 files changed

+442
-26
lines changed

lib/app/bloc/app_bloc.dart

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,36 @@
1+
import 'dart:async';
2+
13
import 'package:bloc/bloc.dart';
24
import 'package:equatable/equatable.dart';
35
import 'package:flutter/material.dart';
4-
import 'package:go_router/go_router.dart';
5-
import 'package:ht_main/router/routes.dart';
6+
import 'package:ht_authentication_firebase/ht_authentication_firebase.dart';
7+
import 'package:ht_authentication_repository/ht_authentication_repository.dart';
68

79
part 'app_event.dart';
810
part 'app_state.dart';
911

1012
class AppBloc extends Bloc<AppEvent, AppState> {
11-
AppBloc() : super(const AppState()) {
13+
AppBloc({required HtAuthenticationRepository authenticationRepository})
14+
: _authenticationRepository = authenticationRepository,
15+
super(const AppState()) {
1216
on<AppNavigationIndexChanged>(_onAppNavigationIndexChanged);
1317
on<AppThemeChanged>(_onAppThemeChanged);
18+
on<AppUserChanged>(_onAppUserChanged);
19+
20+
_userSubscription = _authenticationRepository.user.listen(
21+
(user) => add(AppUserChanged(user)),
22+
);
1423
}
1524

25+
final HtAuthenticationRepository _authenticationRepository;
26+
late final StreamSubscription<User> _userSubscription;
27+
1628
void _onAppNavigationIndexChanged(
1729
AppNavigationIndexChanged event,
1830
Emitter<AppState> emit,
1931
) {
2032
emit(state.copyWith(selectedBottomNavigationIndex: event.index));
21-
event.context.goNamed(Routes.getRouteNameByIndex(event.index));
33+
// event.context.goNamed(Routes.getRouteNameByIndex(event.index)); // Removed direct navigation
2234
}
2335

2436
void _onAppThemeChanged(
@@ -33,4 +45,21 @@ class AppBloc extends Bloc<AppEvent, AppState> {
3345
),
3446
);
3547
}
48+
49+
void _onAppUserChanged(
50+
AppUserChanged event,
51+
Emitter<AppState> emit,
52+
) {
53+
if (event.user.isAnonymous) {
54+
emit(state.copyWith(status: AppStatus.unauthenticated));
55+
} else {
56+
emit(state.copyWith(status: AppStatus.authenticated));
57+
}
58+
}
59+
60+
@override
61+
Future<void> close() {
62+
_userSubscription.cancel();
63+
return super.close();
64+
}
3665
}

lib/app/bloc/app_event.dart

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,36 @@
11
part of 'app_bloc.dart';
22

3-
@immutable
4-
sealed class AppEvent {}
3+
sealed class AppEvent extends Equatable {
4+
const AppEvent();
5+
6+
@override
7+
List<Object> get props => [];
8+
}
59

610
final class AppNavigationIndexChanged extends AppEvent {
7-
AppNavigationIndexChanged({required this.index, required this.context});
11+
const AppNavigationIndexChanged(this.context, this.index);
812

9-
final int index;
1013
final BuildContext context;
14+
final int index;
15+
16+
@override
17+
List<Object> get props => [index];
1118
}
1219

13-
final class AppThemeChanged extends AppEvent {}
20+
final class AppThemeChanged extends AppEvent {
21+
const AppThemeChanged();
22+
}
23+
24+
/// {@template app_user_changed}
25+
/// Event triggered when the user's authentication state changes.
26+
/// {@endtemplate}
27+
final class AppUserChanged extends AppEvent {
28+
/// {@macro app_user_changed}
29+
const AppUserChanged(this.user);
30+
31+
/// The updated [User] object.
32+
final User user;
33+
34+
@override
35+
List<Object> get props => [user];
36+
}

lib/app/bloc/app_state.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
11
part of 'app_bloc.dart';
22

3+
enum AppStatus { authenticated, unauthenticated }
4+
35
class AppState extends Equatable {
46
const AppState({
57
this.selectedBottomNavigationIndex = 0,
68
this.themeMode = ThemeMode.system,
9+
this.status = AppStatus.unauthenticated,
710
});
811
final int selectedBottomNavigationIndex;
912
final ThemeMode themeMode;
13+
final AppStatus status;
1014

1115
AppState copyWith({
1216
int? selectedBottomNavigationIndex,
1317
ThemeMode? themeMode,
18+
AppStatus? status,
1419
}) {
1520
return AppState(
1621
selectedBottomNavigationIndex:
1722
selectedBottomNavigationIndex ?? this.selectedBottomNavigationIndex,
1823
themeMode: themeMode ?? this.themeMode,
24+
status: status ?? this.status,
1925
);
2026
}
2127

2228
@override
23-
List<Object?> get props => [selectedBottomNavigationIndex, themeMode];
29+
List<Object?> get props => [selectedBottomNavigationIndex, themeMode, status];
2430
}

lib/app/view/app.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:flex_color_scheme/flex_color_scheme.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_bloc/flutter_bloc.dart';
44
import 'package:google_fonts/google_fonts.dart';
5+
import 'package:ht_authentication_repository/ht_authentication_repository.dart';
56
import 'package:ht_headlines_repository/ht_headlines_repository.dart';
67
import 'package:ht_main/app/bloc/app_bloc.dart';
78
import 'package:ht_main/l10n/l10n.dart';
@@ -10,19 +11,28 @@ import 'package:ht_main/router/router.dart';
1011
class App extends StatelessWidget {
1112
const App({
1213
required HtHeadlinesRepository htHeadlinesRepository,
14+
required HtAuthenticationRepository htAuthenticationRepository,
1315
super.key,
14-
}) : _htHeadlinesRepository = htHeadlinesRepository;
16+
}) : _htHeadlinesRepository = htHeadlinesRepository,
17+
_htAuthenticationRepository = htAuthenticationRepository;
1518

1619
final HtHeadlinesRepository _htHeadlinesRepository;
20+
final HtAuthenticationRepository _htAuthenticationRepository;
1721

1822
@override
1923
Widget build(BuildContext context) {
2024
return MultiRepositoryProvider(
21-
providers: [RepositoryProvider.value(value: _htHeadlinesRepository)],
25+
providers: [
26+
RepositoryProvider.value(value: _htHeadlinesRepository),
27+
RepositoryProvider.value(value: _htAuthenticationRepository),
28+
],
2229
child: MultiBlocProvider(
2330
providers: [
2431
BlocProvider(
25-
create: (context) => AppBloc(),
32+
create: (context) => AppBloc(
33+
authenticationRepository:
34+
context.read<HtAuthenticationRepository>(),
35+
),
2636
),
2737
],
2838
child: const _AppView(),

lib/app/view/app_scaffold.dart

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
33
import 'package:flutter_bloc/flutter_bloc.dart';
4-
import 'package:go_router/go_router.dart';
54
import 'package:ht_main/app/bloc/app_bloc.dart';
6-
import 'package:ht_main/router/routes.dart';
75

86
class AppScaffold extends StatelessWidget {
97
const AppScaffold({required this.child, super.key});
@@ -27,13 +25,8 @@ class AppScaffold extends StatelessWidget {
2725
selectedIndex: state.selectedBottomNavigationIndex,
2826
onSelectedIndexChange: (index) {
2927
context.read<AppBloc>().add(
30-
AppNavigationIndexChanged(index: index, context: context),
28+
AppNavigationIndexChanged(context, index),
3129
);
32-
if (index == 0) {
33-
context.goNamed(Routes.headlinesFeedName);
34-
} else if (index == 1) {
35-
context.goNamed(Routes.settingsName);
36-
}
3730
},
3831
destinations: const [
3932
NavigationDestination(
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import 'dart:async';
2+
3+
import 'package:bloc/bloc.dart';
4+
import 'package:equatable/equatable.dart';
5+
import 'package:ht_authentication_firebase/ht_authentication_firebase.dart';
6+
import 'package:ht_authentication_repository/ht_authentication_repository.dart';
7+
8+
part 'authentication_event.dart';
9+
part 'authentication_state.dart';
10+
11+
/// {@template authentication_bloc}
12+
/// Bloc responsible for managing the authentication state of the application.
13+
/// {@endtemplate}
14+
class AuthenticationBloc
15+
extends Bloc<AuthenticationEvent, AuthenticationState> {
16+
/// {@macro authentication_bloc}
17+
AuthenticationBloc({
18+
required HtAuthenticationRepository authenticationRepository,
19+
}) : _authenticationRepository = authenticationRepository,
20+
super(AuthenticationInitial()) {
21+
on<AuthenticationUserChanged>(_onAuthenticationUserChanged);
22+
on<AuthenticationEmailSignInRequested>(
23+
_onAuthenticationEmailSignInRequested,);
24+
on<AuthenticationGoogleSignInRequested>(
25+
_onAuthenticationGoogleSignInRequested,
26+
);
27+
on<AuthenticationAnonymousSignInRequested>(
28+
_onAuthenticationAnonymousSignInRequested,
29+
);
30+
on<AuthenticationSignOutRequested>(_onAuthenticationSignOutRequested);
31+
on<AuthenticationDeleteAccountRequested>(
32+
_onAuthenticationDeleteAccountRequested,
33+
);
34+
35+
_userSubscription = _authenticationRepository.user.listen(
36+
(user) => add(AuthenticationUserChanged(user)),
37+
);
38+
}
39+
40+
final HtAuthenticationRepository _authenticationRepository;
41+
late final StreamSubscription<User> _userSubscription;
42+
43+
/// Handles [AuthenticationUserChanged] events.
44+
void _onAuthenticationUserChanged(
45+
AuthenticationUserChanged event,
46+
Emitter<AuthenticationState> emit,
47+
) {
48+
if (event.user.isAnonymous) {
49+
emit(AuthenticationUnauthenticated());
50+
} else {
51+
emit(AuthenticationAuthenticated(event.user));
52+
}
53+
}
54+
55+
/// Handles [AuthenticationEmailSignInRequested] events.
56+
Future<void> _onAuthenticationEmailSignInRequested(
57+
AuthenticationEmailSignInRequested event,
58+
Emitter<AuthenticationState> emit,
59+
) async {
60+
emit(AuthenticationLoading());
61+
try {
62+
await _authenticationRepository.signInWithEmailAndPassword(
63+
email: event.email,
64+
password: event.password,
65+
);
66+
} on EmailSignInException catch (e) {
67+
emit(AuthenticationFailure(e.toString()));
68+
} catch (e) {
69+
emit(AuthenticationFailure(e.toString()));
70+
}
71+
}
72+
73+
/// Handles [AuthenticationGoogleSignInRequested] events.
74+
Future<void> _onAuthenticationGoogleSignInRequested(
75+
AuthenticationGoogleSignInRequested event,
76+
Emitter<AuthenticationState> emit,
77+
) async {
78+
emit(AuthenticationLoading());
79+
try {
80+
await _authenticationRepository.signInWithGoogle();
81+
} on GoogleSignInException catch (e) {
82+
emit(AuthenticationFailure(e.toString()));
83+
} catch (e) {
84+
emit(AuthenticationFailure(e.toString()));
85+
}
86+
}
87+
88+
/// Handles [AuthenticationAnonymousSignInRequested] events.
89+
Future<void> _onAuthenticationAnonymousSignInRequested(
90+
AuthenticationAnonymousSignInRequested event,
91+
Emitter<AuthenticationState> emit,
92+
) async {
93+
emit(AuthenticationLoading());
94+
try {
95+
await _authenticationRepository.signInAnonymously();
96+
} on AnonymousLoginException catch (e) {
97+
emit(AuthenticationFailure(e.toString()));
98+
} catch (e) {
99+
emit(AuthenticationFailure(e.toString()));
100+
}
101+
}
102+
103+
/// Handles [AuthenticationSignOutRequested] events.
104+
Future<void> _onAuthenticationSignOutRequested(
105+
AuthenticationSignOutRequested event,
106+
Emitter<AuthenticationState> emit,
107+
) async {
108+
emit(AuthenticationLoading());
109+
try {
110+
await _authenticationRepository.signOut();
111+
} on LogoutException catch (e) {
112+
emit(AuthenticationFailure(e.toString()));
113+
} catch (e) {
114+
emit(AuthenticationFailure(e.toString()));
115+
}
116+
}
117+
118+
Future<void> _onAuthenticationDeleteAccountRequested(
119+
AuthenticationDeleteAccountRequested event,
120+
Emitter<AuthenticationState> emit,
121+
) async {
122+
emit(AuthenticationLoading());
123+
try {
124+
await _authenticationRepository.deleteAccount();
125+
} on DeleteAccountException catch (e) {
126+
emit(AuthenticationFailure(e.toString()));
127+
} catch (e) {
128+
emit(AuthenticationFailure(e.toString()));
129+
}
130+
}
131+
132+
@override
133+
Future<void> close() {
134+
_userSubscription.cancel();
135+
return super.close();
136+
}
137+
}

0 commit comments

Comments
 (0)