Skip to content

Commit 986a56d

Browse files
hoanghaozcoderabbitai[bot]CodeRabbit
authored
Enhance authentication UI and implement Focus feature set (#35)
* 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 * refactor(LoginScreen) : compact all items to fit in screen to help users interface easily --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: CodeRabbit <[email protected]>
1 parent 7a77888 commit 986a56d

3 files changed

Lines changed: 71 additions & 60 deletions

File tree

src/lib/core/theme/auth_layout_template.dart

Lines changed: 58 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class AuthLayoutTemplate extends StatelessWidget {
1515
final Widget? footerContent;
1616
final VoidCallback? onGoogleTap; // Login with Google
1717
final VoidCallback? onFacebookTap; // Login with Facebook
18+
final bool compactMode;
1819

1920
const AuthLayoutTemplate({
2021
super.key,
@@ -30,6 +31,7 @@ class AuthLayoutTemplate extends StatelessWidget {
3031
this.footerContent,
3132
this.onGoogleTap,
3233
this.onFacebookTap,
34+
this.compactMode = false,
3335
});
3436

3537
@override
@@ -61,48 +63,54 @@ class AuthLayoutTemplate extends StatelessWidget {
6163
)
6264
: null,
6365
),
64-
body: Container(
65-
decoration: BoxDecoration(
66-
gradient: isDark
67-
? const LinearGradient(
68-
begin: Alignment.topCenter,
69-
end: Alignment.bottomCenter,
70-
colors: [Color(0xFF08142D), Color(0xFF0B1A38), Color(0xFF0A1834)],
71-
)
72-
: null,
73-
),
74-
child: SafeArea(
75-
child: Center(
76-
child: SingleChildScrollView(
77-
padding: const EdgeInsets.fromLTRB(24.0, 16.0, 24.0, 48.0),
78-
child: Column(
79-
mainAxisAlignment: MainAxisAlignment.center,
80-
children: [
81-
_buildHeader(context),
82-
const SizedBox(height: 32),
83-
useCard
84-
? _buildCardContainer(context)
85-
: _buildTransparentContainer(context),
86-
const SizedBox(height: 32),
87-
if (footerContent != null) footerContent!,
88-
],
66+
body: LayoutBuilder(
67+
builder: (context, constraints) {
68+
final isCompact = compactMode || constraints.maxHeight <= 780;
69+
70+
return Container(
71+
decoration: BoxDecoration(
72+
gradient: isDark
73+
? const LinearGradient(
74+
begin: Alignment.topCenter,
75+
end: Alignment.bottomCenter,
76+
colors: [Color(0xFF08142D), Color(0xFF0B1A38), Color(0xFF0A1834)],
77+
)
78+
: null,
79+
),
80+
child: SafeArea(
81+
child: Center(
82+
child: SingleChildScrollView(
83+
padding: EdgeInsets.fromLTRB(20.0, isCompact ? 8.0 : 16.0, 20.0, isCompact ? 16.0 : 36.0),
84+
child: Column(
85+
mainAxisAlignment: MainAxisAlignment.center,
86+
children: [
87+
_buildHeader(context, isCompact),
88+
SizedBox(height: isCompact ? 16 : 28),
89+
useCard
90+
? _buildCardContainer(context, isCompact)
91+
: _buildTransparentContainer(context, isCompact),
92+
SizedBox(height: isCompact ? 12 : 24),
93+
if (footerContent != null) footerContent!,
94+
],
95+
),
96+
),
8997
),
9098
),
91-
),
92-
),
99+
);
100+
},
93101
),
94102
);
95103
}
96104

97-
Widget _buildHeader(BuildContext context) {
105+
Widget _buildHeader(BuildContext context, bool isCompact) {
98106
final isDark = Theme.of(context).brightness == Brightness.dark;
99107

100108
return Column(
101109
children: [
102110
customHeaderIcon ??
103111
Container(
104-
width: 80,
105-
height: 80,
112+
width: isCompact ? 64 : 80,
113+
height: isCompact ? 64 : 80,
106114
decoration: BoxDecoration(
107115
color: isDark ? const Color(0xFF1E2B47) : Theme.of(context).colorScheme.surface,
108116
borderRadius: BorderRadius.circular(24),
@@ -117,26 +125,26 @@ class AuthLayoutTemplate extends StatelessWidget {
117125
child: Center(
118126
child: Icon(
119127
Icons.task_alt,
120-
size: 48,
128+
size: isCompact ? 36 : 48,
121129
color: Theme.of(context).colorScheme.primary,
122130
),
123131
),
124132
),
125-
const SizedBox(height: 24),
133+
SizedBox(height: isCompact ? 12 : 24),
126134
Text(
127135
title,
128136
style: TextStyle(
129-
fontSize: 28,
137+
fontSize: isCompact ? 24 : 28,
130138
fontWeight: FontWeight.w800,
131139
color: Theme.of(context).colorScheme.onSurface,
132140
letterSpacing: -0.5,
133141
),
134142
),
135-
const SizedBox(height: 8),
143+
SizedBox(height: isCompact ? 4 : 8),
136144
Text(
137145
subtitle,
138146
style: TextStyle(
139-
fontSize: 14,
147+
fontSize: isCompact ? 13 : 14,
140148
fontWeight: FontWeight.w500,
141149
color: Theme.of(context).colorScheme.onSurfaceVariant,
142150
),
@@ -146,11 +154,11 @@ class AuthLayoutTemplate extends StatelessWidget {
146154
);
147155
}
148156

149-
Widget _buildCardContainer(BuildContext context) {
157+
Widget _buildCardContainer(BuildContext context, bool isCompact) {
150158
final isDark = Theme.of(context).brightness == Brightness.dark;
151159

152160
return Container(
153-
padding: const EdgeInsets.all(32),
161+
padding: EdgeInsets.all(isCompact ? 20 : 32),
154162
decoration: BoxDecoration(
155163
color: isDark ? const Color(0xFF1A2945) : Theme.of(context).colorScheme.surface,
156164
borderRadius: BorderRadius.circular(32),
@@ -163,29 +171,29 @@ class AuthLayoutTemplate extends StatelessWidget {
163171
),
164172
],
165173
),
166-
child: _buildFormElements(context),
174+
child: _buildFormElements(context, isCompact),
167175
);
168176
}
169177

170-
Widget _buildTransparentContainer(BuildContext context) =>
171-
_buildFormElements(context);
178+
Widget _buildTransparentContainer(BuildContext context, bool isCompact) =>
179+
_buildFormElements(context, isCompact);
172180

173-
Widget _buildFormElements(BuildContext context) {
181+
Widget _buildFormElements(BuildContext context, bool isCompact) {
174182
final isDark = Theme.of(context).brightness == Brightness.dark;
175183

176184
return Column(
177185
crossAxisAlignment: CrossAxisAlignment.stretch,
178186
children: [
179187
formContent,
180-
const SizedBox(height: 16),
188+
SizedBox(height: isCompact ? 12 : 16),
181189
ElevatedButton(
182190
// Disable button if loading
183191
onPressed: isLoading ? null : onSubmit,
184192
style: ElevatedButton.styleFrom(
185193
backgroundColor: Theme.of(context).colorScheme.primary,
186194
disabledBackgroundColor:
187195
Theme.of(context).colorScheme.primary.withValues(alpha: 0.6),
188-
padding: const EdgeInsets.symmetric(vertical: 20),
196+
padding: EdgeInsets.symmetric(vertical: isCompact ? 14 : 20),
189197
shape: RoundedRectangleBorder(
190198
borderRadius: BorderRadius.circular(16),
191199
),
@@ -209,7 +217,7 @@ class AuthLayoutTemplate extends StatelessWidget {
209217
Text(
210218
submitText,
211219
style: TextStyle(
212-
fontSize: 16,
220+
fontSize: isCompact ? 15 : 16,
213221
fontWeight: FontWeight.bold,
214222
color: Theme.of(context).colorScheme.surface,
215223
),
@@ -224,14 +232,14 @@ class AuthLayoutTemplate extends StatelessWidget {
224232
),
225233
),
226234
if (showSocial) ...[
227-
const SizedBox(height: 32),
235+
SizedBox(height: isCompact ? 16 : 32),
228236
Row(
229237
children: [
230238
Expanded(
231239
child: Divider(color: Theme.of(context).colorScheme.outline),
232240
),
233241
Padding(
234-
padding: EdgeInsets.symmetric(horizontal: 16),
242+
padding: EdgeInsets.symmetric(horizontal: isCompact ? 12 : 16),
235243
child: Text(
236244
'OR',
237245
style: TextStyle(
@@ -246,7 +254,7 @@ class AuthLayoutTemplate extends StatelessWidget {
246254
),
247255
],
248256
),
249-
const SizedBox(height: 24),
257+
SizedBox(height: isCompact ? 12 : 24),
250258
Row(
251259
children: [
252260
Expanded(
@@ -259,7 +267,7 @@ class AuthLayoutTemplate extends StatelessWidget {
259267
shape: RoundedRectangleBorder(
260268
borderRadius: BorderRadius.circular(16),
261269
),
262-
padding: const EdgeInsets.symmetric(vertical: 14),
270+
padding: EdgeInsets.symmetric(vertical: isCompact ? 10 : 14),
263271
),
264272
onPressed: onGoogleTap,
265273
icon: const Icon(
@@ -270,7 +278,7 @@ class AuthLayoutTemplate extends StatelessWidget {
270278
label: const Text('Google'),
271279
),
272280
),
273-
const SizedBox(width: 16),
281+
SizedBox(width: isCompact ? 10 : 16),
274282
Expanded(
275283
child: OutlinedButton.icon(
276284
style: OutlinedButton.styleFrom(
@@ -281,7 +289,7 @@ class AuthLayoutTemplate extends StatelessWidget {
281289
shape: RoundedRectangleBorder(
282290
borderRadius: BorderRadius.circular(16),
283291
),
284-
padding: const EdgeInsets.symmetric(vertical: 14),
292+
padding: EdgeInsets.symmetric(vertical: isCompact ? 10 : 14),
285293
),
286294
onPressed: onFacebookTap,
287295
icon: const Icon(Icons.facebook, color: Color(0xFF1877F2)),

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class _LoginViewState extends State<LoginView> {
2222
title: 'Task Management',
2323
subtitle: 'Chào mừng trở lại!',
2424
submitText: 'Đăng nhập',
25+
compactMode: true,
2526
isLoading: _vm.isLoading,
2627
showSocial: true,
2728
onGoogleTap: () async {
@@ -79,6 +80,7 @@ class _LoginViewState extends State<LoginView> {
7980
),
8081
TextButton(
8182
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ForgotPasswordView())),
83+
style: TextButton.styleFrom(minimumSize: const Size(50, 40), padding: const EdgeInsets.symmetric(horizontal: 4)),
8284
child: Text(
8385
'Quên mật khẩu?',
8486
style: TextStyle(
@@ -94,8 +96,8 @@ class _LoginViewState extends State<LoginView> {
9496
'Chưa có tài khoản? ',
9597
'Đăng ký ngay',
9698
() {
97-
Navigator.push(context, MaterialPageRoute(builder: (_) => const RegisterView()));
98-
},
99+
Navigator.push(context, MaterialPageRoute(builder: (_) => const RegisterView()));
100+
},
99101
),
100102
),
101103
);
@@ -108,26 +110,26 @@ class _LoginViewState extends State<LoginView> {
108110
VoidCallback onTap,
109111
) {
110112
return Padding(
111-
padding: const EdgeInsets.only(bottom: 24.0),
113+
padding: const EdgeInsets.only(bottom: 14.0),
112114
child: Row(
113115
mainAxisAlignment: MainAxisAlignment.center,
114116
children: [
115117
Text(
116118
text,
117119
style: TextStyle(
118120
color: Theme.of(context).colorScheme.onSurfaceVariant,
119-
fontSize: 16,
121+
fontSize: 15,
120122
),
121123
),
122124
TextButton(
123125
onPressed: onTap,
124-
style: TextButton.styleFrom(minimumSize: const Size(50, 48)),
126+
style: TextButton.styleFrom(minimumSize: const Size(50, 40)),
125127
child: Text(
126128
action,
127129
style: TextStyle(
128130
color: Theme.of(context).colorScheme.primary,
129131
fontWeight: FontWeight.bold,
130-
fontSize: 16,
132+
fontSize: 15,
131133
),
132134
),
133135
),

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class _RegisterViewState extends State<RegisterView> {
2121
title: 'Tạo tài khoản mới',
2222
subtitle: 'Bắt đầu quản lý công việc khoa học',
2323
submitText: 'Đăng ký',
24+
compactMode: true,
2425
isLoading: _vm.isLoading,
2526
showSocial: true,
2627
onSubmit: () async {
@@ -61,26 +62,26 @@ class _RegisterViewState extends State<RegisterView> {
6162
],
6263
),
6364
footerContent: Padding(
64-
padding: const EdgeInsets.only(bottom: 24.0),
65+
padding: const EdgeInsets.only(bottom: 14.0),
6566
child: Row(
6667
mainAxisAlignment: MainAxisAlignment.center,
6768
children: [
6869
Text(
6970
'Đã có tài khoản? ',
7071
style: TextStyle(
7172
color: Theme.of(context).colorScheme.onSurfaceVariant,
72-
fontSize: 16,
73+
fontSize: 15,
7374
),
7475
),
7576
TextButton(
7677
onPressed: () => Navigator.pop(context),
77-
style: TextButton.styleFrom(minimumSize: const Size(50, 48)),
78+
style: TextButton.styleFrom(minimumSize: const Size(50, 40)),
7879
child: Text(
7980
'Đăng nhập',
8081
style: TextStyle(
8182
color: Theme.of(context).colorScheme.primary,
8283
fontWeight: FontWeight.bold,
83-
fontSize: 16,
84+
fontSize: 15,
8485
),
8586
),
8687
),

0 commit comments

Comments
 (0)