From c1bd9453c9bd4a7eef41c17caf415ab2dcd2d391 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 10:45:52 +0100 Subject: [PATCH 1/5] feat(authentication): add responsive authentication layout - Create AuthLayout widget for centering and constraining authentication content - Enable scrolling for smaller screens - Improve visual experience on larger screens --- lib/authentication/widgets/auth_layout.dart | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 lib/authentication/widgets/auth_layout.dart diff --git a/lib/authentication/widgets/auth_layout.dart b/lib/authentication/widgets/auth_layout.dart new file mode 100644 index 00000000..4df62a1b --- /dev/null +++ b/lib/authentication/widgets/auth_layout.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/shared/constants/app_constants.dart'; +import 'package:ui_kit/ui_kit.dart'; + +/// {@template auth_layout} +/// A responsive layout for authentication pages. +/// +/// It centers the content and constrains its width for a better +/// visual experience on larger screens, while allowing it to be +/// scrollable on smaller screens. +/// {@endtemplate} +class AuthLayout extends StatelessWidget { + /// {@macro auth_layout} + const AuthLayout({required this.child, super.key}); + + /// The child widget to display within the layout. + final Widget child; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(AppSpacing.lg), + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: AppConstants.kMaxAuthWidth, + ), + child: SingleChildScrollView(child: child), + ), + ), + ); + } +} From d8de5be69aa380d600c349750106fc3b7e439db4 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 10:46:15 +0100 Subject: [PATCH 2/5] feat(constants): add max width for authentication pages - Introduce new constant kMaxAuthWidth with a value of 400 - This constant can be used to ensure consistent max width across all authentication pages in the application --- lib/shared/constants/app_constants.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/shared/constants/app_constants.dart b/lib/shared/constants/app_constants.dart index b975677c..9fa85f3a 100644 --- a/lib/shared/constants/app_constants.dart +++ b/lib/shared/constants/app_constants.dart @@ -3,6 +3,9 @@ abstract final class AppConstants { /// The maximum width the application should occupy on large screens. static const double kMaxAppWidth = 1000; + /// The maximum width for the authentication pages. + static const double kMaxAuthWidth = 400; + /// The duration for debouncing search input, to prevent excessive API calls. static const Duration kSearchDebounceDuration = Duration(milliseconds: 300); From 478b449201756f582e7233d74225b47845fc8379 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 10:46:51 +0100 Subject: [PATCH 3/5] refactor(authentication): implement AuthLayout widget - Replace custom layout with AuthLayout widget - Simplify UI structure and improve code readability - Enhance maintainability and consistency of authentication page --- .../view/authentication_page.dart | 107 ++++++++---------- 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/lib/authentication/view/authentication_page.dart b/lib/authentication/view/authentication_page.dart index 071d713e..e4c46ac5 100644 --- a/lib/authentication/view/authentication_page.dart +++ b/lib/authentication/view/authentication_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/authentication/bloc/authentication_bloc.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/authentication/widgets/auth_layout.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart'; import 'package:go_router/go_router.dart'; @@ -52,68 +53,56 @@ class AuthenticationPage extends StatelessWidget { state.status == AuthenticationStatus.loading || state.status == AuthenticationStatus.requestCodeLoading; - return Padding( - padding: const EdgeInsets.all(AppSpacing.paddingLarge), - child: Center( - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // --- Icon --- - Padding( - padding: const EdgeInsets.only(bottom: AppSpacing.xl), - child: Icon( - Icons.newspaper, - size: AppSpacing.xxl * 2, - color: colorScheme.primary, - ), - ), - // --- Headline and Subheadline --- - Text( - l10n.authenticationPageHeadline, - style: textTheme.headlineMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: AppSpacing.md), - Text( - l10n.authenticationPageSubheadline, - style: textTheme.bodyLarge?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: AppSpacing.xxl), + return AuthLayout( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // --- Icon --- + Padding( + padding: const EdgeInsets.only(bottom: AppSpacing.xl), + child: Icon( + Icons.newspaper, + size: AppSpacing.xxl * 2, + color: colorScheme.primary, + ), + ), + // --- Headline and Subheadline --- + Text( + l10n.authenticationPageHeadline, + style: textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: AppSpacing.md), + Text( + l10n.authenticationPageSubheadline, + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: AppSpacing.xxl), - // --- Email Sign-In Button --- - ElevatedButton.icon( - icon: const Icon(Icons.email_outlined), - onPressed: isLoading - ? null - : () { - context.goNamed(Routes.requestCodeName); - }, - label: Text(l10n.authenticationEmailSignInButton), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - vertical: AppSpacing.md, - ), - textStyle: textTheme.labelLarge, - ), + // --- Email Sign-In Button --- + ElevatedButton.icon( + icon: const Icon(Icons.email_outlined), + onPressed: isLoading + ? null + : () => context.goNamed(Routes.requestCodeName), + label: Text(l10n.authenticationEmailSignInButton), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + vertical: AppSpacing.md, ), - const SizedBox(height: AppSpacing.lg), - - // --- Loading Indicator --- - if (isLoading) - const Padding( - padding: EdgeInsets.only(top: AppSpacing.xl), - child: Center(child: CircularProgressIndicator()), - ), - ], + textStyle: textTheme.labelLarge, + ), ), - ), + const SizedBox(height: AppSpacing.lg), + if (isLoading) + const Center(child: CircularProgressIndicator()), + ], ), ); }, From 57194f23b33900b9ee1688d4ae95f899acd3ae60 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 10:47:02 +0100 Subject: [PATCH 4/5] refactor(authentication): migrate to AuthLayout and simplify UI code - Replace custom layout with AuthLayout widget - Remove nested padding and center widgets - Simplify column structure and reduce indentation - Adjust spacing and alignment using AuthLayout --- .../view/email_code_verification_page.dart | 110 +++++++++--------- 1 file changed, 52 insertions(+), 58 deletions(-) diff --git a/lib/authentication/view/email_code_verification_page.dart b/lib/authentication/view/email_code_verification_page.dart index c8348ac6..dc621e04 100644 --- a/lib/authentication/view/email_code_verification_page.dart +++ b/lib/authentication/view/email_code_verification_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app/bloc/app_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app/config/config.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/authentication/bloc/authentication_bloc.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/authentication/widgets/auth_layout.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; import 'package:pinput/pinput.dart'; import 'package:ui_kit/ui_kit.dart'; @@ -45,65 +46,58 @@ class EmailCodeVerificationPage extends StatelessWidget { builder: (context, state) { final isLoading = state.status == AuthenticationStatus.loading; - return Padding( - padding: const EdgeInsets.all(AppSpacing.paddingLarge), - child: Center( - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Icon( - Icons.mark_email_read_outlined, - size: AppSpacing.xxl * 2, - color: colorScheme.primary, - ), - const SizedBox(height: AppSpacing.xl), - Text( - l10n.emailCodeSentConfirmation(email), - style: textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: AppSpacing.lg), - Text( - l10n.emailCodeSentInstructions, - style: textTheme.bodyLarge?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - textAlign: TextAlign.center, - ), - // Display demo code if in demo environment - BlocSelector( - selector: (state) => state.environment, - builder: (context, environment) { - if (environment == AppEnvironment.demo) { - return Column( - children: [ - const SizedBox(height: AppSpacing.md), - Text( - l10n.demoCodeHint('123456'), - style: textTheme.bodyMedium?.copyWith( - color: colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - ], - ); - } - return const SizedBox.shrink(); - }, - ), - const SizedBox(height: AppSpacing.xl), - _EmailCodeVerificationForm( - email: email, - isLoading: isLoading, - ), - ], + return AuthLayout( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Icon( + Icons.mark_email_read_outlined, + size: AppSpacing.xxl * 2, + color: colorScheme.primary, ), - ), + const SizedBox(height: AppSpacing.xl), + Text( + l10n.emailCodeSentConfirmation(email), + style: textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: AppSpacing.lg), + Text( + l10n.emailCodeSentInstructions, + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + // Display demo code if in demo environment + BlocSelector( + selector: (state) => state.environment, + builder: (context, environment) { + if (environment == AppEnvironment.demo) { + return Padding( + padding: const EdgeInsets.only(top: AppSpacing.md), + child: Text( + l10n.demoCodeHint('123456'), + style: textTheme.bodyMedium?.copyWith( + color: colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ); + } + return const SizedBox.shrink(); + }, + ), + const SizedBox(height: AppSpacing.xl), + _EmailCodeVerificationForm( + email: email, + isLoading: isLoading, + ), + ], ), ); }, From 98b6801b30a69825e4cb3d0cb311728e81587b9d Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 10:47:18 +0100 Subject: [PATCH 5/5] refactor(authentication): implement AuthLayout in RequestCodePage This refactoring: - Introduces AuthLayout to wrap the content of _RequestCodeView - Removes redundant padding and center alignment - Simplifies the layout structure --- .../view/request_code_page.dart | 117 ++++++++---------- 1 file changed, 55 insertions(+), 62 deletions(-) diff --git a/lib/authentication/view/request_code_page.dart b/lib/authentication/view/request_code_page.dart index d7b6acf5..7f523cec 100644 --- a/lib/authentication/view/request_code_page.dart +++ b/lib/authentication/view/request_code_page.dart @@ -8,6 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app/bloc/app_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app/config/config.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/authentication/bloc/authentication_bloc.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/authentication/widgets/auth_layout.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart'; import 'package:go_router/go_router.dart'; @@ -101,69 +102,61 @@ class _RequestCodeView extends StatelessWidget { final isLoading = state.status == AuthenticationStatus.requestCodeLoading; - return Padding( - padding: const EdgeInsets.all(AppSpacing.paddingLarge), - child: Center( - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // --- Icon --- - Padding( - padding: const EdgeInsets.only(bottom: AppSpacing.xl), - child: Icon( - Icons.email_outlined, - size: AppSpacing.xxl * 2, - color: colorScheme.primary, - ), - ), - // const SizedBox(height: AppSpacing.lg), - // --- Explanation Text --- - Text( - l10n.requestCodePageHeadline, - style: textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: AppSpacing.md), - Text( - l10n.requestCodePageSubheadline, - style: textTheme.bodyLarge?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - textAlign: TextAlign.center, - ), - // Display demo email if in demo environment - BlocSelector( - selector: (state) => state.environment, - builder: (context, environment) { - if (environment == AppEnvironment.demo) { - return Padding( - padding: const EdgeInsets.only( - top: AppSpacing.lg, - ), - child: Text( - l10n.demoEmailHint( - 'admin@example.com | publisher@example.com', - ), - style: textTheme.bodyMedium?.copyWith( - color: colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - ); - } - return const SizedBox.shrink(); - }, - ), - const SizedBox(height: AppSpacing.xxl), - _EmailLinkForm(isLoading: isLoading), - ], + return AuthLayout( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // --- Icon --- + Padding( + padding: const EdgeInsets.only(bottom: AppSpacing.xl), + child: Icon( + Icons.email_outlined, + size: AppSpacing.xxl * 2, + color: colorScheme.primary, + ), + ), + // --- Explanation Text --- + Text( + l10n.requestCodePageHeadline, + style: textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: AppSpacing.md), + Text( + l10n.requestCodePageSubheadline, + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + // Display demo email if in demo environment + BlocSelector( + selector: (state) => state.environment, + builder: (context, environment) { + if (environment == AppEnvironment.demo) { + return Padding( + padding: const EdgeInsets.only(top: AppSpacing.lg), + child: Text( + l10n.demoEmailHint( + 'admin@example.com | publisher@example.com', + ), + style: textTheme.bodyMedium?.copyWith( + color: colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ); + } + return const SizedBox.shrink(); + }, ), - ), + const SizedBox(height: AppSpacing.xxl), + _EmailLinkForm(isLoading: isLoading), + ], ), ); },