Skip to content

Commit 0438027

Browse files
committed
docs: Add docs for LoginScreen and LoginBloc.
1 parent 390ebb6 commit 0438027

18 files changed

Lines changed: 183 additions & 82 deletions

lib/data/services/remote/api_service.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class ApiService {
123123
headers.addAll({'authorization': utils.authToken});
124124
}
125125

126+
/// Strict validation. Used after authentication.
126127
static bool _validateStrict(int? status) {
127128
if (status == 401) {
128129
utils.clearAuthToken();
@@ -131,6 +132,7 @@ class ApiService {
131132
return status! < 500;
132133
}
133134

135+
/// Looser validation. Used before authentication.
134136
static bool _validateLoose(int? status) {
135137
return status! < 500;
136138
}

lib/domain/models/status.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@ import 'package:freezed_annotation/freezed_annotation.dart';
33
part 'status.freezed.dart';
44

55
@freezed
6+
7+
/// State of a screen.
68
class Status with _$Status {
9+
/// State of a screen.
710
const factory Status([String? message]) = Idle;
11+
12+
/// State of a screen.
813
const factory Status.loading() = Loading;
9-
const factory Status.error(String message) = Error;
14+
15+
/// State of a screen.
16+
const factory Status.error(String errorMessage) = Error;
1017
}

lib/domain/repositories/user_repository.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class UserRepository {
5353
return null;
5454
}
5555

56+
/// Logs in the user.
5657
static Future<String?> login({
5758
required String username,
5859
required String password,
@@ -76,7 +77,6 @@ class UserRepository {
7677
shouldPersist: rememberMe,
7778
);
7879
StorageService.user = await fetchUserDetails();
79-
8080
return 'Success';
8181
case 401:
8282
return 'Unauthorized';
@@ -110,6 +110,7 @@ class UserRepository {
110110
return response['status_code'] == 200;
111111
}
112112

113+
/// Requests a password reset.
113114
static Future<bool?> resetPassword(String email) async {
114115
const endpoint = 'user/password-reset-email';
115116

lib/presentation/SAMPLE

Whitespace-only changes.

lib/presentation/components/SAMPLE

Whitespace-only changes.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import 'package:flutter/material.dart';
2+
3+
import '../../../data/constants/colors.dart';
4+
import '../../../data/constants/styles.dart';
5+
6+
/// App-wide common customization of [TextFormField].
7+
class TextInput extends StatelessWidget {
8+
/// App-wide common customization of [TextFormField].
9+
const TextInput({
10+
required this.hint,
11+
this.action = TextInputAction.next,
12+
this.controller,
13+
this.initialValue = '',
14+
this.keyboard,
15+
this.maxLines = 1,
16+
this.obscureText = false,
17+
this.onChanged,
18+
this.prefix,
19+
Key? key,
20+
}) : assert(
21+
controller != null || onChanged != null,
22+
'Both controller and onChanged cannot be null',
23+
),
24+
super(key: key);
25+
26+
final bool obscureText;
27+
final int maxLines;
28+
final String initialValue;
29+
final String hint;
30+
final Function(String)? onChanged;
31+
final TextInputAction action;
32+
final TextInputType? keyboard;
33+
final TextEditingController? controller;
34+
final Widget? prefix;
35+
36+
@override
37+
Widget build(BuildContext context) {
38+
return TextFormField(
39+
controller: controller,
40+
decoration: InputDecoration(
41+
hintText: hint,
42+
hintStyle: AppStyles.h6,
43+
prefixIcon: prefix,
44+
border: const OutlineInputBorder(
45+
borderSide: BorderSide(color: AppColors.primary),
46+
),
47+
focusedBorder: const OutlineInputBorder(
48+
borderSide: BorderSide(
49+
color: AppColors.primary,
50+
width: 1.5,
51+
),
52+
),
53+
),
54+
initialValue: controller == null ? initialValue : null,
55+
keyboardType: keyboard,
56+
maxLines: maxLines,
57+
onChanged: onChanged,
58+
style: AppStyles.h6.copyWith(color: AppColors.grey3),
59+
textInputAction: action,
60+
);
61+
}
62+
}

lib/presentation/login/bloc/login_bloc.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ part 'login_bloc.freezed.dart';
1212
part 'login_event.dart';
1313
part 'login_state.dart';
1414

15+
/// The Bloc for the login screen.
1516
class LoginBloc extends Bloc<LoginEvent, LoginState> {
17+
/// The Bloc for the login screen.
1618
LoginBloc() : super(const LoginState()) {
1719
on<ToggleDialog>(_toggleDialog);
1820
on<PasswordInput>(_updatePasswordInput);
@@ -25,7 +27,11 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
2527
void _submitForm(Submit event, Emitter<LoginState> emit) async {
2628
if (!state.isFormFilled()) return;
2729

28-
emit(state.copyWith(status: const Status.loading()));
30+
emit(state.copyWith(
31+
isPasswordFocused: false,
32+
isUsernameFocused: false,
33+
status: const Status.loading(),
34+
));
2935

3036
final result = await UserRepository.login(
3137
username: state.username,

lib/presentation/login/bloc/login_event.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
part of 'login_bloc.dart';
22

3+
/// Base event class for [LoginBloc].
34
abstract class LoginEvent extends Equatable {
5+
/// Base event class for [LoginBloc].
46
const LoginEvent();
57

68
@override
79
List<Object?> get props => [];
810
}
911

12+
/// Added when the text in the password field is changed.
1013
class PasswordInput extends LoginEvent {
14+
/// Added when the text in the password field is changed.
1115
const PasswordInput(this.value);
1216

1317
final String value;
@@ -16,28 +20,39 @@ class PasswordInput extends LoginEvent {
1620
List<Object> get props => [value];
1721
}
1822

23+
/// Added when the login button is pressed.
1924
class Submit extends LoginEvent {
25+
/// Added when the login button is pressed.
2026
const Submit();
2127
}
2228

29+
/// Added when the "Forgot Password" button is pressed or the dialog is closed.
2330
class ToggleDialog extends LoginEvent {
31+
/// Added when the "Forgot Password" button is pressed or the dialog is closed.
2432
const ToggleDialog({this.email});
2533

34+
/// Value entered in the text field in the dialog.
2635
final String? email;
2736

2837
@override
2938
List<Object?> get props => [email];
3039
}
3140

41+
/// Added when the password field's eye icon is pressed.
3242
class ToggleObscure extends LoginEvent {
43+
/// Added when the password field's eye icon is pressed.
3344
const ToggleObscure();
3445
}
3546

47+
/// Added when the "Keep me signed in" checkbox is toggled.
3648
class ToggleRememberMe extends LoginEvent {
49+
/// Added when the "Keep me signed in" checkbox is toggled.
3750
const ToggleRememberMe();
3851
}
3952

53+
/// Added when the text in the username field is changed.
4054
class UsernameInput extends LoginEvent {
55+
/// Added when the text in the username field is changed.
4156
const UsernameInput(this.value);
4257

4358
final String value;
Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,41 @@
11
part of 'login_bloc.dart';
22

33
@freezed
4+
5+
/// State class for [LoginBloc].
46
class LoginState with _$LoginState {
7+
/// State class for [LoginBloc].
58
const factory LoginState({
6-
@Default(true) bool rememberMe,
9+
/// Whether the username field is in focus.
710
@Default(false) bool isUsernameFocused,
11+
12+
/// Whether the password field is in focus.
813
@Default(false) bool isPasswordFocused,
14+
15+
/// Whether the password field should be obscured.
916
@Default(true) bool obscurePassword,
17+
18+
/// State of the "Keep me signed in" checkbox.
19+
@Default(true) bool rememberMe,
20+
21+
/// Whether the "Forgot Password" dialog should be displayed.
1022
@Default(false) bool showDialog,
23+
24+
/// State of the screen.
1125
@Default(Status()) Status status,
26+
27+
/// Value in the username field.
1228
@Default('') String username,
29+
30+
/// Value in the password field.
1331
@Default('') String password,
1432
}) = _LoginState;
1533

1634
const LoginState._();
1735

36+
/// The form is considered filled when both fields are non-empty.
1837
bool isFormFilled() => username.isNotEmpty && password.isNotEmpty;
1938

39+
/// The login button is active when the form is filled and the screen is not loading.
2040
bool isLoginButtonActive() => isFormFilled() && status is! Loading;
2141
}

lib/presentation/login/login_screen.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import '../../data/constants/styles.dart';
66
import 'bloc/login_bloc.dart';
77
import 'widgets/login_widgets.dart';
88

9+
/// The login screen widget.
910
class LoginScreen extends StatelessWidget {
11+
/// The login screen widget.
1012
const LoginScreen({Key? key}) : super(key: key);
1113

1214
@override

0 commit comments

Comments
 (0)