Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
eb206d7
feat(core): add auth layout template, custom textfield and colors
hoanghaoz Mar 24, 2026
63f3e9e
feat(auth): implement viewmodels for auth flow (MVVM)
hoanghaoz Mar 24, 2026
18d3e82
feat(auth): build complete auth UI screens (Login, Register, OTP, Pas…
hoanghaoz Mar 24, 2026
5ebec14
chore(main): set LoginView as initial route
hoanghaoz Mar 24, 2026
911444c
refactor(auth) : delete .gitkeep
hoanghaoz Mar 24, 2026
abcc21f
Merge branch 'main' of https://github.com/tqha1011/TaskManagement int…
hoanghaoz Mar 25, 2026
f8ce39c
chore: update dependencies and pubspec.lock
hoanghaoz Mar 25, 2026
7dd8680
refactor(auth): optimize registration logic, timezone handling, and f…
hoanghaoz Mar 25, 2026
14e37b6
feat(auth): update UI for login, registration, and forgot password sc…
hoanghaoz Mar 25, 2026
b6dada7
feat(tasks): update task management UI and statistics screen
hoanghaoz Mar 25, 2026
a19a4cc
chore: update main entry point and fix widget tests
hoanghaoz Mar 25, 2026
ee1d86f
chore: ignore devtools_options.yaml
hoanghaoz Mar 25, 2026
d9f46b8
chore: ignore devtools_options.yaml
hoanghaoz Mar 25, 2026
7c734b2
style(login) : rewrite title for login view
hoanghaoz Mar 25, 2026
654f4f5
feat(auth): configure android deep link for supabase oauth
hoanghaoz Mar 26, 2026
1503c7f
refactor(ui): add social login callbacks to auth layout template
hoanghaoz Mar 26, 2026
d4fdfe6
feat(auth): update oauth methods with redirect url and signout
hoanghaoz Mar 26, 2026
a0be5d0
feat(auth): implement AuthGate using StreamBuilder for session tracking
hoanghaoz Mar 26, 2026
336610b
feat(viewmodel): add oauth logic and improve provider lifecycle
hoanghaoz Mar 26, 2026
2e3b212
refactor(ui): migrate LoginView to Provider pattern
hoanghaoz Mar 26, 2026
4e4e120
chore(main): set AuthGate as initial route and setup provider
hoanghaoz Mar 26, 2026
949d75a
fix conflict merge
hoanghaoz Apr 7, 2026
f96dcc6
feat: implement full Focus feature set
hoanghaoz Apr 8, 2026
3c68ce2
fix (auth) : dispose TextEditingControllers to prevent memory leaks
hoanghaoz Apr 8, 2026
0e4ad09
refactor (alarm ) : create off alarm button when time out
hoanghaoz Apr 8, 2026
a4a6a83
fix: apply CodeRabbit auto-fixes
coderabbitai[bot] Apr 8, 2026
3dcb2a3
fix(timer): prevent division by zero in progress calculation and sani…
hoanghaoz Apr 8, 2026
63d7e73
fix conflict
hoanghaoz Apr 8, 2026
2ec7d45
Merge branch 'feature/countdown_timer' of https://github.com/tqha1011…
hoanghaoz Apr 8, 2026
1b8984e
fix(timer): prevent division by zero in progress calculation and sani…
hoanghaoz Apr 8, 2026
f17dd00
fix(auth): unblock new-user login and add settings logout
hoanghaoz Apr 9, 2026
40e7c4a
fix conflict
hoanghaoz Apr 11, 2026
c1075b1
refactor(LoginScreen) : compact all items to fit in screen to help us…
hoanghaoz Apr 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 58 additions & 50 deletions src/lib/core/theme/auth_layout_template.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class AuthLayoutTemplate extends StatelessWidget {
final Widget? footerContent;
final VoidCallback? onGoogleTap; // Login with Google
final VoidCallback? onFacebookTap; // Login with Facebook
final bool compactMode;

const AuthLayoutTemplate({
super.key,
Expand All @@ -30,6 +31,7 @@ class AuthLayoutTemplate extends StatelessWidget {
this.footerContent,
this.onGoogleTap,
this.onFacebookTap,
this.compactMode = false,
});

@override
Expand Down Expand Up @@ -61,48 +63,54 @@ class AuthLayoutTemplate extends StatelessWidget {
)
: null,
),
body: Container(
decoration: BoxDecoration(
gradient: isDark
? const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF08142D), Color(0xFF0B1A38), Color(0xFF0A1834)],
)
: null,
),
child: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(24.0, 16.0, 24.0, 48.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildHeader(context),
const SizedBox(height: 32),
useCard
? _buildCardContainer(context)
: _buildTransparentContainer(context),
const SizedBox(height: 32),
if (footerContent != null) footerContent!,
],
body: LayoutBuilder(
builder: (context, constraints) {
final isCompact = compactMode || constraints.maxHeight <= 780;
Comment on lines +66 to +68

Comment on lines +66 to +69
return Container(
decoration: BoxDecoration(
gradient: isDark
? const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF08142D), Color(0xFF0B1A38), Color(0xFF0A1834)],
)
: null,
),
child: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: EdgeInsets.fromLTRB(20.0, isCompact ? 8.0 : 16.0, 20.0, isCompact ? 16.0 : 36.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildHeader(context, isCompact),
SizedBox(height: isCompact ? 16 : 28),
useCard
? _buildCardContainer(context, isCompact)
: _buildTransparentContainer(context, isCompact),
SizedBox(height: isCompact ? 12 : 24),
if (footerContent != null) footerContent!,
],
),
),
),
),
),
),
);
},
),
);
}

Widget _buildHeader(BuildContext context) {
Widget _buildHeader(BuildContext context, bool isCompact) {
final isDark = Theme.of(context).brightness == Brightness.dark;

return Column(
children: [
customHeaderIcon ??
Container(
width: 80,
height: 80,
width: isCompact ? 64 : 80,
height: isCompact ? 64 : 80,
decoration: BoxDecoration(
color: isDark ? const Color(0xFF1E2B47) : Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(24),
Expand All @@ -117,26 +125,26 @@ class AuthLayoutTemplate extends StatelessWidget {
child: Center(
child: Icon(
Icons.task_alt,
size: 48,
size: isCompact ? 36 : 48,
color: Theme.of(context).colorScheme.primary,
),
),
),
const SizedBox(height: 24),
SizedBox(height: isCompact ? 12 : 24),
Text(
title,
style: TextStyle(
fontSize: 28,
fontSize: isCompact ? 24 : 28,
fontWeight: FontWeight.w800,
color: Theme.of(context).colorScheme.onSurface,
letterSpacing: -0.5,
),
),
const SizedBox(height: 8),
SizedBox(height: isCompact ? 4 : 8),
Text(
subtitle,
style: TextStyle(
fontSize: 14,
fontSize: isCompact ? 13 : 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
Expand All @@ -146,11 +154,11 @@ class AuthLayoutTemplate extends StatelessWidget {
);
}

Widget _buildCardContainer(BuildContext context) {
Widget _buildCardContainer(BuildContext context, bool isCompact) {
final isDark = Theme.of(context).brightness == Brightness.dark;

return Container(
padding: const EdgeInsets.all(32),
padding: EdgeInsets.all(isCompact ? 20 : 32),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF1A2945) : Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(32),
Expand All @@ -163,29 +171,29 @@ class AuthLayoutTemplate extends StatelessWidget {
),
],
),
child: _buildFormElements(context),
child: _buildFormElements(context, isCompact),
);
}

Widget _buildTransparentContainer(BuildContext context) =>
_buildFormElements(context);
Widget _buildTransparentContainer(BuildContext context, bool isCompact) =>
_buildFormElements(context, isCompact);

Widget _buildFormElements(BuildContext context) {
Widget _buildFormElements(BuildContext context, bool isCompact) {
final isDark = Theme.of(context).brightness == Brightness.dark;

return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
formContent,
const SizedBox(height: 16),
SizedBox(height: isCompact ? 12 : 16),
ElevatedButton(
// Disable button if loading
onPressed: isLoading ? null : onSubmit,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
disabledBackgroundColor:
Theme.of(context).colorScheme.primary.withValues(alpha: 0.6),
padding: const EdgeInsets.symmetric(vertical: 20),
padding: EdgeInsets.symmetric(vertical: isCompact ? 14 : 20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
Expand All @@ -209,7 +217,7 @@ class AuthLayoutTemplate extends StatelessWidget {
Text(
submitText,
style: TextStyle(
fontSize: 16,
fontSize: isCompact ? 15 : 16,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.surface,
),
Expand All @@ -224,14 +232,14 @@ class AuthLayoutTemplate extends StatelessWidget {
),
),
if (showSocial) ...[
const SizedBox(height: 32),
SizedBox(height: isCompact ? 16 : 32),
Row(
children: [
Expanded(
child: Divider(color: Theme.of(context).colorScheme.outline),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
padding: EdgeInsets.symmetric(horizontal: isCompact ? 12 : 16),
child: Text(
'OR',
style: TextStyle(
Expand All @@ -246,7 +254,7 @@ class AuthLayoutTemplate extends StatelessWidget {
),
],
),
const SizedBox(height: 24),
SizedBox(height: isCompact ? 12 : 24),
Row(
children: [
Expanded(
Expand All @@ -259,7 +267,7 @@ class AuthLayoutTemplate extends StatelessWidget {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
padding: const EdgeInsets.symmetric(vertical: 14),
padding: EdgeInsets.symmetric(vertical: isCompact ? 10 : 14),
),
Comment on lines 267 to 271
onPressed: onGoogleTap,
icon: const Icon(
Expand All @@ -270,7 +278,7 @@ class AuthLayoutTemplate extends StatelessWidget {
label: const Text('Google'),
),
),
const SizedBox(width: 16),
SizedBox(width: isCompact ? 10 : 16),
Expanded(
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
Expand All @@ -281,7 +289,7 @@ class AuthLayoutTemplate extends StatelessWidget {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
padding: const EdgeInsets.symmetric(vertical: 14),
padding: EdgeInsets.symmetric(vertical: isCompact ? 10 : 14),
),
onPressed: onFacebookTap,
icon: const Icon(Icons.facebook, color: Color(0xFF1877F2)),
Expand Down
14 changes: 8 additions & 6 deletions src/lib/features/auth/presentation/view/login_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class _LoginViewState extends State<LoginView> {
title: 'Task Management',
subtitle: 'Chào mừng trở lại!',
submitText: 'Đăng nhập',
compactMode: true,
isLoading: _vm.isLoading,
showSocial: true,
onGoogleTap: () async {
Expand Down Expand Up @@ -79,6 +80,7 @@ class _LoginViewState extends State<LoginView> {
),
TextButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ForgotPasswordView())),
style: TextButton.styleFrom(minimumSize: const Size(50, 40), padding: const EdgeInsets.symmetric(horizontal: 4)),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep interactive touch targets at least 48dp height.

Reducing button minimum height to 40 may make tap targets too small on mobile. Prefer 48 for better accessibility and usability consistency.

Suggested adjustment
- style: TextButton.styleFrom(minimumSize: const Size(50, 40), padding: const EdgeInsets.symmetric(horizontal: 4)),
+ style: TextButton.styleFrom(minimumSize: const Size(50, 48), padding: const EdgeInsets.symmetric(horizontal: 4)),
...
- style: TextButton.styleFrom(minimumSize: const Size(50, 40)),
+ style: TextButton.styleFrom(minimumSize: const Size(50, 48)),

Also applies to: 126-126

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/auth/presentation/view/login_view.dart` at line 83, The
TextButton touch target is set to a 40dp minimum height which is below
accessibility guidelines; update the TextButton.styleFrom(minimumSize: const
Size(50, 40), ...) instances in login_view.dart (e.g., the style usage in the
widget building code around the login view) to use a minimum height of 48 (e.g.,
Size(50, 48)) and adjust the other occurrence mentioned (line ~126) similarly so
all interactive buttons meet the 48dp touch target recommendation.

child: Text(
'Quên mật khẩu?',
style: TextStyle(
Expand All @@ -94,8 +96,8 @@ class _LoginViewState extends State<LoginView> {
'Chưa có tài khoản? ',
'Đăng ký ngay',
() {
Navigator.push(context, MaterialPageRoute(builder: (_) => const RegisterView()));
},
Navigator.push(context, MaterialPageRoute(builder: (_) => const RegisterView()));
},
),
),
);
Expand All @@ -108,26 +110,26 @@ class _LoginViewState extends State<LoginView> {
VoidCallback onTap,
) {
return Padding(
padding: const EdgeInsets.only(bottom: 24.0),
padding: const EdgeInsets.only(bottom: 14.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
text,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 16,
fontSize: 15,
),
),
TextButton(
onPressed: onTap,
style: TextButton.styleFrom(minimumSize: const Size(50, 48)),
style: TextButton.styleFrom(minimumSize: const Size(50, 40)),
child: Text(
action,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
fontSize: 16,
fontSize: 15,
),
),
),
Expand Down
9 changes: 5 additions & 4 deletions src/lib/features/auth/presentation/view/register_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class _RegisterViewState extends State<RegisterView> {
title: 'Tạo tài khoản mới',
subtitle: 'Bắt đầu quản lý công việc khoa học',
submitText: 'Đăng ký',
compactMode: true,
isLoading: _vm.isLoading,
showSocial: true,
onSubmit: () async {
Expand Down Expand Up @@ -61,26 +62,26 @@ class _RegisterViewState extends State<RegisterView> {
],
),
footerContent: Padding(
padding: const EdgeInsets.only(bottom: 24.0),
padding: const EdgeInsets.only(bottom: 14.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Đã có tài khoản? ',
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 16,
fontSize: 15,
),
),
TextButton(
onPressed: () => Navigator.pop(context),
style: TextButton.styleFrom(minimumSize: const Size(50, 48)),
style: TextButton.styleFrom(minimumSize: const Size(50, 40)),
child: Text(
'Đăng nhập',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
fontSize: 16,
fontSize: 15,
),
),
),
Expand Down
Loading