From c2eb002ebe2adb770b453f709f6c4744d9ab85be Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:00:59 +0100 Subject: [PATCH 01/26] feat(l10n): add Arabic and English translations for about icon and close button - Add Arabic translations for "aboutIconTooltip" and "closeButtonText" in app_ar.arb - Add English translations for "aboutIconTooltip" and "closeButtonText" in app_en.arb - Include descriptions for new translations to provide context for translators --- lib/l10n/app_localizations.dart | 12 ++++++++++++ lib/l10n/app_localizations_ar.dart | 6 ++++++ lib/l10n/app_localizations_en.dart | 6 ++++++ lib/l10n/arb/app_ar.arb | 8 ++++++++ lib/l10n/arb/app_en.arb | 8 ++++++++ 5 files changed, 40 insertions(+) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index a774c5d1..19ecc205 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2713,6 +2713,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'No results found with current filters. Try resetting them.'** String get noResultsWithCurrentFilters; + + /// Tooltip for the information icon that shows page description. + /// + /// In en, this message translates to: + /// **'About this page'** + String get aboutIconTooltip; + + /// Text for the close button in a dialog. + /// + /// In en, this message translates to: + /// **'Close'** + String get closeButtonText; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index b91f5f65..940dc8ff 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -1448,4 +1448,10 @@ class AppLocalizationsAr extends AppLocalizations { @override String get noResultsWithCurrentFilters => 'لم يتم العثور على نتائج باستخدام الفلاتر الحالية. حاول إعادة تعيينها.'; + + @override + String get aboutIconTooltip => 'حول هذه الصفحة'; + + @override + String get closeButtonText => 'إغلاق'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 649118a2..b0b89aa6 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1453,4 +1453,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get noResultsWithCurrentFilters => 'No results found with current filters. Try resetting them.'; + + @override + String get aboutIconTooltip => 'About this page'; + + @override + String get closeButtonText => 'Close'; } diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 24878602..2a250568 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -1835,5 +1835,13 @@ "noResultsWithCurrentFilters": "لم يتم العثور على نتائج باستخدام الفلاتر الحالية. حاول إعادة تعيينها.", "@noResultsWithCurrentFilters": { "description": "Message displayed when no results are found due to active filters, prompting the user to reset them." + }, +"aboutIconTooltip": "حول هذه الصفحة", + "@aboutIconTooltip": { + "description": "Tooltip for the information icon that shows page description." + }, + "closeButtonText": "إغلاق", + "@closeButtonText": { + "description": "Text for the close button in a dialog." } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 9b420d45..db98ebb4 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1831,5 +1831,13 @@ "noResultsWithCurrentFilters": "No results found with current filters. Try resetting them.", "@noResultsWithCurrentFilters": { "description": "Message displayed when no results are found due to active filters, prompting the user to reset them." + }, + "aboutIconTooltip": "About this page", + "@aboutIconTooltip": { + "description": "Tooltip for the information icon that shows page description." + }, + "closeButtonText": "Close", + "@closeButtonText": { + "description": "Text for the close button in a dialog." } } \ No newline at end of file From 3a32342cb2bb24e5f34a81c4d81387913b7d4a37 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:01:10 +0100 Subject: [PATCH 02/26] feat(app_shell): enhance navigation rail and simplify app bar - Add app name at the top of the navigation rail - Implement UserNavigationRailFooter at the bottom of the navigation rail - Remove PopupMenuButton from app bar for user actions - Update imports to reflect changes --- lib/app/view/app_shell.dart | 50 ++++++++++++------------------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/lib/app/view/app_shell.dart b/lib/app/view/app_shell.dart index dffdc917..aa8dcb5c 100644 --- a/lib/app/view/app_shell.dart +++ b/lib/app/view/app_shell.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; -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/l10n/l10n.dart'; -import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/user_navigation_rail_footer.dart'; import 'package:go_router/go_router.dart'; import 'package:ui_kit/ui_kit.dart'; @@ -28,37 +26,9 @@ class AppShell extends StatelessWidget { return Scaffold( appBar: AppBar( title: Text(l10n.dashboardTitle), - actions: [ - PopupMenuButton( - onSelected: (value) { - if (value == 'settings') { - context.goNamed(Routes.settingsName); - } else if (value == 'signOut') { - context.read().add(const AppLogoutRequested()); - } - }, - itemBuilder: (BuildContext context) => >[ - PopupMenuItem( - value: 'settings', - child: Text(l10n.settings), - ), - PopupMenuItem( - value: 'signOut', - child: Text(l10n.signOut), - ), - ], - child: Padding( - padding: const EdgeInsets.all(AppSpacing.sm), - child: CircleAvatar( - backgroundColor: Theme.of(context).colorScheme.primaryContainer, - child: Icon( - Icons.person, - color: Theme.of(context).colorScheme.onPrimaryContainer, - ), - ), - ), - ), - const SizedBox(width: AppSpacing.sm), + // Removed PopupMenuButton for user actions + actions: const [ + SizedBox(width: AppSpacing.sm), ], ), body: AdaptiveScaffold( @@ -86,6 +56,18 @@ class AppShell extends StatelessWidget { label: l10n.appConfiguration, ), ], + // Add the app name at the top of the navigation rail + leadingExtendedNavRail: Padding( + padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg), + child: Text( + l10n.dashboardTitle, // App name at the top + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + // Add the UserNavigationRailFooter at the bottom of the navigation rail + trailingNavRail: const UserNavigationRailFooter(), body: (_) => Padding( padding: const EdgeInsets.fromLTRB( 0, From c7ada334b3cca1ef780611314365ea9dd51a7a45 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:01:34 +0100 Subject: [PATCH 03/26] refactor(app_configuration): improve app configuration page layout - Remove bottom PreferredSize widget - Add AboutIcon to actions - Simplify TabBar layout - Use padding from AppPadding medium instead of large --- .../view/app_configuration_page.dart | 46 ++++++------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/lib/app_configuration/view/app_configuration_page.dart b/lib/app_configuration/view/app_configuration_page.dart index e8144e30..3a881259 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -5,6 +5,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuratio import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/feed_configuration_tab.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/general_configuration_tab.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/about_icon.dart'; import 'package:ui_kit/ui_kit.dart'; /// {@template app_configuration_page} @@ -48,38 +49,21 @@ class _AppConfigurationPageState extends State l10n.appConfigurationPageTitle, style: Theme.of(context).textTheme.headlineSmall, ), - bottom: PreferredSize( - preferredSize: const Size.fromHeight( - kTextTabBarHeight + AppSpacing.lg, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only( - left: AppSpacing.lg, - right: AppSpacing.lg, - bottom: AppSpacing.lg, - ), - child: Text( - l10n.appConfigurationPageDescription, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - TabBar( - controller: _tabController, - tabAlignment: TabAlignment.start, - isScrollable: true, - tabs: [ - Tab(text: l10n.generalTab), - Tab(text: l10n.feedTab), - Tab(text: l10n.advertisementsTab), - ], - ), - ], + actions: [ + AboutIcon( + dialogTitle: l10n.appConfigurationPageTitle, + dialogDescription: l10n.appConfigurationPageDescription, ), + ], + bottom: TabBar( + controller: _tabController, + tabAlignment: TabAlignment.start, + isScrollable: true, + tabs: [ + Tab(text: l10n.generalTab), + Tab(text: l10n.feedTab), + Tab(text: l10n.advertisementsTab), + ], ), ), body: BlocConsumer( From 97ebae98832e53c30b85b921a2bedfce6b42ef1f Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:02:27 +0100 Subject: [PATCH 04/26] refactor(content_management): improve code formatting and structure - Adjust indentation and line breaks for better readability - Remove unnecessary imports and code - Replace description in AppBar with AboutIcon for better UI/UX - Optimize TabBar placement and actions in AppBar --- .../bloc/content_management_bloc.dart | 16 ++-- .../view/content_management_page.dart | 90 ++++++++----------- 2 files changed, 48 insertions(+), 58 deletions(-) diff --git a/lib/content_management/bloc/content_management_bloc.dart b/lib/content_management/bloc/content_management_bloc.dart index be6bc8e5..704e948d 100644 --- a/lib/content_management/bloc/content_management_bloc.dart +++ b/lib/content_management/bloc/content_management_bloc.dart @@ -104,9 +104,10 @@ class ContentManagementBloc ); }); - _deletionEventsSubscription = _pendingDeletionsService.deletionEvents.listen( - (event) => add(DeletionEventReceived(event)), - ); + _deletionEventsSubscription = _pendingDeletionsService.deletionEvents + .listen( + (event) => add(DeletionEventReceived(event)), + ); } final DataRepository _headlinesRepository; @@ -120,7 +121,8 @@ class ContentManagementBloc late final StreamSubscription _headlineUpdateSubscription; late final StreamSubscription _topicUpdateSubscription; late final StreamSubscription _sourceUpdateSubscription; - late final StreamSubscription> _deletionEventsSubscription; + late final StreamSubscription> + _deletionEventsSubscription; @override Future close() { @@ -221,7 +223,8 @@ class ContentManagementBloc final previousHeadlines = isPaginating ? state.headlines : []; final paginatedHeadlines = await _headlinesRepository.readAll( - filter: event.filter ?? buildHeadlinesFilterMap(_headlinesFilterBloc.state), + filter: + event.filter ?? buildHeadlinesFilterMap(_headlinesFilterBloc.state), sort: [const SortOption('updatedAt', SortOrder.desc)], pagination: PaginationOptions( cursor: event.startAfterId, @@ -609,8 +612,7 @@ class ContentManagementBloc final previousSources = isPaginating ? state.sources : []; final paginatedSources = await _sourcesRepository.readAll( - filter: - event.filter ?? buildSourcesFilterMap(_sourcesFilterBloc.state), + filter: event.filter ?? buildSourcesFilterMap(_sourcesFilterBloc.state), sort: [const SortOption('updatedAt', SortOrder.desc)], pagination: PaginationOptions( cursor: event.startAfterId, diff --git a/lib/content_management/view/content_management_page.dart b/lib/content_management/view/content_management_page.dart index 87832e5b..03bf0b00 100644 --- a/lib/content_management/view/content_management_page.dart +++ b/lib/content_management/view/content_management_page.dart @@ -12,8 +12,8 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/content_manageme import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/view/topics_page.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:flutter_news_app_web_dashboard_full_source_code/shared/widgets/about_icon.dart'; import 'package:go_router/go_router.dart'; -import 'package:ui_kit/ui_kit.dart'; /// {@template content_management_page} /// A page for Content Management with tabbed navigation for sub-sections. @@ -66,10 +66,14 @@ class _ContentManagementPageState extends State listenWhen: (previous, current) => previous.searchQuery != current.searchQuery || previous.selectedStatus != current.selectedStatus || - !const DeepCollectionEquality() - .equals(previous.selectedSourceIds, current.selectedSourceIds) || - !const DeepCollectionEquality() - .equals(previous.selectedTopicIds, current.selectedTopicIds) || + !const DeepCollectionEquality().equals( + previous.selectedSourceIds, + current.selectedSourceIds, + ) || + !const DeepCollectionEquality().equals( + previous.selectedTopicIds, + current.selectedTopicIds, + ) || !const DeepCollectionEquality().equals( previous.selectedCountryIds, current.selectedCountryIds, @@ -146,22 +150,22 @@ class _ContentManagementPageState extends State switch (activeTab) { case ContentManagementTab.headlines: context.read().add( - UndoDeleteHeadlineRequested( - lastPendingDeletionId, - ), - ); + UndoDeleteHeadlineRequested( + lastPendingDeletionId, + ), + ); case ContentManagementTab.topics: context.read().add( - UndoDeleteTopicRequested( - lastPendingDeletionId, - ), - ); + UndoDeleteTopicRequested( + lastPendingDeletionId, + ), + ); case ContentManagementTab.sources: context.read().add( - UndoDeleteSourceRequested( - lastPendingDeletionId, - ), - ); + UndoDeleteSourceRequested( + lastPendingDeletionId, + ), + ); } } }, @@ -174,40 +178,12 @@ class _ContentManagementPageState extends State child: Scaffold( appBar: AppBar( title: Text(l10n.contentManagement), - bottom: PreferredSize( - preferredSize: const Size.fromHeight( - kTextTabBarHeight + AppSpacing.lg, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only( - left: AppSpacing.lg, - right: AppSpacing.lg, - bottom: AppSpacing.lg, - ), - child: Text( - l10n.contentManagementPageDescription, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - TabBar( - controller: _tabController, - tabAlignment: TabAlignment.start, - isScrollable: true, - tabs: [ - Tab(text: l10n.headlines), - Tab(text: l10n.topics), - Tab(text: l10n.sources), - ], - ), - ], - ), - ), + // Removed bottom description actions: [ + AboutIcon( + dialogTitle: l10n.contentManagement, + dialogDescription: l10n.contentManagementPageDescription, + ), IconButton( icon: const Icon(Icons.filter_list), tooltip: l10n.filter, @@ -229,7 +205,9 @@ class _ContentManagementPageState extends State 'topicsRepository': topicsRepository, 'countriesRepository': countriesRepository, 'languagesRepository': languagesRepository, - 'headlinesFilterState': context.read().state, + 'headlinesFilterState': context + .read() + .state, 'topicsFilterState': context.read().state, 'sourcesFilterState': context.read().state, }; @@ -239,6 +217,16 @@ class _ContentManagementPageState extends State }, ), ], + bottom: TabBar( + controller: _tabController, + tabAlignment: TabAlignment.start, + isScrollable: true, + tabs: [ + Tab(text: l10n.headlines), + Tab(text: l10n.topics), + Tab(text: l10n.sources), + ], + ), ), floatingActionButton: FloatingActionButton( onPressed: () { From 61187cfbf8b8903c5d00bb510dfaf764617ebc19 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:02:58 +0100 Subject: [PATCH 05/26] style: format --- lib/content_management/view/headlines_page.dart | 15 ++++++++------- lib/content_management/view/sources_page.dart | 10 +++++----- lib/content_management/view/topics_page.dart | 10 +++++----- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/content_management/view/headlines_page.dart b/lib/content_management/view/headlines_page.dart index b87dd327..ec9e4755 100644 --- a/lib/content_management/view/headlines_page.dart +++ b/lib/content_management/view/headlines_page.dart @@ -31,9 +31,9 @@ class _HeadlinesPageState extends State { context.read().add( LoadHeadlinesRequested( limit: kDefaultRowsPerPage, - filter: context - .read() - .buildHeadlinesFilterMap(context.read().state), + filter: context.read().buildHeadlinesFilterMap( + context.read().state, + ), ), ); } @@ -54,8 +54,9 @@ class _HeadlinesPageState extends State { padding: const EdgeInsets.all(AppSpacing.lg), child: BlocBuilder( builder: (context, state) { - final headlinesFilterState = - context.watch().state; + final headlinesFilterState = context + .watch() + .state; final filtersActive = _areFiltersActive(headlinesFilterState); if (state.headlinesStatus == ContentManagementStatus.loading && @@ -99,8 +100,8 @@ class _HeadlinesPageState extends State { ElevatedButton( onPressed: () { context.read().add( - const HeadlinesFilterReset(), - ); + const HeadlinesFilterReset(), + ); }, child: Text(l10n.resetFiltersButtonText), ), diff --git a/lib/content_management/view/sources_page.dart b/lib/content_management/view/sources_page.dart index 15760450..7e73544e 100644 --- a/lib/content_management/view/sources_page.dart +++ b/lib/content_management/view/sources_page.dart @@ -32,9 +32,9 @@ class _SourcesPageState extends State { context.read().add( LoadSourcesRequested( limit: kDefaultRowsPerPage, - filter: context - .read() - .buildSourcesFilterMap(context.read().state), + filter: context.read().buildSourcesFilterMap( + context.read().state, + ), ), ); } @@ -99,8 +99,8 @@ class _SourcesPageState extends State { ElevatedButton( onPressed: () { context.read().add( - const SourcesFilterReset(), - ); + const SourcesFilterReset(), + ); }, child: Text(l10n.resetFiltersButtonText), ), diff --git a/lib/content_management/view/topics_page.dart b/lib/content_management/view/topics_page.dart index d5795e53..d1a6bebd 100644 --- a/lib/content_management/view/topics_page.dart +++ b/lib/content_management/view/topics_page.dart @@ -31,9 +31,9 @@ class _TopicPageState extends State { context.read().add( LoadTopicsRequested( limit: kDefaultRowsPerPage, - filter: context - .read() - .buildTopicsFilterMap(context.read().state), + filter: context.read().buildTopicsFilterMap( + context.read().state, + ), ), ); } @@ -95,8 +95,8 @@ class _TopicPageState extends State { ElevatedButton( onPressed: () { context.read().add( - const TopicsFilterReset(), - ); + const TopicsFilterReset(), + ); }, child: Text(l10n.resetFiltersButtonText), ), From d966b03bee8e6c3235fc4587c91605bcf2bb8f0a Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:03:10 +0100 Subject: [PATCH 06/26] style: format --- .../widgets/filter_dialog/filter_dialog.dart | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/content_management/widgets/filter_dialog/filter_dialog.dart b/lib/content_management/widgets/filter_dialog/filter_dialog.dart index 051e3ccc..06d20d1c 100644 --- a/lib/content_management/widgets/filter_dialog/filter_dialog.dart +++ b/lib/content_management/widgets/filter_dialog/filter_dialog.dart @@ -118,20 +118,22 @@ class _FilterDialogState extends State { onPressed: () { // Dispatch reset event context.read().add( - const FilterDialogReset(), - ); + const FilterDialogReset(), + ); // After reset, get the new state and apply filters - final resetState = - context.read().state.copyWith( - searchQuery: '', - selectedStatus: ContentStatus.active, - selectedSourceIds: [], - selectedTopicIds: [], - selectedCountryIds: [], - selectedSourceTypes: [], - selectedLanguageCodes: [], - selectedHeadquartersCountryIds: [], - ); + final resetState = context + .read() + .state + .copyWith( + searchQuery: '', + selectedStatus: ContentStatus.active, + selectedSourceIds: [], + selectedTopicIds: [], + selectedCountryIds: [], + selectedSourceTypes: [], + selectedLanguageCodes: [], + selectedHeadquartersCountryIds: [], + ); _dispatchFilterApplied(resetState); Navigator.of(context).pop(); }, From 619dc4bbcbffabd94c9aef12c1ca3f98ad02bb0d Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:03:34 +0100 Subject: [PATCH 07/26] feat(settings): add about icon to settings page - Remove settings page description from AppBar - Add AboutIcon widget to AppBar actions - Import AboutIcon from shared widgets --- lib/settings/view/settings_page.dart | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/lib/settings/view/settings_page.dart b/lib/settings/view/settings_page.dart index 09639fac..57f9d426 100644 --- a/lib/settings/view/settings_page.dart +++ b/lib/settings/view/settings_page.dart @@ -6,6 +6,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/app/bloc/app_blo import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/settings/bloc/settings_bloc.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/about_icon.dart'; import 'package:ui_kit/ui_kit.dart'; /// {@template settings_page} @@ -60,22 +61,12 @@ class _SettingsViewState extends State<_SettingsView> { return Scaffold( appBar: AppBar( title: Text(l10n.settings), - bottom: PreferredSize( - preferredSize: const Size.fromHeight(kToolbarHeight + AppSpacing.lg), - child: Padding( - padding: const EdgeInsets.only( - left: AppSpacing.lg, - right: AppSpacing.lg, - bottom: AppSpacing.lg, - ), - child: Text( - l10n.settingsPageDescription, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), + actions: [ + AboutIcon( + dialogTitle: l10n.settings, + dialogDescription: l10n.settingsPageDescription, ), - ), + ], ), body: BlocConsumer( listenWhen: (previous, current) => From 9a4b38a75ec77d3fa1cd8fa895084060e3c8cd67 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:03:45 +0100 Subject: [PATCH 08/26] feat(ui): add about icon widget - Create a new AboutIcon widget that displays information in a dialog - The widget takes dialogTitle and dialogDescription as parameters - It shows an info icon which, when pressed, displays an AlertDialog - The dialog includes a close button for dismissal --- lib/shared/widgets/about_icon.dart | 53 ++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 lib/shared/widgets/about_icon.dart diff --git a/lib/shared/widgets/about_icon.dart b/lib/shared/widgets/about_icon.dart new file mode 100644 index 00000000..f8dadb64 --- /dev/null +++ b/lib/shared/widgets/about_icon.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; + +/// {@template about_icon} +/// A widget that displays an information icon which, when pressed, shows +/// a dialog with a title and a descriptive message. +/// {@endtemplate} +class AboutIcon extends StatelessWidget { + /// {@macro about_icon} + const AboutIcon({ + required this.dialogTitle, + required this.dialogDescription, + super.key, + }); + + /// The title to display in the dialog. + final String dialogTitle; + + /// The descriptive message to display in the dialog. + final String dialogDescription; + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizationsX(context).l10n; + return IconButton( + icon: const Icon(Icons.info_outline), + tooltip: l10n.aboutIconTooltip, + onPressed: () { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return AlertDialog( + title: Text( + dialogTitle, + style: Theme.of(dialogContext).textTheme.titleLarge, + ), + content: Text( + dialogDescription, + style: Theme.of(dialogContext).textTheme.bodyMedium, + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(dialogContext).pop(), + child: Text(l10n.closeButtonText), + ), + ], + ); + }, + ); + }, + ); + } +} From 2c5b818b4905b6550cc4318814fc2bfba87df023 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:03:56 +0100 Subject: [PATCH 09/26] feat(ui): add user navigation rail footer widget - Create a new widget for displaying user-specific actions in the navigation rail footer - Include settings and sign-out options - Implement localization support for labels - Add routing for settings page and logout functionality --- .../widgets/user_navigation_rail_footer.dart | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 lib/shared/widgets/user_navigation_rail_footer.dart diff --git a/lib/shared/widgets/user_navigation_rail_footer.dart b/lib/shared/widgets/user_navigation_rail_footer.dart new file mode 100644 index 00000000..855541b8 --- /dev/null +++ b/lib/shared/widgets/user_navigation_rail_footer.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +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/l10n/l10n.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart'; +import 'package:go_router/go_router.dart'; +import 'package:ui_kit/ui_kit.dart'; + +/// {@template user_navigation_rail_footer} +/// A widget designed to be placed at the bottom of a [NavigationRail] +/// to display user-specific actions like settings and sign-out. +/// {@endtemplate} +class UserNavigationRailFooter extends StatelessWidget { + /// {@macro user_navigation_rail_footer} + const UserNavigationRailFooter({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizationsX(context).l10n; + final theme = Theme.of(context); + + return Container( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.sm, + vertical: AppSpacing.md, + ), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(AppSpacing.sm), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Settings Tile + ListTile( + leading: Icon( + Icons.settings, + color: theme.colorScheme.onSurface, + ), + title: Text( + l10n.settings, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurface, + ), + ), + onTap: () { + context.goNamed(Routes.settingsName); + }, + ), + // Sign Out Tile + ListTile( + leading: Icon( + Icons.logout, + color: theme.colorScheme.error, + ), + title: Text( + l10n.signOut, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.error, + ), + ), + onTap: () { + context.read().add(const AppLogoutRequested()); + }, + ), + ], + ), + ); + } +} From 241696a19c993768eb770bb07a6ee8aaa599ac23 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:04:14 +0100 Subject: [PATCH 10/26] feat(shared): add exports for new widgets - Add export for about_icon.dart - Add export for user_navigation_rail_footer.dart - Update export path for searchable_selection_input.dart --- lib/shared/widgets/widgets.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/shared/widgets/widgets.dart b/lib/shared/widgets/widgets.dart index effa3f4f..8ca08924 100644 --- a/lib/shared/widgets/widgets.dart +++ b/lib/shared/widgets/widgets.dart @@ -1 +1,3 @@ -export 'searchable_selection_input.dart'; +export 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/about_icon.dart'; +export 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/searchable_selection_input.dart'; +export 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/user_navigation_rail_footer.dart'; From 066fed55d3cf9a872c0430882734e2b148799fa7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:19:50 +0100 Subject: [PATCH 11/26] refactor(app_shell): restructure navigation rail and app bar - Remove app bar and move dashboard title to navigation rail - Add UserNavigationRailFooter and align to bottom - Implement adaptive scaffolding for various screen sizes - Adjust layout for both extended and unextended navigation rail states --- lib/app/view/app_shell.dart | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/app/view/app_shell.dart b/lib/app/view/app_shell.dart index aa8dcb5c..1b8561e2 100644 --- a/lib/app/view/app_shell.dart +++ b/lib/app/view/app_shell.dart @@ -24,13 +24,6 @@ class AppShell extends StatelessWidget { Widget build(BuildContext context) { final l10n = AppLocalizationsX(context).l10n; return Scaffold( - appBar: AppBar( - title: Text(l10n.dashboardTitle), - // Removed PopupMenuButton for user actions - actions: const [ - SizedBox(width: AppSpacing.sm), - ], - ), body: AdaptiveScaffold( selectedIndex: navigationShell.currentIndex, onSelectedIndexChange: (index) { @@ -56,18 +49,29 @@ class AppShell extends StatelessWidget { label: l10n.appConfiguration, ), ], - // Add the app name at the top of the navigation rail + // App name at the top of the navigation rail for both extended and unextended states. + leadingUnextendedNavRail: Padding( + padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg), + child: Text( + l10n.dashboardTitle, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + ), leadingExtendedNavRail: Padding( padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg), child: Text( - l10n.dashboardTitle, // App name at the top + l10n.dashboardTitle, style: Theme.of(context).textTheme.titleLarge?.copyWith( color: Theme.of(context).colorScheme.primary, ), ), ), - // Add the UserNavigationRailFooter at the bottom of the navigation rail + // UserNavigationRailFooter pinned to the bottom of the navigation rail. trailingNavRail: const UserNavigationRailFooter(), + // Ensure the navigation rail items are aligned to the start, pushing the footer to the end. + groupAlignment: 1, body: (_) => Padding( padding: const EdgeInsets.fromLTRB( 0, From 6f532dd6320e976634d9b4cc865209137872149c Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:20:06 +0100 Subject: [PATCH 12/26] feat(app_configuration): move about icon to app bar title - Replace AppBar actions with a Row containing the title and AboutIcon - Add SizedBox for spacing between title and icon - This change allows for a more compact and centered app bar layout --- .../view/app_configuration_page.dart | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/app_configuration/view/app_configuration_page.dart b/lib/app_configuration/view/app_configuration_page.dart index 3a881259..040edf9b 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -45,16 +45,22 @@ class _AppConfigurationPageState extends State final l10n = AppLocalizationsX(context).l10n; return Scaffold( appBar: AppBar( - title: Text( - l10n.appConfigurationPageTitle, - style: Theme.of(context).textTheme.headlineSmall, + title: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + l10n.appConfigurationPageTitle, + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox( + width: AppSpacing.xs, + ), // Spacing between title and icon + AboutIcon( + dialogTitle: l10n.appConfigurationPageTitle, + dialogDescription: l10n.appConfigurationPageDescription, + ), + ], ), - actions: [ - AboutIcon( - dialogTitle: l10n.appConfigurationPageTitle, - dialogDescription: l10n.appConfigurationPageDescription, - ), - ], bottom: TabBar( controller: _tabController, tabAlignment: TabAlignment.start, From 86b857e53ea4b3ff8ad6ad46132f73cfdb06f939 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 20:20:18 +0100 Subject: [PATCH 13/26] feat(ui): add about icon to app bar titles and update user navigation rail footer - Add AboutIcon to ContentManagementPage and SettingsPage app bars - Move user navigation rail footer content outside of Container widget - Adjust spacing and layout for app bar titles and icons --- .../view/content_management_page.dart | 20 +++-- lib/settings/view/settings_page.dart | 20 +++-- .../widgets/user_navigation_rail_footer.dart | 73 ++++++++----------- 3 files changed, 59 insertions(+), 54 deletions(-) diff --git a/lib/content_management/view/content_management_page.dart b/lib/content_management/view/content_management_page.dart index 03bf0b00..ded687df 100644 --- a/lib/content_management/view/content_management_page.dart +++ b/lib/content_management/view/content_management_page.dart @@ -14,6 +14,7 @@ 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:flutter_news_app_web_dashboard_full_source_code/shared/widgets/about_icon.dart'; import 'package:go_router/go_router.dart'; +import 'package:ui_kit/ui_kit.dart'; /// {@template content_management_page} /// A page for Content Management with tabbed navigation for sub-sections. @@ -177,13 +178,20 @@ class _ContentManagementPageState extends State ], child: Scaffold( appBar: AppBar( - title: Text(l10n.contentManagement), - // Removed bottom description + title: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(l10n.contentManagement), + const SizedBox( + width: AppSpacing.xs, + ), // Spacing between title and icon + AboutIcon( + dialogTitle: l10n.contentManagement, + dialogDescription: l10n.contentManagementPageDescription, + ), + ], + ), actions: [ - AboutIcon( - dialogTitle: l10n.contentManagement, - dialogDescription: l10n.contentManagementPageDescription, - ), IconButton( icon: const Icon(Icons.filter_list), tooltip: l10n.filter, diff --git a/lib/settings/view/settings_page.dart b/lib/settings/view/settings_page.dart index 57f9d426..056cc2e6 100644 --- a/lib/settings/view/settings_page.dart +++ b/lib/settings/view/settings_page.dart @@ -60,13 +60,19 @@ class _SettingsViewState extends State<_SettingsView> { final l10n = AppLocalizationsX(context).l10n; return Scaffold( appBar: AppBar( - title: Text(l10n.settings), - actions: [ - AboutIcon( - dialogTitle: l10n.settings, - dialogDescription: l10n.settingsPageDescription, - ), - ], + title: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(l10n.settings), + const SizedBox( + width: AppSpacing.xs, + ), // Spacing between title and icon + AboutIcon( + dialogTitle: l10n.settings, + dialogDescription: l10n.settingsPageDescription, + ), + ], + ), ), body: BlocConsumer( listenWhen: (previous, current) => diff --git a/lib/shared/widgets/user_navigation_rail_footer.dart b/lib/shared/widgets/user_navigation_rail_footer.dart index 855541b8..fb805514 100644 --- a/lib/shared/widgets/user_navigation_rail_footer.dart +++ b/lib/shared/widgets/user_navigation_rail_footer.dart @@ -4,7 +4,6 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/app/bloc/app_blo 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'; -import 'package:ui_kit/ui_kit.dart'; /// {@template user_navigation_rail_footer} /// A widget designed to be placed at the bottom of a [NavigationRail] @@ -19,52 +18,44 @@ class UserNavigationRailFooter extends StatelessWidget { final l10n = AppLocalizationsX(context).l10n; final theme = Theme.of(context); - return Container( - padding: const EdgeInsets.symmetric( - horizontal: AppSpacing.sm, - vertical: AppSpacing.md, - ), - decoration: BoxDecoration( - color: theme.colorScheme.surfaceContainerHighest, - borderRadius: BorderRadius.circular(AppSpacing.sm), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Settings Tile - ListTile( - leading: Icon( - Icons.settings, + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Settings Tile + ListTile( + leading: Icon( + Icons.settings, + color: theme.colorScheme.onSurface, + size: 24, // Explicitly set icon size + ), + title: Text( + l10n.settings, + style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface, ), - title: Text( - l10n.settings, - style: theme.textTheme.bodyMedium?.copyWith( - color: theme.colorScheme.onSurface, - ), - ), - onTap: () { - context.goNamed(Routes.settingsName); - }, ), - // Sign Out Tile - ListTile( - leading: Icon( - Icons.logout, + onTap: () { + context.goNamed(Routes.settingsName); + }, + ), + // Sign Out Tile + ListTile( + leading: Icon( + Icons.logout, + color: theme.colorScheme.error, + size: 24, // Explicitly set icon size + ), + title: Text( + l10n.signOut, + style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.error, ), - title: Text( - l10n.signOut, - style: theme.textTheme.bodyMedium?.copyWith( - color: theme.colorScheme.error, - ), - ), - onTap: () { - context.read().add(const AppLogoutRequested()); - }, ), - ], - ), + onTap: () { + context.read().add(const AppLogoutRequested()); + }, + ), + ], ); } } From a64cdabb526e531d550cbb0a6398830fe4699283 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 21:08:39 +0100 Subject: [PATCH 14/26] style(app_configuration): adjust title spacing and remove font style - Remove custom font style for page title - Increase spacing between title and icon --- lib/app_configuration/view/app_configuration_page.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/app_configuration/view/app_configuration_page.dart b/lib/app_configuration/view/app_configuration_page.dart index 040edf9b..1a7a41d5 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -50,10 +50,9 @@ class _AppConfigurationPageState extends State children: [ Text( l10n.appConfigurationPageTitle, - style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox( - width: AppSpacing.xs, + width: AppSpacing.sm, ), // Spacing between title and icon AboutIcon( dialogTitle: l10n.appConfigurationPageTitle, From f972771e201a91821ee8f59dd63ce65ac9d6f007 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 21:08:52 +0100 Subject: [PATCH 15/26] style(content_management): increase spacing between title and icon - Change SizedBox width from AppSpacing.xs to AppSpacing.sm --- lib/content_management/view/content_management_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/content_management/view/content_management_page.dart b/lib/content_management/view/content_management_page.dart index ded687df..93d9501c 100644 --- a/lib/content_management/view/content_management_page.dart +++ b/lib/content_management/view/content_management_page.dart @@ -183,7 +183,7 @@ class _ContentManagementPageState extends State children: [ Text(l10n.contentManagement), const SizedBox( - width: AppSpacing.xs, + width: AppSpacing.sm, ), // Spacing between title and icon AboutIcon( dialogTitle: l10n.contentManagement, From d314ad0c628c3b449f066d50e337cf416e45f03a Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 21:09:00 +0100 Subject: [PATCH 16/26] style(settings): increase spacing between title and icon - Adjust spacing from AppSpacing.xs to AppSpacing.sm for better visual separation --- lib/settings/view/settings_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/settings/view/settings_page.dart b/lib/settings/view/settings_page.dart index 056cc2e6..c3c8f9b2 100644 --- a/lib/settings/view/settings_page.dart +++ b/lib/settings/view/settings_page.dart @@ -65,7 +65,7 @@ class _SettingsViewState extends State<_SettingsView> { children: [ Text(l10n.settings), const SizedBox( - width: AppSpacing.xs, + width: AppSpacing.sm, ), // Spacing between title and icon AboutIcon( dialogTitle: l10n.settings, From 6f877897bb06a6a5a65807ab59998665a1ace3da Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 21:09:12 +0100 Subject: [PATCH 17/26] feat(AboutIcon): customize icon color and size - Add theme color for icon to match surface variant - Set icon size to 20 for better visibility - These changes improve the icon's appearance and consistency with the app's design guidelines --- lib/shared/widgets/about_icon.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/shared/widgets/about_icon.dart b/lib/shared/widgets/about_icon.dart index f8dadb64..9c065daa 100644 --- a/lib/shared/widgets/about_icon.dart +++ b/lib/shared/widgets/about_icon.dart @@ -22,8 +22,13 @@ class AboutIcon extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = AppLocalizationsX(context).l10n; + final theme = Theme.of(context); return IconButton( - icon: const Icon(Icons.info_outline), + icon: Icon( + Icons.info_outline, + color: theme.colorScheme.onSurfaceVariant, + ), + iconSize: 20, tooltip: l10n.aboutIconTooltip, onPressed: () { showDialog( From 657623e9bf2fed3638e15e4968f3bd72c4a065a3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 21:09:46 +0100 Subject: [PATCH 18/26] feat(app_shell): refactor navigation rail and add settings/logout tiles - Replace UserNavigationRailFooter with custom settings and sign-out tiles - Add expanded row for app name in extended state - Implement consistent styling for nav rail items - Add Bloc listener for app logout functionality - Update imports to include necessary packages and routes --- lib/app/view/app_shell.dart | 120 ++++++++++++++++++++++++++++++------ 1 file changed, 100 insertions(+), 20 deletions(-) diff --git a/lib/app/view/app_shell.dart b/lib/app/view/app_shell.dart index 1b8561e2..fedb8e68 100644 --- a/lib/app/view/app_shell.dart +++ b/lib/app/view/app_shell.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; +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/l10n/l10n.dart'; -import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/user_navigation_rail_footer.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart'; import 'package:go_router/go_router.dart'; import 'package:ui_kit/ui_kit.dart'; @@ -23,6 +25,11 @@ class AppShell extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = AppLocalizationsX(context).l10n; + final theme = Theme.of(context); + + // Use the same text style as the NavigationRail labels for consistency. + final navRailLabelStyle = theme.textTheme.labelMedium; + return Scaffold( body: AdaptiveScaffold( selectedIndex: navigationShell.currentIndex, @@ -49,29 +56,102 @@ class AppShell extends StatelessWidget { label: l10n.appConfiguration, ), ], - // App name at the top of the navigation rail for both extended and unextended states. - leadingUnextendedNavRail: Padding( - padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg), - child: Text( - l10n.dashboardTitle, - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), - ), + leadingUnextendedNavRail: const Padding( + padding: EdgeInsets.symmetric(vertical: AppSpacing.lg), + child: Icon(Icons.newspaper_outlined), ), leadingExtendedNavRail: Padding( - padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg), - child: Text( - l10n.dashboardTitle, - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), + padding: const EdgeInsets.all(AppSpacing.lg), + child: Row( + children: [ + const Icon(Icons.newspaper_outlined), + const SizedBox(width: AppSpacing.md), + Text( + l10n.dashboardTitle, + style: theme.textTheme.titleLarge?.copyWith( + color: theme.colorScheme.primary, + ), + ), + ], ), ), - // UserNavigationRailFooter pinned to the bottom of the navigation rail. - trailingNavRail: const UserNavigationRailFooter(), - // Ensure the navigation rail items are aligned to the start, pushing the footer to the end. - groupAlignment: 1, + trailingNavRail: Builder( + builder: (context) { + final isExtended = Breakpoints.mediumLarge.isActive(context); + return Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: AppSpacing.lg), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + // Settings Tile + InkWell( + onTap: () => context.goNamed(Routes.settingsName), + child: Padding( + padding: EdgeInsets.symmetric( + vertical: AppSpacing.md, + horizontal: isExtended ? 24 : 16, + ), + child: Row( + mainAxisAlignment: isExtended + ? MainAxisAlignment.start + : MainAxisAlignment.center, + children: [ + Icon( + Icons.settings_outlined, + color: theme.colorScheme.onSurfaceVariant, + size: 24, + ), + if (isExtended) ...[ + const SizedBox(width: AppSpacing.lg), + Text( + l10n.settings, + style: navRailLabelStyle, + ), + ], + ], + ), + ), + ), + // Sign Out Tile + InkWell( + onTap: () => context.read().add( + const AppLogoutRequested(), + ), + child: Padding( + padding: EdgeInsets.symmetric( + vertical: AppSpacing.md, + horizontal: isExtended ? 24 : 16, + ), + child: Row( + mainAxisAlignment: isExtended + ? MainAxisAlignment.start + : MainAxisAlignment.center, + children: [ + Icon( + Icons.logout, + color: theme.colorScheme.error, + size: 24, + ), + if (isExtended) ...[ + const SizedBox(width: AppSpacing.lg), + Text( + l10n.signOut, + style: navRailLabelStyle?.copyWith( + color: theme.colorScheme.error, + ), + ), + ], + ], + ), + ), + ), + ], + ), + ), + ); + }, + ), body: (_) => Padding( padding: const EdgeInsets.fromLTRB( 0, From 11a5173d054d72b44592bd372251716eabad8de5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 21:30:04 +0100 Subject: [PATCH 19/26] refactor: remove unused UserNavigationRailFooter widget - Deleted the entire UserNavigationRailFooter widget class - Removed all related imports and code --- .../widgets/user_navigation_rail_footer.dart | 61 ------------------- 1 file changed, 61 deletions(-) delete mode 100644 lib/shared/widgets/user_navigation_rail_footer.dart diff --git a/lib/shared/widgets/user_navigation_rail_footer.dart b/lib/shared/widgets/user_navigation_rail_footer.dart deleted file mode 100644 index fb805514..00000000 --- a/lib/shared/widgets/user_navigation_rail_footer.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:flutter/material.dart'; -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/l10n/l10n.dart'; -import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart'; -import 'package:go_router/go_router.dart'; - -/// {@template user_navigation_rail_footer} -/// A widget designed to be placed at the bottom of a [NavigationRail] -/// to display user-specific actions like settings and sign-out. -/// {@endtemplate} -class UserNavigationRailFooter extends StatelessWidget { - /// {@macro user_navigation_rail_footer} - const UserNavigationRailFooter({super.key}); - - @override - Widget build(BuildContext context) { - final l10n = AppLocalizationsX(context).l10n; - final theme = Theme.of(context); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Settings Tile - ListTile( - leading: Icon( - Icons.settings, - color: theme.colorScheme.onSurface, - size: 24, // Explicitly set icon size - ), - title: Text( - l10n.settings, - style: theme.textTheme.bodyMedium?.copyWith( - color: theme.colorScheme.onSurface, - ), - ), - onTap: () { - context.goNamed(Routes.settingsName); - }, - ), - // Sign Out Tile - ListTile( - leading: Icon( - Icons.logout, - color: theme.colorScheme.error, - size: 24, // Explicitly set icon size - ), - title: Text( - l10n.signOut, - style: theme.textTheme.bodyMedium?.copyWith( - color: theme.colorScheme.error, - ), - ), - onTap: () { - context.read().add(const AppLogoutRequested()); - }, - ), - ], - ); - } -} From 850f7043b8ee8f61bef5dd9f8ee27b9287f42a9d Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 21:34:46 +0100 Subject: [PATCH 20/26] feat(navigator): extend navigator in more screen sizes - Modify the condition for extending the navigator - Include small screen sizes in addition to medium-large sizes --- lib/app/view/app_shell.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/app/view/app_shell.dart b/lib/app/view/app_shell.dart index fedb8e68..4aec8f4f 100644 --- a/lib/app/view/app_shell.dart +++ b/lib/app/view/app_shell.dart @@ -77,7 +77,9 @@ class AppShell extends StatelessWidget { ), trailingNavRail: Builder( builder: (context) { - final isExtended = Breakpoints.mediumLarge.isActive(context); + final isExtended = + Breakpoints.mediumLarge.isActive(context) || + Breakpoints.small.isActive(context); return Expanded( child: Padding( padding: const EdgeInsets.only(bottom: AppSpacing.lg), From e8150dcd7c2e86363a3bf26b130115cb377c304c Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 21:38:19 +0100 Subject: [PATCH 21/26] refactor(widgets): remove unused export - Removed unused export statement for 'user_navigation_rail_footer.dart' - This change simplifies the codebase by eliminating unused imports --- lib/shared/widgets/widgets.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/shared/widgets/widgets.dart b/lib/shared/widgets/widgets.dart index 8ca08924..311cf333 100644 --- a/lib/shared/widgets/widgets.dart +++ b/lib/shared/widgets/widgets.dart @@ -1,3 +1,2 @@ export 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/about_icon.dart'; export 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/searchable_selection_input.dart'; -export 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/user_navigation_rail_footer.dart'; From 3c38f6d587e72cfc10ce5d28a8b9983c7501b5ad Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 21:40:18 +0100 Subject: [PATCH 22/26] style: misc --- .../view/app_configuration_page.dart | 2 +- .../widgets/feed_ad_settings_form.dart | 3 --- .../widgets/interstitial_ad_settings_form.dart | 4 ---- .../bloc/content_management_state.dart | 8 +++----- .../view/content_management_page.dart | 4 ++-- lib/content_management/view/headlines_page.dart | 8 ++++---- lib/content_management/view/topics_page.dart | 8 ++++---- .../widgets/content_action_buttons.dart | 2 +- .../widgets/filter_dialog/filter_dialog.dart | 2 +- .../create_local_banner_ad_bloc.dart | 2 +- .../create_local_interstitial_ad_bloc.dart | 2 +- .../create_local_native_ad_bloc.dart | 2 +- .../create_local_video_ad_bloc.dart | 2 +- .../update_local_banner_ad_bloc.dart | 2 +- .../update_local_interstitial_ad_bloc.dart | 2 +- .../update_local_native_ad_bloc.dart | 2 +- .../update_local_video_ad_bloc.dart | 2 +- .../view/local_ads_management_page.dart | 1 - lib/router/router.dart | 2 +- lib/settings/view/settings_page.dart | 2 +- lib/shared/widgets/searchable_selection_input.dart | 14 +++++++------- .../bloc/searchable_selection_state.dart | 10 +++++----- .../selection_page/searchable_selection_page.dart | 2 +- .../selection_page/selection_page_arguments.dart | 12 ++++++------ 24 files changed, 45 insertions(+), 55 deletions(-) diff --git a/lib/app_configuration/view/app_configuration_page.dart b/lib/app_configuration/view/app_configuration_page.dart index 1a7a41d5..f42c8603 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -53,7 +53,7 @@ class _AppConfigurationPageState extends State ), const SizedBox( width: AppSpacing.sm, - ), // Spacing between title and icon + ), AboutIcon( dialogTitle: l10n.appConfigurationPageTitle, dialogDescription: l10n.appConfigurationPageDescription, diff --git a/lib/app_configuration/widgets/feed_ad_settings_form.dart b/lib/app_configuration/widgets/feed_ad_settings_form.dart index 202a1013..301cf7e5 100644 --- a/lib/app_configuration/widgets/feed_ad_settings_form.dart +++ b/lib/app_configuration/widgets/feed_ad_settings_form.dart @@ -49,8 +49,6 @@ class _FeedAdSettingsFormState extends State vsync: this, ); _initializeControllers(); - // Removed _tabController.addListener(_onTabChanged); as automatic disabling - // for premium users is no longer required. } /// Initializes text editing controllers for each user role based on current @@ -272,7 +270,6 @@ class _FeedAdSettingsFormState extends State FeedAdConfiguration config, ) { final roleConfig = config.visibleTo[role]; - // Removed isEnabled check as premium users can now be manually configured. return Column( children: [ diff --git a/lib/app_configuration/widgets/interstitial_ad_settings_form.dart b/lib/app_configuration/widgets/interstitial_ad_settings_form.dart index a9f3bc2c..5c0193bc 100644 --- a/lib/app_configuration/widgets/interstitial_ad_settings_form.dart +++ b/lib/app_configuration/widgets/interstitial_ad_settings_form.dart @@ -45,8 +45,6 @@ class _InterstitialAdSettingsFormState extends State vsync: this, ); _initializeControllers(); - // Removed _tabController.addListener(_onTabChanged); as automatic disabling - // for premium users is no longer required. } /// Initializes text editing controllers for each user role based on current @@ -204,7 +202,6 @@ class _InterstitialAdSettingsFormState extends State InterstitialAdConfiguration config, ) { final roleConfig = config.visibleTo[role]; - // Removed isEnabled check as premium users can now be manually configured. return Column( children: [ @@ -265,7 +262,6 @@ class _InterstitialAdSettingsFormState extends State }, controller: _transitionsBeforeShowingInterstitialAdsControllers[role], - // Removed enabled: isEnabled ), ), ], diff --git a/lib/content_management/bloc/content_management_state.dart b/lib/content_management/bloc/content_management_state.dart index ca96559d..c7c6009b 100644 --- a/lib/content_management/bloc/content_management_state.dart +++ b/lib/content_management/bloc/content_management_state.dart @@ -122,11 +122,9 @@ class ContentManagementState extends Equatable { sources: sources ?? this.sources, sourcesCursor: sourcesCursor ?? this.sourcesCursor, sourcesHasMore: sourcesHasMore ?? this.sourcesHasMore, - exception: exception, // Explicitly set to null if not provided - lastPendingDeletionId: - lastPendingDeletionId, // Explicitly set to null if not provided - snackbarMessage: - snackbarMessage, // Explicitly set to null if not provided + exception: exception, + lastPendingDeletionId: lastPendingDeletionId, + snackbarMessage: snackbarMessage, ); } diff --git a/lib/content_management/view/content_management_page.dart b/lib/content_management/view/content_management_page.dart index 93d9501c..951b602f 100644 --- a/lib/content_management/view/content_management_page.dart +++ b/lib/content_management/view/content_management_page.dart @@ -1,4 +1,4 @@ -import 'package:collection/collection.dart'; // For deep equality check on filter maps +import 'package:collection/collection.dart'; import 'package:core/core.dart'; import 'package:data_repository/data_repository.dart'; import 'package:flutter/material.dart'; @@ -184,7 +184,7 @@ class _ContentManagementPageState extends State Text(l10n.contentManagement), const SizedBox( width: AppSpacing.sm, - ), // Spacing between title and icon + ), AboutIcon( dialogTitle: l10n.contentManagement, dialogDescription: l10n.contentManagementPageDescription, diff --git a/lib/content_management/view/headlines_page.dart b/lib/content_management/view/headlines_page.dart index ec9e4755..e22e3d5e 100644 --- a/lib/content_management/view/headlines_page.dart +++ b/lib/content_management/view/headlines_page.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/content_management_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/headlines_filter/headlines_filter_bloc.dart'; -import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/widgets/content_action_buttons.dart'; // Import the new widget +import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/widgets/content_action_buttons.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.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'; @@ -146,7 +146,7 @@ class _HeadlinesPageState extends State { headlines: state.headlines, hasMore: state.headlinesHasMore, l10n: l10n, - isMobile: isMobile, // Pass isMobile to data source + isMobile: isMobile, ), rowsPerPage: kDefaultRowsPerPage, availableRowsPerPage: const [kDefaultRowsPerPage], @@ -195,14 +195,14 @@ class _HeadlinesDataSource extends DataTableSource { required this.headlines, required this.hasMore, required this.l10n, - required this.isMobile, // New parameter + required this.isMobile, }); final BuildContext context; final List headlines; final bool hasMore; final AppLocalizations l10n; - final bool isMobile; // New parameter + final bool isMobile; @override DataRow? getRow(int index) { diff --git a/lib/content_management/view/topics_page.dart b/lib/content_management/view/topics_page.dart index d1a6bebd..e0c03adc 100644 --- a/lib/content_management/view/topics_page.dart +++ b/lib/content_management/view/topics_page.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/content_management_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/topics_filter/topics_filter_bloc.dart'; -import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/widgets/content_action_buttons.dart'; // Import the new widget +import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/widgets/content_action_buttons.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.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'; @@ -136,7 +136,7 @@ class _TopicPageState extends State { topics: state.topics, hasMore: state.topicsHasMore, l10n: l10n, - isMobile: isMobile, // Pass isMobile to data source + isMobile: isMobile, ), rowsPerPage: kDefaultRowsPerPage, availableRowsPerPage: const [kDefaultRowsPerPage], @@ -185,14 +185,14 @@ class _TopicsDataSource extends DataTableSource { required this.topics, required this.hasMore, required this.l10n, - required this.isMobile, // New parameter + required this.isMobile, }); final BuildContext context; final List topics; final bool hasMore; final AppLocalizations l10n; - final bool isMobile; // New parameter + final bool isMobile; @override DataRow? getRow(int index) { diff --git a/lib/content_management/widgets/content_action_buttons.dart b/lib/content_management/widgets/content_action_buttons.dart index dd9a7285..b65f4b71 100644 --- a/lib/content_management/widgets/content_action_buttons.dart +++ b/lib/content_management/widgets/content_action_buttons.dart @@ -47,7 +47,7 @@ class ContentActionButtons extends StatelessWidget { itemId = (item as Source).id; status = (item as Source).status; } else { - return const SizedBox.shrink(); // Should not happen with current FeedItem types + return const SizedBox.shrink(); } // Action 1: Edit (always visible as the first action) diff --git a/lib/content_management/widgets/filter_dialog/filter_dialog.dart b/lib/content_management/widgets/filter_dialog/filter_dialog.dart index 06d20d1c..a71f7eca 100644 --- a/lib/content_management/widgets/filter_dialog/filter_dialog.dart +++ b/lib/content_management/widgets/filter_dialog/filter_dialog.dart @@ -389,7 +389,7 @@ class _FilterDialogState extends State { ], ); case ContentManagementTab.topics: - return const SizedBox.shrink(); // No additional filters for topics + return const SizedBox.shrink(); case ContentManagementTab.sources: return Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/local_ads_management/bloc/create_local_ads/create_local_banner_ad_bloc.dart b/lib/local_ads_management/bloc/create_local_ads/create_local_banner_ad_bloc.dart index c20e3985..daef1e7a 100644 --- a/lib/local_ads_management/bloc/create_local_ads/create_local_banner_ad_bloc.dart +++ b/lib/local_ads_management/bloc/create_local_ads/create_local_banner_ad_bloc.dart @@ -52,7 +52,7 @@ class CreateLocalBannerAdBloc targetUrl: state.targetUrl, createdAt: now, updatedAt: now, - status: ContentStatus.active, // Set status to active on creation + status: ContentStatus.active, ); await _localAdsRepository.create(item: newLocalBannerAd); diff --git a/lib/local_ads_management/bloc/create_local_ads/create_local_interstitial_ad_bloc.dart b/lib/local_ads_management/bloc/create_local_ads/create_local_interstitial_ad_bloc.dart index 93c7bb63..4694d3ac 100644 --- a/lib/local_ads_management/bloc/create_local_ads/create_local_interstitial_ad_bloc.dart +++ b/lib/local_ads_management/bloc/create_local_ads/create_local_interstitial_ad_bloc.dart @@ -53,7 +53,7 @@ class CreateLocalInterstitialAdBloc targetUrl: state.targetUrl, createdAt: now, updatedAt: now, - status: ContentStatus.active, // Set status to active on creation + status: ContentStatus.active, ); await _localAdsRepository.create(item: newLocalInterstitialAd); diff --git a/lib/local_ads_management/bloc/create_local_ads/create_local_native_ad_bloc.dart b/lib/local_ads_management/bloc/create_local_ads/create_local_native_ad_bloc.dart index 645e8618..5c1c929c 100644 --- a/lib/local_ads_management/bloc/create_local_ads/create_local_native_ad_bloc.dart +++ b/lib/local_ads_management/bloc/create_local_ads/create_local_native_ad_bloc.dart @@ -70,7 +70,7 @@ class CreateLocalNativeAdBloc targetUrl: state.targetUrl, createdAt: now, updatedAt: now, - status: ContentStatus.active, // Set status to active on creation + status: ContentStatus.active, ); await _localAdsRepository.create(item: newLocalNativeAd); diff --git a/lib/local_ads_management/bloc/create_local_ads/create_local_video_ad_bloc.dart b/lib/local_ads_management/bloc/create_local_ads/create_local_video_ad_bloc.dart index 93cc1d19..c31217fe 100644 --- a/lib/local_ads_management/bloc/create_local_ads/create_local_video_ad_bloc.dart +++ b/lib/local_ads_management/bloc/create_local_ads/create_local_video_ad_bloc.dart @@ -52,7 +52,7 @@ class CreateLocalVideoAdBloc targetUrl: state.targetUrl, createdAt: now, updatedAt: now, - status: ContentStatus.active, // Set status to active on creation + status: ContentStatus.active, ); await _localAdsRepository.create(item: newLocalVideoAd); diff --git a/lib/local_ads_management/bloc/update_local_ads/update_local_banner_ad_bloc.dart b/lib/local_ads_management/bloc/update_local_ads/update_local_banner_ad_bloc.dart index 0702b979..e8e681a0 100644 --- a/lib/local_ads_management/bloc/update_local_ads/update_local_banner_ad_bloc.dart +++ b/lib/local_ads_management/bloc/update_local_ads/update_local_banner_ad_bloc.dart @@ -86,7 +86,7 @@ class UpdateLocalBannerAdBloc imageUrl: state.imageUrl, targetUrl: state.targetUrl, updatedAt: now, - status: state.initialAd!.status, // Maintain original status + status: state.initialAd!.status, ); await _localAdsRepository.update( diff --git a/lib/local_ads_management/bloc/update_local_ads/update_local_interstitial_ad_bloc.dart b/lib/local_ads_management/bloc/update_local_ads/update_local_interstitial_ad_bloc.dart index d7b2b522..2aedd804 100644 --- a/lib/local_ads_management/bloc/update_local_ads/update_local_interstitial_ad_bloc.dart +++ b/lib/local_ads_management/bloc/update_local_ads/update_local_interstitial_ad_bloc.dart @@ -90,7 +90,7 @@ class UpdateLocalInterstitialAdBloc imageUrl: state.imageUrl, targetUrl: state.targetUrl, updatedAt: now, - status: state.initialAd!.status, // Maintain original status + status: state.initialAd!.status, ); await _localAdsRepository.update( diff --git a/lib/local_ads_management/bloc/update_local_ads/update_local_native_ad_bloc.dart b/lib/local_ads_management/bloc/update_local_ads/update_local_native_ad_bloc.dart index 28f696f6..22f3de20 100644 --- a/lib/local_ads_management/bloc/update_local_ads/update_local_native_ad_bloc.dart +++ b/lib/local_ads_management/bloc/update_local_ads/update_local_native_ad_bloc.dart @@ -106,7 +106,7 @@ class UpdateLocalNativeAdBloc imageUrl: state.imageUrl, targetUrl: state.targetUrl, updatedAt: now, - status: state.initialAd!.status, // Maintain original status + status: state.initialAd!.status, ); await _localAdsRepository.update( diff --git a/lib/local_ads_management/bloc/update_local_ads/update_local_video_ad_bloc.dart b/lib/local_ads_management/bloc/update_local_ads/update_local_video_ad_bloc.dart index c99ecd69..6818ed22 100644 --- a/lib/local_ads_management/bloc/update_local_ads/update_local_video_ad_bloc.dart +++ b/lib/local_ads_management/bloc/update_local_ads/update_local_video_ad_bloc.dart @@ -86,7 +86,7 @@ class UpdateLocalVideoAdBloc videoUrl: state.videoUrl, targetUrl: state.targetUrl, updatedAt: now, - status: state.initialAd!.status, // Maintain original status + status: state.initialAd!.status, ); await _localAdsRepository.update( diff --git a/lib/local_ads_management/view/local_ads_management_page.dart b/lib/local_ads_management/view/local_ads_management_page.dart index 29f1f2a6..565e67d9 100644 --- a/lib/local_ads_management/view/local_ads_management_page.dart +++ b/lib/local_ads_management/view/local_ads_management_page.dart @@ -8,7 +8,6 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localiz import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/bloc/local_ads_management_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart'; -// Added import import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/extensions.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; diff --git a/lib/router/router.dart b/lib/router/router.dart index af67c388..8cf58262 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -1,5 +1,5 @@ import 'package:auth_repository/auth_repository.dart'; -import 'package:core/core.dart' hide AppStatus; // Hide AppStatus from core.dart +import 'package:core/core.dart' hide AppStatus; import 'package:data_repository/data_repository.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/settings/view/settings_page.dart b/lib/settings/view/settings_page.dart index c3c8f9b2..76fff8f2 100644 --- a/lib/settings/view/settings_page.dart +++ b/lib/settings/view/settings_page.dart @@ -66,7 +66,7 @@ class _SettingsViewState extends State<_SettingsView> { Text(l10n.settings), const SizedBox( width: AppSpacing.sm, - ), // Spacing between title and icon + ), AboutIcon( dialogTitle: l10n.settings, dialogDescription: l10n.settingsPageDescription, diff --git a/lib/shared/widgets/searchable_selection_input.dart b/lib/shared/widgets/searchable_selection_input.dart index b1eb139a..b96ed750 100644 --- a/lib/shared/widgets/searchable_selection_input.dart +++ b/lib/shared/widgets/searchable_selection_input.dart @@ -26,15 +26,15 @@ class SearchableSelectionInput extends StatefulWidget { required this.itemBuilder, required this.itemToString, required this.onChanged, - this.selectedItems, // Changed to List? + this.selectedItems, this.repository, this.filterBuilder, this.sortOptions, this.limit, this.staticItems, this.includeInactiveSelectedItem = false, - this.isMultiSelect = false, // New parameter - this.hintText, // New parameter + this.isMultiSelect = false, + this.hintText, super.key, }) : assert( (repository != null && @@ -49,7 +49,7 @@ class SearchableSelectionInput extends StatefulWidget { final String label; /// The currently selected item(s). - final List? selectedItems; // Changed to List? + final List? selectedItems; /// If true, the [selectedItems] will be included in the fetched results /// even if they do not match the current filter criteria (e.g., if they are @@ -66,7 +66,7 @@ class SearchableSelectionInput extends StatefulWidget { final String Function(T item) itemToString; /// Callback when item(s) are selected or cleared. - final ValueChanged?> onChanged; // Changed to ValueChanged?> + final ValueChanged?> onChanged; /// The [DataRepository] to use for fetching items (if not using static items). /// The generic type of the repository must match [T]. @@ -175,7 +175,7 @@ class _SearchableSelectionInputState if (result != null) { widget.onChanged(result.cast()); } else { - widget.onChanged(null); // Clear selection if nothing was chosen + widget.onChanged(null); } } @@ -187,7 +187,7 @@ class _SearchableSelectionInputState readOnly: true, decoration: InputDecoration( labelText: widget.label, - hintText: widget.hintText, // Use new hintText parameter + hintText: widget.hintText, border: Theme.of(context).inputDecorationTheme.border, suffixIcon: IconButton( icon: const Icon(Icons.arrow_drop_down), diff --git a/lib/shared/widgets/selection_page/bloc/searchable_selection_state.dart b/lib/shared/widgets/selection_page/bloc/searchable_selection_state.dart index a59c1b12..c109a000 100644 --- a/lib/shared/widgets/selection_page/bloc/searchable_selection_state.dart +++ b/lib/shared/widgets/selection_page/bloc/searchable_selection_state.dart @@ -23,7 +23,7 @@ final class SearchableSelectionState extends Equatable { const SearchableSelectionState({ this.status = SearchableSelectionStatus.initial, this.items = const [], - this.selectedItems = const [], // Changed to List + this.selectedItems = const [], this.searchTerm = '', this.cursor, this.hasMore = true, @@ -37,7 +37,7 @@ final class SearchableSelectionState extends Equatable { final List items; /// The currently selected items. - final List selectedItems; // Changed to List + final List selectedItems; /// The current search term applied to the items. final String searchTerm; @@ -55,7 +55,7 @@ final class SearchableSelectionState extends Equatable { SearchableSelectionState copyWith({ SearchableSelectionStatus? status, List? items, - List? selectedItems, // Changed to List + List? selectedItems, String? searchTerm, String? cursor, bool? hasMore, @@ -64,7 +64,7 @@ final class SearchableSelectionState extends Equatable { return SearchableSelectionState( status: status ?? this.status, items: items ?? this.items, - selectedItems: selectedItems ?? this.selectedItems, // Updated + selectedItems: selectedItems ?? this.selectedItems, searchTerm: searchTerm ?? this.searchTerm, cursor: cursor ?? this.cursor, hasMore: hasMore ?? this.hasMore, @@ -76,7 +76,7 @@ final class SearchableSelectionState extends Equatable { List get props => [ status, items, - selectedItems, // Updated + selectedItems, searchTerm, cursor, hasMore, diff --git a/lib/shared/widgets/selection_page/searchable_selection_page.dart b/lib/shared/widgets/selection_page/searchable_selection_page.dart index fe9cf6d2..efde10b8 100644 --- a/lib/shared/widgets/selection_page/searchable_selection_page.dart +++ b/lib/shared/widgets/selection_page/searchable_selection_page.dart @@ -129,7 +129,7 @@ class _SearchableSelectionViewState extends State<_SearchableSelectionView> { if (widget.arguments.isMultiSelect) IconButton( icon: const Icon(Icons.check), - tooltip: l10n.apply, // Assuming l10n.apply exists + tooltip: l10n.apply, onPressed: () { // Return the locally managed selected items. Navigator.of(context).pop(_tempSelectedItems); diff --git a/lib/shared/widgets/selection_page/selection_page_arguments.dart b/lib/shared/widgets/selection_page/selection_page_arguments.dart index d85cb2e8..fd3c9067 100644 --- a/lib/shared/widgets/selection_page/selection_page_arguments.dart +++ b/lib/shared/widgets/selection_page/selection_page_arguments.dart @@ -31,9 +31,9 @@ class SelectionPageArguments extends Equatable { this.sortOptions, this.limit, this.staticItems, - this.initialSelectedItems, // Changed to List? + this.initialSelectedItems, this.includeInactiveSelectedItem = false, - this.isMultiSelect = false, // New parameter + this.isMultiSelect = false, }) : assert( (repository != null && filterBuilder != null && @@ -81,7 +81,7 @@ class SelectionPageArguments extends Equatable { /// The items that are initially selected when the page opens. /// The items are of type [Object] and must be cast to [itemType] before use. - final List? initialSelectedItems; // Changed to List? + final List? initialSelectedItems; /// If true, the [initialSelectedItems] will be included in the fetched results /// even if they do not match the current filter criteria (e.g., if they are @@ -91,7 +91,7 @@ class SelectionPageArguments extends Equatable { /// If true, the selection page will allow multiple items to be selected. /// Defaults to false for single selection. - final bool isMultiSelect; // New parameter + final bool isMultiSelect; @override List get props => [ @@ -104,8 +104,8 @@ class SelectionPageArguments extends Equatable { sortOptions, limit, staticItems, - initialSelectedItems, // Updated + initialSelectedItems, includeInactiveSelectedItem, - isMultiSelect, // New parameter + isMultiSelect, ]; } From 02502fb1a3dc929046b835c8f96b1171872bb644 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 21:54:01 +0100 Subject: [PATCH 23/26] style(content_management): decrease column spacing in headlines page - Change column spacing from AppSpacing.md to AppSpacing.sm in the DataTable widget --- lib/content_management/view/headlines_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/content_management/view/headlines_page.dart b/lib/content_management/view/headlines_page.dart index e22e3d5e..54f4703e 100644 --- a/lib/content_management/view/headlines_page.dart +++ b/lib/content_management/view/headlines_page.dart @@ -175,7 +175,7 @@ class _HeadlinesPageState extends State { fit: FlexFit.tight, headingRowHeight: 56, dataRowHeight: 56, - columnSpacing: AppSpacing.md, + columnSpacing: AppSpacing.sm, horizontalMargin: AppSpacing.md, ); }, From e51691fb8f9beb7226f46455c05511689cc91ef5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 21:54:10 +0100 Subject: [PATCH 24/26] style(content_management): improve layout and spacing of action buttons - Add visualDensity and iconSize to Edit button for better appearance - Wrap PopupMenuButton in SizedBox to adjust width and prevent layout overflow - Reduce iconSize of overflow button for consistency with Edit button --- .../widgets/content_action_buttons.dart | 134 +++++++++--------- 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/lib/content_management/widgets/content_action_buttons.dart b/lib/content_management/widgets/content_action_buttons.dart index b65f4b71..e72e2742 100644 --- a/lib/content_management/widgets/content_action_buttons.dart +++ b/lib/content_management/widgets/content_action_buttons.dart @@ -53,6 +53,8 @@ class ContentActionButtons extends StatelessWidget { // Action 1: Edit (always visible as the first action) visibleActions.add( IconButton( + visualDensity: VisualDensity.compact, + iconSize: 20, icon: const Icon(Icons.edit), tooltip: l10n.edit, onPressed: () { @@ -152,70 +154,74 @@ class ContentActionButtons extends StatelessWidget { // (publish/archive/restore for draft/active/archived, and delete for draft/archived headlines). // So, we will always show the ellipsis. visibleActions.add( - PopupMenuButton( - icon: const Icon(Icons.more_vert), - tooltip: l10n.moreActions, - onSelected: (value) { - switch (value) { - case 'publish': - if (item is Headline) { - context.read().add( - PublishHeadlineRequested(itemId), - ); - } else if (item is Topic) { - context.read().add( - PublishTopicRequested(itemId), - ); - } else if (item is Source) { - context.read().add( - PublishSourceRequested(itemId), - ); - } - case 'archive': - if (item is Headline) { - context.read().add( - ArchiveHeadlineRequested(itemId), - ); - } else if (item is Topic) { - context.read().add( - ArchiveTopicRequested(itemId), - ); - } else if (item is Source) { - context.read().add( - ArchiveSourceRequested(itemId), - ); - } - case 'restore': - if (item is Headline) { - context.read().add( - RestoreHeadlineRequested(itemId), - ); - } else if (item is Topic) { - context.read().add( - RestoreTopicRequested(itemId), - ); - } else if (item is Source) { - context.read().add( - RestoreSourceRequested(itemId), - ); - } - case 'delete': - if (item is Headline) { - context.read().add( - DeleteHeadlineForeverRequested(itemId), - ); - } else if (item is Topic) { - context.read().add( - DeleteTopicForeverRequested(itemId), - ); - } else if (item is Source) { - context.read().add( - DeleteSourceForeverRequested(itemId), - ); - } - } - }, - itemBuilder: (BuildContext context) => overflowMenuItems, + SizedBox( + width: 32, + child: PopupMenuButton( + iconSize: 20, + icon: const Icon(Icons.more_vert), + tooltip: l10n.moreActions, + onSelected: (value) { + switch (value) { + case 'publish': + if (item is Headline) { + context.read().add( + PublishHeadlineRequested(itemId), + ); + } else if (item is Topic) { + context.read().add( + PublishTopicRequested(itemId), + ); + } else if (item is Source) { + context.read().add( + PublishSourceRequested(itemId), + ); + } + case 'archive': + if (item is Headline) { + context.read().add( + ArchiveHeadlineRequested(itemId), + ); + } else if (item is Topic) { + context.read().add( + ArchiveTopicRequested(itemId), + ); + } else if (item is Source) { + context.read().add( + ArchiveSourceRequested(itemId), + ); + } + case 'restore': + if (item is Headline) { + context.read().add( + RestoreHeadlineRequested(itemId), + ); + } else if (item is Topic) { + context.read().add( + RestoreTopicRequested(itemId), + ); + } else if (item is Source) { + context.read().add( + RestoreSourceRequested(itemId), + ); + } + case 'delete': + if (item is Headline) { + context.read().add( + DeleteHeadlineForeverRequested(itemId), + ); + } else if (item is Topic) { + context.read().add( + DeleteTopicForeverRequested(itemId), + ); + } else if (item is Source) { + context.read().add( + DeleteSourceForeverRequested(itemId), + ); + } + } + }, + itemBuilder: (BuildContext context) => overflowMenuItems, + ), ), ); From ff69e0d88cc2ad7a102283d88cee56bf44d22841 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 22:13:39 +0100 Subject: [PATCH 25/26] style(content_management): adjust padding for filter pages - Change padding from all sides to only top - Update applied for headlines_page.dart, sources_page.dart, and topics_page.dart - Adjust padding value from AppSpacing.lg to AppSpacing.sm --- lib/content_management/view/headlines_page.dart | 2 +- lib/content_management/view/sources_page.dart | 2 +- lib/content_management/view/topics_page.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/content_management/view/headlines_page.dart b/lib/content_management/view/headlines_page.dart index 54f4703e..d7838a39 100644 --- a/lib/content_management/view/headlines_page.dart +++ b/lib/content_management/view/headlines_page.dart @@ -51,7 +51,7 @@ class _HeadlinesPageState extends State { Widget build(BuildContext context) { final l10n = AppLocalizationsX(context).l10n; return Padding( - padding: const EdgeInsets.all(AppSpacing.lg), + padding: const EdgeInsets.only(top: AppSpacing.sm), child: BlocBuilder( builder: (context, state) { final headlinesFilterState = context diff --git a/lib/content_management/view/sources_page.dart b/lib/content_management/view/sources_page.dart index 7e73544e..b0c4b8d0 100644 --- a/lib/content_management/view/sources_page.dart +++ b/lib/content_management/view/sources_page.dart @@ -52,7 +52,7 @@ class _SourcesPageState extends State { Widget build(BuildContext context) { final l10n = AppLocalizationsX(context).l10n; return Padding( - padding: const EdgeInsets.all(AppSpacing.lg), + padding: const EdgeInsets.only(top: AppSpacing.sm), child: BlocBuilder( builder: (context, state) { final sourcesFilterState = context.watch().state; diff --git a/lib/content_management/view/topics_page.dart b/lib/content_management/view/topics_page.dart index e0c03adc..049b9f8c 100644 --- a/lib/content_management/view/topics_page.dart +++ b/lib/content_management/view/topics_page.dart @@ -48,7 +48,7 @@ class _TopicPageState extends State { Widget build(BuildContext context) { final l10n = AppLocalizationsX(context).l10n; return Padding( - padding: const EdgeInsets.all(AppSpacing.lg), + padding: const EdgeInsets.only(top: AppSpacing.sm), child: BlocBuilder( builder: (context, state) { final topicsFilterState = context.watch().state; From 016306cce925bb0470b7cf2483521d8a43207226 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 25 Sep 2025 22:14:50 +0100 Subject: [PATCH 26/26] style(spacing): adjust spacing between title and About icon - Change SizedBox width from AppSpacing.sm to AppSpacing.xs in multiple files: - app_configuration_page.dart - content_management_page.dart - settings_page.dart --- lib/app_configuration/view/app_configuration_page.dart | 2 +- lib/content_management/view/content_management_page.dart | 2 +- lib/settings/view/settings_page.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/app_configuration/view/app_configuration_page.dart b/lib/app_configuration/view/app_configuration_page.dart index f42c8603..20281e82 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -52,7 +52,7 @@ class _AppConfigurationPageState extends State l10n.appConfigurationPageTitle, ), const SizedBox( - width: AppSpacing.sm, + width: AppSpacing.xs, ), AboutIcon( dialogTitle: l10n.appConfigurationPageTitle, diff --git a/lib/content_management/view/content_management_page.dart b/lib/content_management/view/content_management_page.dart index 951b602f..fecb7144 100644 --- a/lib/content_management/view/content_management_page.dart +++ b/lib/content_management/view/content_management_page.dart @@ -183,7 +183,7 @@ class _ContentManagementPageState extends State children: [ Text(l10n.contentManagement), const SizedBox( - width: AppSpacing.sm, + width: AppSpacing.xs, ), AboutIcon( dialogTitle: l10n.contentManagement, diff --git a/lib/settings/view/settings_page.dart b/lib/settings/view/settings_page.dart index 76fff8f2..44f140f2 100644 --- a/lib/settings/view/settings_page.dart +++ b/lib/settings/view/settings_page.dart @@ -65,7 +65,7 @@ class _SettingsViewState extends State<_SettingsView> { children: [ Text(l10n.settings), const SizedBox( - width: AppSpacing.sm, + width: AppSpacing.xs, ), AboutIcon( dialogTitle: l10n.settings,