Skip to content

Commit 14e37b6

Browse files
committed
feat(auth): update UI for login, registration, and forgot password screens
1 parent 7dd8680 commit 14e37b6

5 files changed

Lines changed: 211 additions & 340 deletions

File tree

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import 'package:flutter/material.dart';
2-
import '../../../../../../core/theme/app_colors.dart';
3-
import '../../../../../../core/theme/auth_layout_template.dart';
4-
import '../../../../../../core/theme/custom_text_field.dart';
5-
import '../auth/presentation/viewmodels/auth_viewmodels.dart'
6-
import '../../otp_verification_view.dart';
2+
import '../../../../core/theme/app_colors.dart';
3+
import '../../../../core/theme/auth_layout_template.dart';
4+
import '../../../../core/theme/custom_text_field.dart';
5+
import '../viewmodels/auth_viewmodels.dart';
6+
import 'otp_verification_view.dart';
77

88
class ForgotPasswordView extends StatefulWidget {
99
const ForgotPasswordView({super.key});
10-
1110
@override
1211
State<ForgotPasswordView> createState() => _ForgotPasswordViewState();
1312
}
@@ -26,37 +25,27 @@ class _ForgotPasswordViewState extends State<ForgotPasswordView> {
2625
useCard: false,
2726
isLoading: _vm.isLoading,
2827
customHeaderIcon: Container(
29-
width: 120,
30-
height: 120,
28+
width: 120, height: 120,
3129
decoration: const BoxDecoration(
3230
color: AppColors.white,
3331
shape: BoxShape.circle,
3432
boxShadow: [BoxShadow(color: Color(0xFFD1D9E6), blurRadius: 16)],
3533
),
36-
child: const Icon(
37-
Icons.lock_reset,
38-
size: 64,
39-
color: AppColors.primary,
40-
),
34+
child: const Icon(Icons.lock_reset, size: 64, color: AppColors.primary),
4135
),
4236
onSubmit: () async {
43-
FocusScope.of(context).unfocus();
44-
final success = await _vm.sendCode();
37+
FocusScope.of(context).unfocus(); // Đóng bàn phím
38+
39+
// Lấy câu chửi từ ViewModel (nếu có lỗi)
40+
final errorMessage = await _vm.sendCode();
4541
if (!context.mounted) return;
4642

47-
if (success) {
48-
// Jump to Step 2: OTP
49-
Navigator.push(
50-
context,
51-
MaterialPageRoute(builder: (_) => const OtpVerificationView()),
52-
);
43+
if (errorMessage == null) {
44+
// Thành công (null) -> Bay qua màn hình điền OTP 6 số
45+
Navigator.push(context, MaterialPageRoute(builder: (_) => const OtpVerificationView()));
5346
} else {
54-
ScaffoldMessenger.of(context).showSnackBar(
55-
const SnackBar(
56-
content: Text('Vui lòng nhập email hợp lệ!'),
57-
backgroundColor: AppColors.error,
58-
),
59-
);
47+
// Có lỗi (như nhập sai định dạng, spam nút) -> Vã cái lỗi màu đỏ ra
48+
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(errorMessage), backgroundColor: AppColors.error));
6049
}
6150
},
6251
formContent: CustomTextField(
@@ -65,22 +54,18 @@ class _ForgotPasswordViewState extends State<ForgotPasswordView> {
6554
icon: Icons.mail,
6655
controller: _vm.emailCtrl,
6756
),
68-
footerContent: const Row(
69-
mainAxisAlignment: MainAxisAlignment.center,
70-
children: [
71-
Icon(Icons.help, size: 16, color: AppColors.primary),
72-
SizedBox(width: 4),
73-
Text(
74-
'Cần hỗ trợ? Liên hệ CSKH',
75-
style: TextStyle(
76-
color: AppColors.textSecondary,
77-
fontSize: 12,
78-
fontWeight: FontWeight.bold,
79-
),
80-
),
81-
],
57+
footerContent: const Padding(
58+
padding: EdgeInsets.only(bottom: 24.0),
59+
child: Row(
60+
mainAxisAlignment: MainAxisAlignment.center,
61+
children: [
62+
Icon(Icons.help, size: 16, color: AppColors.primary),
63+
SizedBox(width: 4),
64+
Text('Cần hỗ trợ? Liên hệ CSKH', style: TextStyle(color: AppColors.textSecondary, fontSize: 14, fontWeight: FontWeight.bold)),
65+
],
66+
),
8267
),
8368
),
8469
);
8570
}
86-
}
71+
}
Lines changed: 42 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import 'package:flutter/material.dart';
2-
import '../../core/theme/app_colors.dart';
3-
import '../../core/theme/auth_layout_template.dart';
4-
import '../../core/theme/custom_text_field.dart';
5-
import '../auth/presentation/viewmodels/auth_viewmodels.dart';
2+
import '../../../../core/theme/app_colors.dart';
3+
import '../../../../core/theme/auth_layout_template.dart';
4+
import '../../../../core/theme/custom_text_field.dart';
5+
import '../viewmodels/auth_viewmodels.dart';
66
import 'register_view.dart';
7-
import 'presentation/view /forgot_password_view.dart';
7+
import 'forgot_password_view.dart';
88

99
class LoginView extends StatefulWidget {
1010
const LoginView({super.key});
11-
1211
@override
1312
State<LoginView> createState() => _LoginViewState();
1413
}
@@ -20,105 +19,58 @@ class _LoginViewState extends State<LoginView> {
2019
Widget build(BuildContext context) {
2120
return AnimatedBuilder(
2221
animation: _vm,
23-
builder: (context, _) =>
24-
AuthLayoutTemplate(
25-
title: 'Task Management',
26-
subtitle: 'Chào mừng trở lại!',
27-
submitText: 'Đăng nhập',
28-
isLoading: _vm.isLoading,
29-
showSocial: true,
30-
onSubmit: () async {
31-
FocusScope.of(context).unfocus(); // Hide keyboard
32-
final success = await _vm.login();
33-
if (!context.mounted) return;
22+
builder: (context, _) => AuthLayoutTemplate(
23+
title: 'To-Do List',
24+
subtitle: 'Chào mừng trở lại!',
25+
submitText: 'Đăng nhập',
26+
isLoading: _vm.isLoading,
27+
showSocial: true,
28+
onSubmit: () async {
29+
FocusScope.of(context).unfocus();
30+
final errorMessage = await _vm.login();
31+
if (!context.mounted) return;
3432

35-
if (success) {
36-
ScaffoldMessenger.of(context).showSnackBar(
37-
const SnackBar(
38-
content: Text('Đăng nhập thành công!'),
39-
backgroundColor: AppColors.success,
40-
),
41-
);
42-
// TODO: Navigator.pushReplacementNamed(context, '/home');
43-
} else {
44-
ScaffoldMessenger.of(context).showSnackBar(
45-
const SnackBar(
46-
content: Text('Vui lòng điền đầy đủ thông tin!'),
47-
backgroundColor: AppColors.error,
48-
),
49-
);
50-
}
51-
},
52-
formContent: Column(
53-
crossAxisAlignment: CrossAxisAlignment.end,
54-
children: [
55-
CustomTextField(
56-
label: 'Email',
57-
58-
icon: Icons.mail,
59-
controller: _vm.emailCtrl,
60-
),
61-
CustomTextField(
62-
label: 'Mật khẩu',
63-
hint: '••••••••',
64-
icon: Icons.lock,
65-
controller: _vm.passCtrl,
66-
isPassword: true,
67-
obscureText: _vm.obscurePass,
68-
onToggleVisibility: _vm.togglePass,
69-
),
70-
TextButton(
71-
onPressed: () =>
72-
Navigator.push(
73-
context,
74-
MaterialPageRoute(
75-
builder: (_) => const ForgotPasswordView()),
76-
),
77-
child: const Text(
78-
'Quên mật khẩu?',
79-
style: TextStyle(
80-
color: AppColors.primary,
81-
fontWeight: FontWeight.bold,
82-
),
83-
),
84-
),
85-
],
33+
if (errorMessage == null) {
34+
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Đăng nhập thành công!'), backgroundColor: AppColors.success));
35+
} else {
36+
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(errorMessage), backgroundColor: AppColors.error));
37+
}
38+
},
39+
formContent: Column(
40+
crossAxisAlignment: CrossAxisAlignment.end,
41+
children: [
42+
CustomTextField(label: 'Email', hint: '[email protected]', icon: Icons.mail, controller: _vm.emailCtrl),
43+
CustomTextField(
44+
label: 'Mật khẩu', hint: '••••••••', icon: Icons.lock, controller: _vm.passCtrl,
45+
isPassword: true, obscureText: _vm.obscurePass, onToggleVisibility: _vm.togglePass,
8646
),
87-
footerContent: _buildFooter(
88-
'Chưa có tài khoản? ', 'Đăng ký ngay', () {
89-
Navigator.push(
90-
context,
91-
MaterialPageRoute(builder: (_) => const RegisterView()),
92-
);
93-
}),
94-
),
47+
TextButton(
48+
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ForgotPasswordView())),
49+
child: const Text('Quên mật khẩu?', style: TextStyle(color: AppColors.primary, fontWeight: FontWeight.bold)),
50+
),
51+
],
52+
),
53+
footerContent: _buildFooter('Chưa có tài khoản? ', 'Đăng ký ngay', () {
54+
Navigator.push(context, MaterialPageRoute(builder: (_) => const RegisterView()));
55+
}),
56+
),
9557
);
9658
}
9759

9860
Widget _buildFooter(String text, String action, VoidCallback onTap) {
9961
return Padding(
100-
// Position this block 24dp above the bottom of the screen
10162
padding: const EdgeInsets.only(bottom: 24.0),
10263
child: Row(
10364
mainAxisAlignment: MainAxisAlignment.center,
10465
children: [
105-
Text(text, style: const TextStyle(color: Colors.grey, fontSize: 16)),
106-
66+
Text(text, style: const TextStyle(color: AppColors.textSecondary, fontSize: 16)),
10767
TextButton(
10868
onPressed: onTap,
109-
style: TextButton.styleFrom(
110-
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
111-
minimumSize: const Size(50, 48),
112-
),
113-
child: Text(
114-
action,
115-
style: const TextStyle(color: Color(0xFF5A8DF3),
116-
fontWeight: FontWeight.bold,
117-
fontSize: 16),
118-
),
69+
style: TextButton.styleFrom(minimumSize: const Size(50, 48)),
70+
child: Text(action, style: const TextStyle(color: AppColors.primary, fontWeight: FontWeight.bold, fontSize: 16)),
11971
),
12072
],
12173
),
12274
);
12375
}
124-
}
76+
}
Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import 'package:flutter/material.dart';
2-
import '../../core/theme/app_colors.dart';
3-
import '../../core/theme/auth_layout_template.dart';
4-
import '../../core/theme/custom_text_field.dart';
5-
import '../auth/presentation/viewmodels/auth_viewmodels.dart';
2+
3+
import '../../../../core/theme/app_colors.dart';
4+
import '../../../../core/theme/auth_layout_template.dart';
5+
import '../../../../core/theme/custom_text_field.dart';
6+
import '../viewmodels/auth_viewmodels.dart';
67

78
class NewPasswordView extends StatefulWidget {
89
const NewPasswordView({super.key});
9-
1010
@override
1111
State<NewPasswordView> createState() => _NewPasswordViewState();
1212
}
@@ -30,47 +30,32 @@ class _NewPasswordViewState extends State<NewPasswordView> {
3030
),
3131
onSubmit: () async {
3232
FocusScope.of(context).unfocus();
33-
final success = await _vm.updatePassword();
33+
34+
// Hứng lỗi (nếu có)
35+
final errorMessage = await _vm.updatePassword();
3436
if (!context.mounted) return;
3537

36-
if (success) {
37-
ScaffoldMessenger.of(context).showSnackBar(
38-
const SnackBar(
39-
content: Text('Đổi mật khẩu thành công!'),
40-
backgroundColor: AppColors.success,
41-
),
42-
);
43-
// Complete Flow: Pop everything and return to Login Screen
38+
if (errorMessage == null) {
39+
// Null -> Thành công -> Báo xanh mướt
40+
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Đổi mật khẩu thành công!'), backgroundColor: AppColors.success));
41+
// Cú đá chót: Xóa hết lịch sử trang, đá thẳng mặt về trang Login (isFirst)
4442
Navigator.popUntil(context, (route) => route.isFirst);
4543
} else {
46-
ScaffoldMessenger.of(context).showSnackBar(
47-
const SnackBar(
48-
content: Text('Mật khẩu không khớp!'),
49-
backgroundColor: AppColors.error,
50-
),
51-
);
44+
// Nếu lỗi do User nhập lệch pass -> Chửi
45+
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(errorMessage), backgroundColor: AppColors.error));
5246
}
5347
},
5448
formContent: Column(
5549
children: [
5650
CustomTextField(
57-
label: 'Mật khẩu mới',
58-
hint: '••••••••',
59-
icon: Icons.lock,
60-
controller: _vm.passCtrl,
61-
isPassword: true,
62-
obscureText: _vm.obscurePass,
63-
onToggleVisibility: _vm.togglePass,
51+
label: 'Mật khẩu mới', hint: '••••••••', icon: Icons.lock, controller: _vm.passCtrl,
52+
isPassword: true, obscureText: _vm.obscurePass, onToggleVisibility: _vm.togglePass,
6453
),
6554
CustomTextField(
66-
label: 'Xác nhận mật khẩu mới',
67-
hint: '••••••••',
68-
icon: Icons.lock,
69-
controller: _vm.confirmPassCtrl,
70-
isPassword: true,
71-
obscureText: _vm.obscurePass,
72-
onToggleVisibility: _vm.togglePass,
55+
label: 'Xác nhận mật khẩu mới', hint: '••••••••', icon: Icons.lock, controller: _vm.confirmPassCtrl,
56+
isPassword: true, obscureText: _vm.obscurePass, onToggleVisibility: _vm.togglePass,
7357
),
58+
// Cục Info hướng dẫn
7459
Container(
7560
padding: const EdgeInsets.all(12),
7661
decoration: BoxDecoration(
@@ -82,10 +67,7 @@ class _NewPasswordViewState extends State<NewPasswordView> {
8267
Icon(Icons.info, color: AppColors.primary, size: 16),
8368
SizedBox(width: 8),
8469
Expanded(
85-
child: Text(
86-
'Mật khẩu tối thiểu 8 ký tự để đảm bảo an toàn.',
87-
style: TextStyle(fontSize: 12, color: AppColors.primary),
88-
),
70+
child: Text('Mật khẩu tối thiểu 6 ký tự để đảm bảo an toàn.', style: TextStyle(fontSize: 12, color: AppColors.primary)),
8971
),
9072
],
9173
),
@@ -95,4 +77,4 @@ class _NewPasswordViewState extends State<NewPasswordView> {
9577
),
9678
);
9779
}
98-
}
80+
}

0 commit comments

Comments
 (0)