Skip to content

Commit bb72c59

Browse files
hoanghaozcoderabbitai[bot]CodeRabbit
authored
Implement complete authentication UI and enhance task management features (#29)
* feat(core): add auth layout template, custom textfield and colors * feat(auth): implement viewmodels for auth flow (MVVM) * feat(auth): build complete auth UI screens (Login, Register, OTP, Passwords) * chore(main): set LoginView as initial route * refactor(auth) : delete .gitkeep * chore: update dependencies and pubspec.lock * refactor(auth): optimize registration logic, timezone handling, and form validation * feat(auth): update UI for login, registration, and forgot password screens * feat(tasks): update task management UI and statistics screen * chore: update main entry point and fix widget tests * chore: ignore devtools_options.yaml * chore: ignore devtools_options.yaml * style(login) : rewrite title for login view * feat(auth): configure android deep link for supabase oauth * refactor(ui): add social login callbacks to auth layout template * feat(auth): update oauth methods with redirect url and signout * feat(auth): implement AuthGate using StreamBuilder for session tracking * feat(viewmodel): add oauth logic and improve provider lifecycle * refactor(ui): migrate LoginView to Provider pattern * chore(main): set AuthGate as initial route and setup provider * feat: implement full Focus feature set - Added Pomodoro timer with Start/Reset/Skip logic. - Integrated local Quick Notes with Pin/Delete functionality. - Supported image attachments in notes using image_picker. - Added Focus settings: time duration, vibration, and ringtones. * fix (auth) : dispose TextEditingControllers to prevent memory leaks * refactor (alarm ) : create off alarm button when time out * fix: apply CodeRabbit auto-fixes Fixed 3 file(s) based on 4 unresolved review comments. Co-authored-by: CodeRabbit <[email protected]> * fix(timer): prevent division by zero in progress calculation and sanitize negative settings input * fix(timer): prevent division by zero in progress calculation and sanitize negative settings input * fix(auth): unblock new-user login and add settings logout --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: CodeRabbit <[email protected]>
1 parent 6a20633 commit bb72c59

7 files changed

Lines changed: 88 additions & 16 deletions

File tree

src/lib/features/auth/presentation/view/login_view.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class _LoginViewState extends State<LoginView> {
4444

4545
if (errorMessage == null) {
4646
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Đăng nhập thành công!'), backgroundColor: AppColors.success));
47+
// If LoginView was pushed on top of AuthGate, pop back to root so MainScreen is visible.
48+
Navigator.of(context).popUntil((route) => route.isFirst);
4749
} else {
4850
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(errorMessage), backgroundColor: AppColors.error));
4951
}

src/lib/features/auth/presentation/view/register_view.dart

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'package:flutter/material.dart';
2-
import 'package:task_management_app/features/auth/presentation/view/login_view.dart';
32
import '../../../../core/theme/app_colors.dart';
43
import '../../../../core/theme/auth_layout_template.dart';
54
import '../../../../core/theme/custom_text_field.dart';
@@ -31,10 +30,8 @@ class _RegisterViewState extends State<RegisterView> {
3130

3231
if (errorMessage == null) {
3332
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Đăng ký thành công!'), backgroundColor: AppColors.success));
34-
Navigator.pushReplacement(
35-
context,
36-
MaterialPageRoute(builder: (context) => const LoginView()),
37-
);
33+
// Return to the existing LoginView to avoid stacking duplicate login routes.
34+
Navigator.pop(context);
3835
} else {
3936
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(errorMessage), backgroundColor: AppColors.error));
4037
}

src/lib/features/auth/presentation/viewmodels/auth_viewmodels.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class BaseViewModel extends ChangeNotifier {
4444
'already registered': 'Email này đã được đăng ký!',
4545
'already exists': 'Email này đã được đăng ký!',
4646
'invalid login credentials': 'Email hoặc mật khẩu không chính xác!',
47+
'email not confirmed': 'Email chưa được xác nhận. Vui lòng kiểm tra hộp thư!',
4748
'rate limit': 'Bạn thao tác quá nhanh, vui lòng thử lại sau!',
4849
'over_email_send_rate_limit': 'Bạn thao tác quá nhanh, vui lòng thử lại sau!',
4950
'token has expired or is invalid': 'Mã OTP không hợp lệ hoặc đã hết hạn!',

src/lib/features/auth/services/auth_helper.dart

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,21 @@ class AuthHelper {
1515
password: password,
1616
);
1717

18-
if (response.user != null) {
19-
final userId = response.user!.id;
20-
18+
final user = response.user;
19+
if (user != null) {
20+
final userId = user.id;
21+
final userMetadata = user.userMetadata ?? {};
22+
final String? username = userMetadata['username']?.toString();
2123
final timezoneObj = await FlutterTimezone.getLocalTimezone();
2224
final String currentTimezone = timezoneObj.toString();
2325

2426
final profileData = await supabase
2527
.from('profile')
26-
.update({'timezone': currentTimezone})
27-
.eq('id', userId)
28+
.upsert({
29+
'id': userId,
30+
if (username != null && username.isNotEmpty) 'username': username,
31+
'timezone': currentTimezone,
32+
})
2833
.select()
2934
.single();
3035

@@ -77,14 +82,26 @@ class AuthHelper {
7782
}
7883
);
7984

80-
if (response.user != null) {
81-
final userId = response.user!.id;
85+
final user = response.user;
86+
if (user != null) {
87+
if (response.session == null) {
88+
// Email confirmation may be required; skip profile write until login.
89+
return UserModel(
90+
id: user.id,
91+
email: email,
92+
username: username,
93+
timezone: currentTimezone,
94+
);
95+
}
8296

83-
// Step 2: Insert directly into the 'profile' table
8497
final profileData = await supabase
8598
.from('profile')
99+
.upsert({
100+
'id': user.id,
101+
'username': username,
102+
'timezone': currentTimezone,
103+
})
86104
.select()
87-
.eq('id', userId)
88105
.single();
89106

90107
return UserModel.fromJson(profileData, email);

src/lib/features/main/view/screens/main_screen.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:flutter/material.dart';
22
import 'package:task_management_app/features/statistics/viewmodel/statistics_viewmodel.dart';
33
import 'package:task_management_app/features/tasks/view/screens/home_screen.dart';
4+
import 'settings_screen.dart';
45
import '../../../../core/theme/app_colors.dart';
56
import '../../../note/view/focus_screen.dart';
67
import '../../../note/viewmodel/focus_viewmodel.dart';
@@ -29,7 +30,7 @@ class _MainScreenState extends State<MainScreen> {
2930
create: (_) => StatisticsViewmodel(),
3031
child: const StatisticsScreen(),
3132
),
32-
const Center(child: Text('Màn hình Cài đặt')),
33+
const SettingsScreen(),
3334
];
3435

3536
@override
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import 'package:flutter/material.dart';
2+
import '../../../../core/theme/app_colors.dart';
3+
import '../../../auth/services/auth_helper.dart';
4+
5+
class SettingsScreen extends StatelessWidget {
6+
const SettingsScreen({super.key});
7+
8+
@override
9+
Widget build(BuildContext context) {
10+
return Scaffold(
11+
appBar: AppBar(
12+
title: const Text('Cai dat'),
13+
),
14+
body: Padding(
15+
padding: const EdgeInsets.all(20),
16+
child: Column(
17+
crossAxisAlignment: CrossAxisAlignment.start,
18+
children: [
19+
Text(
20+
'Tai khoan',
21+
style: Theme.of(context).textTheme.titleMedium,
22+
),
23+
const SizedBox(height: 12),
24+
SizedBox(
25+
width: double.infinity,
26+
child: ElevatedButton.icon(
27+
style: ElevatedButton.styleFrom(
28+
backgroundColor: AppColors.error,
29+
foregroundColor: AppColors.white,
30+
padding: const EdgeInsets.symmetric(vertical: 14),
31+
),
32+
onPressed: () async {
33+
try {
34+
await signOut();
35+
} catch (error) {
36+
if (!context.mounted) {
37+
return;
38+
}
39+
ScaffoldMessenger.of(context).showSnackBar(
40+
SnackBar(content: Text('Dang xuat that bai: $error')),
41+
);
42+
}
43+
},
44+
icon: const Icon(Icons.logout_rounded),
45+
label: const Text('Dang xuat'),
46+
),
47+
),
48+
],
49+
),
50+
),
51+
);
52+
}
53+
}
54+

src/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ packages:
601601
source: hosted
602602
version: "0.28.0"
603603
shared_preferences:
604-
dependency: transitive
604+
dependency: "direct main"
605605
description:
606606
name: shared_preferences
607607
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"

0 commit comments

Comments
 (0)