From 045f3568cd44c483da0ab0eac46766df6ee863e1 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:14:47 +0100 Subject: [PATCH 01/30] feat(l10n): add tooltip translations for breaking news icon - Add Arabic and English translations for breaking news tooltip - Update app_ar.arb and app_en --- lib/l10n/arb/app_ar.arb | 4 ++++ lib/l10n/arb/app_en.arb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index cd58a70c..e719749b 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2736,5 +2736,9 @@ "publisherUserTooltip": "ناشر", "@publisherUserTooltip": { "description": "تلميح للأيقونة التي تشير إلى مستخدم ناشر." + }, + "breakingNewsHint": "هذا عنوان خبر عاجل", + "@breakingNewsHint": { + "description": "Tooltip text for the breaking news icon on a headline" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 028171e5..02505d94 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2732,5 +2732,9 @@ "publisherUserTooltip": "Publisher", "@publisherUserTooltip": { "description": "Tooltip for the icon indicating a publisher user." + }, + "breakingNewsHint": "This is a breaking news headline", + "@breakingNewsHint": { + "description": "Tooltip text for the breaking news icon on a headline" } } \ No newline at end of file From 3700515cd69313c9241e0ccf4bd4072efb03d91a Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:15:04 +0100 Subject: [PATCH 02/30] build(l10n): sync --- lib/l10n/app_localizations.dart | 6 ++++++ lib/l10n/app_localizations_ar.dart | 3 +++ lib/l10n/app_localizations_en.dart | 3 +++ 3 files changed, 12 insertions(+) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 427cd04c..3bf0d003 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -4039,6 +4039,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Publisher'** String get publisherUserTooltip; + + /// Tooltip text for the breaking news icon on a headline + /// + /// In en, this message translates to: + /// **'This is a breaking news headline'** + String get breakingNewsHint; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index a5514382..52094da2 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -2167,4 +2167,7 @@ class AppLocalizationsAr extends AppLocalizations { @override String get publisherUserTooltip => 'ناشر'; + + @override + String get breakingNewsHint => 'هذا عنوان خبر عاجل'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index ab2c3add..a158f8fb 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2173,4 +2173,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get publisherUserTooltip => 'Publisher'; + + @override + String get breakingNewsHint => 'This is a breaking news headline'; } From c69d9f0929cfb9cb0333637e86fa3873970693c5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:16:05 +0100 Subject: [PATCH 03/30] fix(content_management): improve breaking news display and accessibility - Replace Stack with RichText for better layout and performance - Add tooltip for breaking news icon to aid screen readers - Adjust icon size and spacing for better visual consistency - Ensure text and icon alignment across different screen sizes --- .../view/headlines_page.dart | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/lib/content_management/view/headlines_page.dart b/lib/content_management/view/headlines_page.dart index f4ba6b18..52d4d550 100644 --- a/lib/content_management/view/headlines_page.dart +++ b/lib/content_management/view/headlines_page.dart @@ -221,32 +221,30 @@ class _HeadlinesDataSource extends DataTableSource { }, cells: [ DataCell( - Stack( - alignment: AlignmentDirectional.centerStart, - children: [ - // Add padding to the text to make space for the icon only when - // the headline is breaking news. This ensures all titles align - // vertically regardless of the icon's presence. - Padding( - padding: EdgeInsetsDirectional.only( - start: headline.isBreaking - ? AppSpacing.xl + AppSpacing.xs - : 0, - ), - child: Text( - headline.title, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ), - // Conditionally display the icon at the start of the cell. - if (headline.isBreaking) - Icon( - Icons.flash_on, - size: 18, - color: Theme.of(context).colorScheme.primary, - ), - ], + RichText( + maxLines: 2, + overflow: TextOverflow.ellipsis, + text: TextSpan( + style: Theme.of(context).textTheme.bodyMedium, + children: [ + TextSpan(text: headline.title), + if (headline.isBreaking) + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Padding( + padding: const EdgeInsets.only(left: AppSpacing.xs), + child: Tooltip( + message: l10n.breakingNewsHint, + child: Icon( + Icons.flash_on, + size: 14, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ), + ], + ), ), ), if (!isMobile) // Conditionally show Source Name From 1201b0b1d56dc68d2a69d365c474df68d90bee34 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:32:50 +0100 Subject: [PATCH 04/30] feat(l10n): add translations for breaking news filter description - Add Arabic and English translations for the breaking news filter description - Extend app_ar.arb and app_en.arb files with new subtitle for the breaking news filter switch --- lib/l10n/arb/app_ar.arb | 4 ++++ lib/l10n/arb/app_en.arb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index e719749b..a1e40536 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2740,5 +2740,9 @@ "breakingNewsHint": "هذا عنوان خبر عاجل", "@breakingNewsHint": { "description": "Tooltip text for the breaking news icon on a headline" + }, + "breakingNewsFilterDescription": "إظهار عناوين الأخبار العاجلة فقط", + "@breakingNewsFilterDescription": { + "description": "Subtitle for the breaking news filter switch" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 02505d94..8f769359 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2736,5 +2736,9 @@ "breakingNewsHint": "This is a breaking news headline", "@breakingNewsHint": { "description": "Tooltip text for the breaking news icon on a headline" + }, + "breakingNewsFilterDescription": "Show only breaking news headlines", + "@breakingNewsFilterDescription": { + "description": "Subtitle for the breaking news filter switch" } } \ No newline at end of file From 27a47ff8ca6bb8ab6d0fe44a1bb1eedd76b205b6 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:33:06 +0100 Subject: [PATCH 05/30] build(l10n): sync --- lib/l10n/app_localizations.dart | 6 ++++++ lib/l10n/app_localizations_ar.dart | 4 ++++ lib/l10n/app_localizations_en.dart | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 3bf0d003..8cdc0d22 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -4045,6 +4045,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'This is a breaking news headline'** String get breakingNewsHint; + + /// Subtitle for the breaking news filter switch + /// + /// In en, this message translates to: + /// **'Show only breaking news headlines'** + String get breakingNewsFilterDescription; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 52094da2..2b74727f 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -2170,4 +2170,8 @@ class AppLocalizationsAr extends AppLocalizations { @override String get breakingNewsHint => 'هذا عنوان خبر عاجل'; + + @override + String get breakingNewsFilterDescription => + 'إظهار عناوين الأخبار العاجلة فقط'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index a158f8fb..e4ac5218 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2176,4 +2176,8 @@ class AppLocalizationsEn extends AppLocalizations { @override String get breakingNewsHint => 'This is a breaking news headline'; + + @override + String get breakingNewsFilterDescription => + 'Show only breaking news headlines'; } From 8b350341f0f9cd12748248357c436bf908f02385 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:35:16 +0100 Subject: [PATCH 06/30] refactor(content_management): simplify breaking news filter flag - Replace `BreakingNewsFilterStatus` enum with a simple boolean `isBreaking` flag - Update default value from `BreakingNewsFilterStatus.all` to `false` - Adjust property and constructor parameter names accordingly --- .../bloc/headlines_filter/headlines_filter_state.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/content_management/bloc/headlines_filter/headlines_filter_state.dart b/lib/content_management/bloc/headlines_filter/headlines_filter_state.dart index dcbb9e5e..9247da78 100644 --- a/lib/content_management/bloc/headlines_filter/headlines_filter_state.dart +++ b/lib/content_management/bloc/headlines_filter/headlines_filter_state.dart @@ -15,7 +15,7 @@ class HeadlinesFilterState extends Equatable { this.selectedSourceIds = const [], this.selectedTopicIds = const [], this.selectedCountryIds = const [], - this.isBreaking = BreakingNewsFilterStatus.all, + this.isBreaking = false, }); /// The current text in the search query field. @@ -33,9 +33,8 @@ class HeadlinesFilterState extends Equatable { /// The list of country IDs to be included in the filter. final List selectedCountryIds; - /// The breaking news status to filter by. - /// `null` = all, `true` = breaking only, `false` = non-breaking only. - final BreakingNewsFilterStatus isBreaking; + /// A flag to filter for breaking news only. + final bool isBreaking; /// Creates a copy of this state with the given fields replaced with the /// new values. @@ -45,7 +44,7 @@ class HeadlinesFilterState extends Equatable { List? selectedSourceIds, List? selectedTopicIds, List? selectedCountryIds, - BreakingNewsFilterStatus? isBreaking, + bool? isBreaking, }) { return HeadlinesFilterState( searchQuery: searchQuery ?? this.searchQuery, From 09a2c46b0e49d74cc495c0113f42d91b809be614 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:35:34 +0100 Subject: [PATCH 07/30] refactor(content_management): update breaking news filter handling - Change HeadlinesBreakingNewsFilterChanged to use bool instead of BreakingNewsFilterStatus - Update HeadlinesFilterApplied to use bool for isBreaking - Simplify props list in both events --- .../bloc/headlines_filter/headlines_filter_event.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/content_management/bloc/headlines_filter/headlines_filter_event.dart b/lib/content_management/bloc/headlines_filter/headlines_filter_event.dart index abd400b1..9bf28d1e 100644 --- a/lib/content_management/bloc/headlines_filter/headlines_filter_event.dart +++ b/lib/content_management/bloc/headlines_filter/headlines_filter_event.dart @@ -62,10 +62,10 @@ final class HeadlinesCountryFilterChanged extends HeadlinesFilterEvent { final class HeadlinesBreakingNewsFilterChanged extends HeadlinesFilterEvent { const HeadlinesBreakingNewsFilterChanged(this.isBreaking); - final BreakingNewsFilterStatus isBreaking; + final bool isBreaking; @override - List get props => [isBreaking]; + List get props => [isBreaking]; } /// Event to request applying all current filters. @@ -84,7 +84,7 @@ final class HeadlinesFilterApplied extends HeadlinesFilterEvent { final List selectedSourceIds; final List selectedTopicIds; final List selectedCountryIds; - final BreakingNewsFilterStatus isBreaking; + final bool isBreaking; @override List get props => [ From 4397dbb33cd05887e3d9d36f8f0b60cc18840f75 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:35:57 +0100 Subject: [PATCH 08/30] refactor(content): simplify breaking news filter logic perf- Remove(content_management unused Breaking): optimizeNewsFilter breaking newsStatus filter logic enum -- Simpl Simplifyify is theBreaking breaking filter news logic filter to by only removing include unused ' enumtrue values' condition- Improve- query Improve performance code by readability only and adding ' reduceis complexityBreaking ' --- .../bloc/content_management_bloc.dart | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/content_management/bloc/content_management_bloc.dart b/lib/content_management/bloc/content_management_bloc.dart index a312c5c4..b133e2b1 100644 --- a/lib/content_management/bloc/content_management_bloc.dart +++ b/lib/content_management/bloc/content_management_bloc.dart @@ -7,7 +7,6 @@ import 'package:equatable/equatable.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/bloc/sources_filter/sources_filter_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/models/breaking_news_filter_status.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/constants/app_constants.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/services/pending_deletions_service.dart'; import 'package:ui_kit/ui_kit.dart'; @@ -154,15 +153,9 @@ class ContentManagementBloc filter['eventCountry.id'] = {r'$in': state.selectedCountryIds}; } - // Handle the breaking news filter based on the enum status. - switch (state.isBreaking) { - case BreakingNewsFilterStatus.breakingOnly: - filter['isBreaking'] = true; - case BreakingNewsFilterStatus.nonBreakingOnly: - filter['isBreaking'] = false; - case BreakingNewsFilterStatus.all: - // For 'all', we don't add the 'isBreaking' key to the filter. - break; + // If the breaking news filter is active, add it to the query. + if (state.isBreaking) { + filter['isBreaking'] = true; } return filter; From 75e97d24a9c16bb111323e6a48e2ec0129311d52 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:36:06 +0100 Subject: [PATCH 09/30] refactor(content_management): simplify breaking news filter logic - Change `isBreaking` from BreakingNewsFilterStatus to bool - Update default value from BreakingNewsFilterStatus.all to false - Remove outdated comments explaining the previous enum-based logic --- .../widgets/filter_dialog/bloc/filter_dialog_state.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/content_management/widgets/filter_dialog/bloc/filter_dialog_state.dart b/lib/content_management/widgets/filter_dialog/bloc/filter_dialog_state.dart index 3373615e..376b7184 100644 --- a/lib/content_management/widgets/filter_dialog/bloc/filter_dialog_state.dart +++ b/lib/content_management/widgets/filter_dialog/bloc/filter_dialog_state.dart @@ -30,7 +30,7 @@ final class FilterDialogState extends Equatable { this.selectedSourceIds = const [], this.selectedTopicIds = const [], this.selectedCountryIds = const [], - this.isBreaking = BreakingNewsFilterStatus.all, + this.isBreaking = false, this.selectedSourceTypes = const [], this.selectedLanguageCodes = const [], this.selectedHeadquartersCountryIds = const [], @@ -67,9 +67,8 @@ final class FilterDialogState extends Equatable { /// The list of country IDs to be included in the filter for headlines. final List selectedCountryIds; - /// The breaking news status to filter by for headlines. - /// `null` = all, `true` = breaking only, `false` = non-breaking only. - final BreakingNewsFilterStatus isBreaking; + /// A flag to filter for breaking news only for headlines. + final bool isBreaking; /// The list of source types to be included in the filter for sources. final List selectedSourceTypes; @@ -103,7 +102,7 @@ final class FilterDialogState extends Equatable { List? selectedSourceIds, List? selectedTopicIds, List? selectedCountryIds, - BreakingNewsFilterStatus? isBreaking, + bool? isBreaking, List? selectedSourceTypes, List? selectedLanguageCodes, List? selectedHeadquartersCountryIds, From 3aba00d00afd5eb88f2cf8bf1554ef08a5761baa Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:36:21 +0100 Subject: [PATCH 10/30] fix(filter-dialog): change type of isBreaking to bool - Update type of isBreaking parameter in FilterDialogBreakingNewsChanged event from BreakingNewsFilterStatus to bool --- .../widgets/filter_dialog/bloc/filter_dialog_event.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/content_management/widgets/filter_dialog/bloc/filter_dialog_event.dart b/lib/content_management/widgets/filter_dialog/bloc/filter_dialog_event.dart index eac566e5..4c1cd6b9 100644 --- a/lib/content_management/widgets/filter_dialog/bloc/filter_dialog_event.dart +++ b/lib/content_management/widgets/filter_dialog/bloc/filter_dialog_event.dart @@ -90,7 +90,7 @@ final class FilterDialogHeadlinesCountryIdsChanged extends FilterDialogEvent { final class FilterDialogBreakingNewsChanged extends FilterDialogEvent { const FilterDialogBreakingNewsChanged(this.isBreaking); - final BreakingNewsFilterStatus isBreaking; + final bool isBreaking; @override List get props => [isBreaking]; From 1f1aecaaf10168b508e9f32420957df9d3d3c6f3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:36:48 +0100 Subject: [PATCH 11/30] fix(content_management): update filter dialog reset functionality - Remove unused import statement - Modify reset logic to set isBreaking to false - Improve code readability by using copyWith method --- .../widgets/filter_dialog/bloc/filter_dialog_bloc.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/content_management/widgets/filter_dialog/bloc/filter_dialog_bloc.dart b/lib/content_management/widgets/filter_dialog/bloc/filter_dialog_bloc.dart index 036f6ab7..2ba7ebcd 100644 --- a/lib/content_management/widgets/filter_dialog/bloc/filter_dialog_bloc.dart +++ b/lib/content_management/widgets/filter_dialog/bloc/filter_dialog_bloc.dart @@ -9,7 +9,6 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/content_manageme 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/bloc/sources_filter/sources_filter_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/models/breaking_news_filter_status.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/widgets/filter_dialog/filter_dialog.dart' show FilterDialog; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/constants/constants.dart'; @@ -263,6 +262,8 @@ class FilterDialogBloc extends Bloc { FilterDialogReset event, Emitter emit, ) { - emit(FilterDialogState(activeTab: state.activeTab)); + emit( + FilterDialogState(activeTab: state.activeTab).copyWith(isBreaking: false), + ); } } From 8f1b727420053ff9b4caa2001ffa9b4dc5a6d2c8 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:37:00 +0100 Subject: [PATCH 12/30] refactor(content_management): simplify headlines filter logic - Remove unused import of BreakingNewsFilterStatus - Simplify isBreaking filter condition - Update comments and documentation --- .../bloc/headlines_filter/headlines_filter_bloc.dart | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/content_management/bloc/headlines_filter/headlines_filter_bloc.dart b/lib/content_management/bloc/headlines_filter/headlines_filter_bloc.dart index 46e70ffe..fbd19b02 100644 --- a/lib/content_management/bloc/headlines_filter/headlines_filter_bloc.dart +++ b/lib/content_management/bloc/headlines_filter/headlines_filter_bloc.dart @@ -1,7 +1,6 @@ import 'package:bloc/bloc.dart'; import 'package:core/core.dart'; import 'package:equatable/equatable.dart'; -import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/models/breaking_news_filter_status.dart'; part 'headlines_filter_event.dart'; part 'headlines_filter_state.dart'; @@ -79,8 +78,7 @@ class HeadlinesFilterBloc /// Handles changes to the breaking news filter. /// - /// This updates the `isBreaking` status for the filter using the - /// [BreakingNewsFilterStatus] enum. + /// This updates the `isBreaking` status for the filter. void _onHeadlinesBreakingNewsFilterChanged( HeadlinesBreakingNewsFilterChanged event, Emitter emit, @@ -139,9 +137,8 @@ class HeadlinesFilterBloc if (state.selectedCountryIds.isNotEmpty) { filter['eventCountry.id'] = {r'$in': state.selectedCountryIds}; } - if (state.isBreaking != BreakingNewsFilterStatus.all) { - filter['isBreaking'] = - state.isBreaking == BreakingNewsFilterStatus.breakingOnly; + if (state.isBreaking) { + filter['isBreaking'] = true; } return filter; From 6002bd2f319d4035f264b0d0b4358f190a2825b3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:37:12 +0100 Subject: [PATCH 13/30] refactor(filter_dialog): replace BreakingNewsFilterStatus with boolean - Remove BreakingNewsFilterStatus enum - Replace with a simple boolean isBreaking - Update UI to use SwitchListTile instead of ChoiceChip - Update localization keys and remove unused ones - Adjust layout and spacing --- .../widgets/filter_dialog/filter_dialog.dart | 66 +++++-------------- 1 file changed, 17 insertions(+), 49 deletions(-) diff --git a/lib/content_management/widgets/filter_dialog/filter_dialog.dart b/lib/content_management/widgets/filter_dialog/filter_dialog.dart index d340802b..c4f5aa27 100644 --- a/lib/content_management/widgets/filter_dialog/filter_dialog.dart +++ b/lib/content_management/widgets/filter_dialog/filter_dialog.dart @@ -6,7 +6,6 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/content_manageme 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/bloc/sources_filter/sources_filter_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/models/breaking_news_filter_status.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/widgets/filter_dialog/bloc/filter_dialog_bloc.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'; @@ -135,7 +134,7 @@ class _FilterDialogState extends State { selectedSourceIds: [], selectedTopicIds: [], selectedCountryIds: [], - isBreaking: BreakingNewsFilterStatus.all, + isBreaking: false, selectedSourceTypes: [], selectedLanguageCodes: [], selectedHeadquartersCountryIds: [], @@ -255,39 +254,23 @@ class _FilterDialogState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: AppSpacing.lg), - Text( - l10n.breakingNewsFilterTitle, - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: AppSpacing.sm), - Wrap( - spacing: AppSpacing.sm, - children: [ - ...BreakingNewsFilterStatus.values.map((status) { - return ChoiceChip( - label: Text(_getBreakingNewsStatusL10n(status, l10n)), - selected: filterDialogState.isBreaking == status, - onSelected: (isSelected) { - if (isSelected) { - context.read().add( - FilterDialogBreakingNewsChanged(status), - ); - } - }, - selectedColor: Theme.of( - context, - ).colorScheme.primaryContainer, - labelStyle: TextStyle( - color: filterDialogState.isBreaking == status - ? Theme.of(context).colorScheme.onPrimaryContainer - : Theme.of(context).colorScheme.onSurface, - ), - ); - }), - ], + const Divider(height: AppSpacing.lg * 2), + SwitchListTile( + title: Text(l10n.breakingNewsFilterBreakingOnly), + subtitle: Text(l10n.breakingNewsFilterDescription), + value: filterDialogState.isBreaking, + onChanged: (value) { + context.read().add( + FilterDialogBreakingNewsChanged(value), + ); + }, + secondary: Icon( + Icons.flash_on, + color: Theme.of(context).colorScheme.primary, + ), + contentPadding: EdgeInsets.zero, ), - const SizedBox(height: AppSpacing.lg), + const Divider(height: AppSpacing.lg * 2), SearchableSelectionInput( label: l10n.sources, hintText: l10n.selectSources, @@ -530,21 +513,6 @@ class _FilterDialogState extends State { } } - /// Returns the localized string for a given [BreakingNewsFilterStatus]. - String _getBreakingNewsStatusL10n( - BreakingNewsFilterStatus status, - AppLocalizations l10n, - ) { - switch (status) { - case BreakingNewsFilterStatus.all: - return l10n.breakingNewsFilterAll; - case BreakingNewsFilterStatus.breakingOnly: - return l10n.breakingNewsFilterBreakingOnly; - case BreakingNewsFilterStatus.nonBreakingOnly: - return l10n.breakingNewsFilterNonBreakingOnly; - } - } - /// Dispatches the filter applied event to the appropriate BLoC. void _dispatchFilterApplied(FilterDialogState filterDialogState) { switch (widget.activeTab) { From 83ac65d312b5d0332f2f27be80e372cbf450efef Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:45:35 +0100 Subject: [PATCH 14/30] feat(l10n): add confirmation dialog translations for item actions - Add Arabic and English translations for publishing, archiving, restoring, and deleting items - Include titles and content for confirmation dialogs related to item actions --- lib/l10n/arb/app_ar.arb | 32 ++++++++++++++++++++++++++++++++ lib/l10n/arb/app_en.arb | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index a1e40536..4211dcf6 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2744,5 +2744,37 @@ "breakingNewsFilterDescription": "إظهار عناوين الأخبار العاجلة فقط", "@breakingNewsFilterDescription": { "description": "Subtitle for the breaking news filter switch" + }, + "publishItemTitle": "نشر العنصر؟", + "@publishItemTitle": { + "description": "Confirmation dialog title for publishing an item." + }, + "publishItemContent": "هل أنت متأكد أنك تريد نشر هذا العنصر؟ سيصبح مرئيًا للعامة.", + "@publishItemContent": { + "description": "Confirmation dialog content for publishing an item." + }, + "archiveItemTitle": "أرشفة العنصر؟", + "@archiveItemTitle": { + "description": "Confirmation dialog title for archiving an item." + }, + "archiveItemContent": "هل أنت متأكد أنك تريد أرشفة هذا العنصر؟ سيتم إخفاؤه عن العرض العام.", + "@archiveItemContent": { + "description": "Confirmation dialog content for archiving an item." + }, + "restoreItemTitle": "استعادة العنصر؟", + "@restoreItemTitle": { + "description": "Confirmation dialog title for restoring an item." + }, + "restoreItemContent": "هل أنت متأكد أنك تريد استعادة هذا العنصر؟ سيصبح نشطًا ومرئيًا للعامة مرة أخرى.", + "@restoreItemContent": { + "description": "Confirmation dialog content for restoring an item." + }, + "deleteItemTitle": "حذف العنصر؟", + "@deleteItemTitle": { + "description": "Confirmation dialog title for deleting an item." + }, + "deleteItemContent": "هل أنت متأكد أنك تريد حذف هذا العنصر؟.", + "@deleteItemContent": { + "description": "Confirmation dialog content for deleting an item." } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8f769359..d34922a9 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2740,5 +2740,38 @@ "breakingNewsFilterDescription": "Show only breaking news headlines", "@breakingNewsFilterDescription": { "description": "Subtitle for the breaking news filter switch" - } + }, + "publishItemTitle": "Republish Item?", +"@publishItemTitle": { + "description": "Confirmation dialog title for publishing an item." +}, +"publishItemContent": "Are you sure you want to republish this item? It will become publicly visible.", +"@publishItemContent": { + "description": "Confirmation dialog content for publishing an item." +}, +"archiveItemTitle": "Archive Item?", +"@archiveItemTitle": { + "description": "Confirmation dialog title for archiving an item." +}, +"archiveItemContent": "Are you sure you want to archive this item? It will be hidden from public view.", +"@archiveItemContent": { + "description": "Confirmation dialog content for archiving an item." +}, +"restoreItemTitle": "Restore Item?", +"@restoreItemTitle": { + "description": "Confirmation dialog title for restoring an item." +}, +"restoreItemContent": "Are you sure you want to restore this item? It will become active and publicly visible again.", +"@restoreItemContent": { + "description": "Confirmation dialog content for restoring an item." +}, +"deleteItemTitle": "Delete Item?", +"@deleteItemTitle": { + "description": "Confirmation dialog title for deleting an item." +}, +"deleteItemContent": "Are you sure you want to delete this item?.", +"@deleteItemContent": { + "description": "Confirmation dialog content for deleting an item." +} + } \ No newline at end of file From 069259f7cef2af0506889e762872e235730ee2a2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:45:56 +0100 Subject: [PATCH 15/30] build(l10n): sync --- lib/l10n/app_localizations.dart | 48 ++++++++++++++++++++++++++++++ lib/l10n/app_localizations_ar.dart | 27 +++++++++++++++++ lib/l10n/app_localizations_en.dart | 27 +++++++++++++++++ 3 files changed, 102 insertions(+) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 8cdc0d22..bf98024f 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -4051,6 +4051,54 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Show only breaking news headlines'** String get breakingNewsFilterDescription; + + /// Confirmation dialog title for publishing an item. + /// + /// In en, this message translates to: + /// **'Republish Item?'** + String get publishItemTitle; + + /// Confirmation dialog content for publishing an item. + /// + /// In en, this message translates to: + /// **'Are you sure you want to republish this item? It will become publicly visible.'** + String get publishItemContent; + + /// Confirmation dialog title for archiving an item. + /// + /// In en, this message translates to: + /// **'Archive Item?'** + String get archiveItemTitle; + + /// Confirmation dialog content for archiving an item. + /// + /// In en, this message translates to: + /// **'Are you sure you want to archive this item? It will be hidden from public view.'** + String get archiveItemContent; + + /// Confirmation dialog title for restoring an item. + /// + /// In en, this message translates to: + /// **'Restore Item?'** + String get restoreItemTitle; + + /// Confirmation dialog content for restoring an item. + /// + /// In en, this message translates to: + /// **'Are you sure you want to restore this item? It will become active and publicly visible again.'** + String get restoreItemContent; + + /// Confirmation dialog title for deleting an item. + /// + /// In en, this message translates to: + /// **'Delete Item?'** + String get deleteItemTitle; + + /// Confirmation dialog content for deleting an item. + /// + /// In en, this message translates to: + /// **'Are you sure you want to delete this item?.'** + String get deleteItemContent; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 2b74727f..7565131b 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -2174,4 +2174,31 @@ class AppLocalizationsAr extends AppLocalizations { @override String get breakingNewsFilterDescription => 'إظهار عناوين الأخبار العاجلة فقط'; + + @override + String get publishItemTitle => 'نشر العنصر؟'; + + @override + String get publishItemContent => + 'هل أنت متأكد أنك تريد نشر هذا العنصر؟ سيصبح مرئيًا للعامة.'; + + @override + String get archiveItemTitle => 'أرشفة العنصر؟'; + + @override + String get archiveItemContent => + 'هل أنت متأكد أنك تريد أرشفة هذا العنصر؟ سيتم إخفاؤه عن العرض العام.'; + + @override + String get restoreItemTitle => 'استعادة العنصر؟'; + + @override + String get restoreItemContent => + 'هل أنت متأكد أنك تريد استعادة هذا العنصر؟ سيصبح نشطًا ومرئيًا للعامة مرة أخرى.'; + + @override + String get deleteItemTitle => 'حذف العنصر؟'; + + @override + String get deleteItemContent => 'هل أنت متأكد أنك تريد حذف هذا العنصر؟.'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index e4ac5218..7c584c2a 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2180,4 +2180,31 @@ class AppLocalizationsEn extends AppLocalizations { @override String get breakingNewsFilterDescription => 'Show only breaking news headlines'; + + @override + String get publishItemTitle => 'Republish Item?'; + + @override + String get publishItemContent => + 'Are you sure you want to republish this item? It will become publicly visible.'; + + @override + String get archiveItemTitle => 'Archive Item?'; + + @override + String get archiveItemContent => + 'Are you sure you want to archive this item? It will be hidden from public view.'; + + @override + String get restoreItemTitle => 'Restore Item?'; + + @override + String get restoreItemContent => + 'Are you sure you want to restore this item? It will become active and publicly visible again.'; + + @override + String get deleteItemTitle => 'Delete Item?'; + + @override + String get deleteItemContent => 'Are you sure you want to delete this item?.'; } From edabb9ca2b8a4650d9637e8e41957ede1709ab5a Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 08:46:44 +0100 Subject: [PATCH 16/30] feat(content_management): add confirmation dialog for content actions - Add confirmation dialog for publish, archive, restore, and delete actions - Refactor content action handling --- .../widgets/content_action_buttons.dart | 170 ++++++++++++------ 1 file changed, 112 insertions(+), 58 deletions(-) diff --git a/lib/content_management/widgets/content_action_buttons.dart b/lib/content_management/widgets/content_action_buttons.dart index e72e2742..109926f0 100644 --- a/lib/content_management/widgets/content_action_buttons.dart +++ b/lib/content_management/widgets/content_action_buttons.dart @@ -4,6 +4,7 @@ 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/l10n/app_localizations.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/confirmation_dialog.dart'; import 'package:go_router/go_router.dart'; import 'package:ui_kit/ui_kit.dart'; @@ -161,64 +162,7 @@ class ContentActionButtons extends StatelessWidget { 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), - ); - } - } + _handleAction(context, value, itemId, l10n); }, itemBuilder: (BuildContext context) => overflowMenuItems, ), @@ -230,4 +174,114 @@ class ContentActionButtons extends StatelessWidget { children: visibleActions, ); } + + void _showConfirmationDialog({ + required BuildContext context, + required String title, + required String content, + required String confirmText, + required VoidCallback onConfirm, + }) { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return ConfirmationDialog( + title: title, + content: content, + confirmText: confirmText, + onConfirm: onConfirm, + ); + }, + ); + } + + void _handleAction( + BuildContext context, + String action, + String itemId, + AppLocalizations l10n, + ) { + switch (action) { + case 'publish': + _showConfirmationDialog( + context: context, + title: l10n.publishItemTitle, + content: l10n.publishItemContent, + confirmText: l10n.publish, + onConfirm: () { + 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': + _showConfirmationDialog( + context: context, + title: l10n.archiveItemTitle, + content: l10n.archiveItemContent, + confirmText: l10n.archive, + onConfirm: () { + 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': + _showConfirmationDialog( + context: context, + title: l10n.restoreItemTitle, + content: l10n.restoreItemContent, + confirmText: l10n.restore, + onConfirm: () { + 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': + _showConfirmationDialog( + context: context, + title: l10n.deleteItemTitle, + content: l10n.deleteItemContent, + confirmText: l10n.deleteForever, + onConfirm: () { + if (item is Headline) { + context.read().add( + DeleteHeadlineForeverRequested(itemId), + ); + } + }, + ); + } + } } From 4c18673fe6d4f0c923cc2caa32ce83c362cd277f Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 09:26:31 +0100 Subject: [PATCH 17/30] feat(localization): add Arabic translations and update deleted item snackbar message - Add Arabic translations for item action confirmations in app_ar.arb - Update app_en.arb to include item deletion snackbar message - Modify existing confirmation messages for consistency across both languages --- lib/l10n/arb/app_ar.arb | 14 ++++++++ lib/l10n/arb/app_en.arb | 77 ++++++++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 4211dcf6..d0a943db 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2776,5 +2776,19 @@ "deleteItemContent": "هل أنت متأكد أنك تريد حذف هذا العنصر؟.", "@deleteItemContent": { "description": "Confirmation dialog content for deleting an item." + }, + "itemDeletedSnackbar": "تم حذف {itemType} \"{itemName}\".", + "@itemDeletedSnackbar": { + "description": "Snackbar message shown after an item is deleted, with an undo option.", + "placeholders": { + "itemType": { + "type": "String", + "example": "Headline" + }, + "itemName": { + "type": "String", + "example": "Breaking News Story" + } + } } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index d34922a9..f98b40ae 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2742,36 +2742,49 @@ "description": "Subtitle for the breaking news filter switch" }, "publishItemTitle": "Republish Item?", -"@publishItemTitle": { - "description": "Confirmation dialog title for publishing an item." -}, -"publishItemContent": "Are you sure you want to republish this item? It will become publicly visible.", -"@publishItemContent": { - "description": "Confirmation dialog content for publishing an item." -}, -"archiveItemTitle": "Archive Item?", -"@archiveItemTitle": { - "description": "Confirmation dialog title for archiving an item." -}, -"archiveItemContent": "Are you sure you want to archive this item? It will be hidden from public view.", -"@archiveItemContent": { - "description": "Confirmation dialog content for archiving an item." -}, -"restoreItemTitle": "Restore Item?", -"@restoreItemTitle": { - "description": "Confirmation dialog title for restoring an item." -}, -"restoreItemContent": "Are you sure you want to restore this item? It will become active and publicly visible again.", -"@restoreItemContent": { - "description": "Confirmation dialog content for restoring an item." -}, -"deleteItemTitle": "Delete Item?", -"@deleteItemTitle": { - "description": "Confirmation dialog title for deleting an item." -}, -"deleteItemContent": "Are you sure you want to delete this item?.", -"@deleteItemContent": { - "description": "Confirmation dialog content for deleting an item." -} - + "@publishItemTitle": { + "description": "Confirmation dialog title for publishing an item." + }, + "publishItemContent": "Are you sure you want to republish this item? It will become publicly visible.", + "@publishItemContent": { + "description": "Confirmation dialog content for publishing an item." + }, + "archiveItemTitle": "Archive Item?", + "@archiveItemTitle": { + "description": "Confirmation dialog title for archiving an item." + }, + "archiveItemContent": "Are you sure you want to archive this item? It will be hidden from public view.", + "@archiveItemContent": { + "description": "Confirmation dialog content for archiving an item." + }, + "restoreItemTitle": "Restore Item?", + "@restoreItemTitle": { + "description": "Confirmation dialog title for restoring an item." + }, + "restoreItemContent": "Are you sure you want to restore this item? It will become active and publicly visible again.", + "@restoreItemContent": { + "description": "Confirmation dialog content for restoring an item." + }, + "deleteItemTitle": "Delete Item?", + "@deleteItemTitle": { + "description": "Confirmation dialog title for deleting an item." + }, + "deleteItemContent": "Are you sure you want to delete this item?.", + "@deleteItemContent": { + "description": "Confirmation dialog content for deleting an item." + }, + "itemDeletedSnackbar": "{itemType} \"{itemName}\" deleted.", + "@itemDeletedSnackbar": { + "description": "Snackbar message shown after an item is deleted, with an undo option.", + "placeholders": { + "itemType": { + "type": "String", + "example": "Headline" + }, + "itemName": { + "type": "String", + "example": "Breaking News Story" + } + } + } } \ No newline at end of file From e0983226debaddba9d0457b316ce51a67c3a7b30 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 09:26:47 +0100 Subject: [PATCH 18/30] build(l10n): sync. --- lib/l10n/app_localizations.dart | 6 ++++++ lib/l10n/app_localizations_ar.dart | 5 +++++ lib/l10n/app_localizations_en.dart | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index bf98024f..ffda61f8 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -4099,6 +4099,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Are you sure you want to delete this item?.'** String get deleteItemContent; + + /// Snackbar message shown after an item is deleted, with an undo option. + /// + /// In en, this message translates to: + /// **'{itemType} \"{itemName}\" deleted.'** + String itemDeletedSnackbar(String itemType, String itemName); } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 7565131b..450b8ce1 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -2201,4 +2201,9 @@ class AppLocalizationsAr extends AppLocalizations { @override String get deleteItemContent => 'هل أنت متأكد أنك تريد حذف هذا العنصر؟.'; + + @override + String itemDeletedSnackbar(String itemType, String itemName) { + return 'تم حذف $itemType \"$itemName\".'; + } } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 7c584c2a..fda7948e 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2207,4 +2207,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get deleteItemContent => 'Are you sure you want to delete this item?.'; + + @override + String itemDeletedSnackbar(String itemType, String itemName) { + return '$itemType \"$itemName\" deleted.'; + } } From 69b04d5ef7658831501008070295fb710aafef9c Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 09:28:28 +0100 Subject: [PATCH 19/30] refactor(content_management): replace snackbarMessage with itemPendingDeletion - Remove snackbarMessage field from ContentManagementState - Add itemPendingDeletion field of type FeedItem? - Update copyWith method to use new field - Update props list in Equatable to include new field --- .../bloc/content_management_state.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/content_management/bloc/content_management_state.dart b/lib/content_management/bloc/content_management_state.dart index c7c6009b..11f04f6b 100644 --- a/lib/content_management/bloc/content_management_state.dart +++ b/lib/content_management/bloc/content_management_state.dart @@ -36,7 +36,7 @@ class ContentManagementState extends Equatable { this.sourcesHasMore = false, this.exception, this.lastPendingDeletionId, - this.snackbarMessage, + this.itemPendingDeletion, }); /// The currently active tab in the content management section. @@ -85,9 +85,9 @@ class ContentManagementState extends Equatable { /// Used to trigger the snackbar display. final String? lastPendingDeletionId; - /// The message to display in the snackbar for pending deletions or other - /// transient messages. - final String? snackbarMessage; + /// The item that was just requested for deletion, used by the UI to show + /// a confirmation snackbar. + final FeedItem? itemPendingDeletion; /// Creates a copy of this [ContentManagementState] with updated values. ContentManagementState copyWith({ @@ -106,7 +106,7 @@ class ContentManagementState extends Equatable { bool? sourcesHasMore, HttpException? exception, String? lastPendingDeletionId, - String? snackbarMessage, + FeedItem? itemPendingDeletion, }) { return ContentManagementState( activeTab: activeTab ?? this.activeTab, @@ -124,7 +124,7 @@ class ContentManagementState extends Equatable { sourcesHasMore: sourcesHasMore ?? this.sourcesHasMore, exception: exception, lastPendingDeletionId: lastPendingDeletionId, - snackbarMessage: snackbarMessage, + itemPendingDeletion: itemPendingDeletion, ); } @@ -145,6 +145,6 @@ class ContentManagementState extends Equatable { sourcesHasMore, exception, lastPendingDeletionId, - snackbarMessage, + itemPendingDeletion, ]; } From 6f3c9492af7551ab09405b2cac2c243542636daf Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 09:29:06 +0100 Subject: [PATCH 20/30] feat(content_management): improve snackbar behavior and state management - Replace snackbarMessage with itemPendingDeletion in state - Remove messageBuilder from undo deletions - Clear itemPendingDeletion on undo or deletion completion - Update comments to explain changes in snackbar logic --- .../bloc/content_management_bloc.dart | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/content_management/bloc/content_management_bloc.dart b/lib/content_management/bloc/content_management_bloc.dart index b133e2b1..3b76b851 100644 --- a/lib/content_management/bloc/content_management_bloc.dart +++ b/lib/content_management/bloc/content_management_bloc.dart @@ -387,7 +387,7 @@ class ContentManagementBloc state.copyWith( headlines: updatedHeadlines, lastPendingDeletionId: event.id, - snackbarMessage: 'Headline "${headlineToDelete.title}" deleted.', + itemPendingDeletion: headlineToDelete, ), ); @@ -395,6 +395,7 @@ class ContentManagementBloc item: headlineToDelete, repository: _headlinesRepository, undoDuration: AppConstants.kSnackbarDuration, + // messageBuilder is omitted, UI will build the message ); } @@ -578,7 +579,7 @@ class ContentManagementBloc state.copyWith( topics: updatedTopics, lastPendingDeletionId: event.id, - snackbarMessage: 'Topic "${topicToDelete.name}" deleted.', + itemPendingDeletion: topicToDelete, ), ); @@ -586,6 +587,7 @@ class ContentManagementBloc item: topicToDelete, repository: _topicsRepository, undoDuration: AppConstants.kSnackbarDuration, + // messageBuilder is omitted, UI will build the message ); } @@ -769,7 +771,7 @@ class ContentManagementBloc state.copyWith( sources: updatedSources, lastPendingDeletionId: event.id, - snackbarMessage: 'Source "${sourceToDelete.name}" deleted.', + itemPendingDeletion: sourceToDelete, ), ); @@ -777,6 +779,7 @@ class ContentManagementBloc item: sourceToDelete, repository: _sourcesRepository, undoDuration: AppConstants.kSnackbarDuration, + // messageBuilder is omitted, UI will build the message ); } @@ -802,8 +805,9 @@ class ContentManagementBloc // The item was already optimistically removed from the list. emit( state.copyWith( - lastPendingDeletionId: null, - snackbarMessage: null, + lastPendingDeletionId: null, // Clear the pending ID + // Clear the item so the snackbar doesn't reappear on rebuilds + itemPendingDeletion: null, ), ); case DeletionStatus.undone: @@ -816,8 +820,8 @@ class ContentManagementBloc emit( state.copyWith( headlines: updatedHeadlines, - lastPendingDeletionId: null, - snackbarMessage: null, + lastPendingDeletionId: null, // Clear the pending ID + itemPendingDeletion: null, ), ); } else if (item is Topic) { @@ -827,8 +831,8 @@ class ContentManagementBloc emit( state.copyWith( topics: updatedTopics, - lastPendingDeletionId: null, - snackbarMessage: null, + lastPendingDeletionId: null, // Clear the pending ID + itemPendingDeletion: null, ), ); } else if (item is Source) { @@ -838,8 +842,8 @@ class ContentManagementBloc emit( state.copyWith( sources: updatedSources, - lastPendingDeletionId: null, - snackbarMessage: null, + lastPendingDeletionId: null, // Clear the pending ID + itemPendingDeletion: null, ), ); } From 7c8ae995ff136de5f8db95753a20507aa079747d Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 09:29:21 +0100 Subject: [PATCH 21/30] feat(content_management): improve snackbar message for item deletion - Replace generic snackbar message with specific item-related message - Add logic to determine deleted item type and name - Update UI to display customized snackbar message for deleted items --- .../view/content_management_page.dart | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/content_management/view/content_management_page.dart b/lib/content_management/view/content_management_page.dart index 7f5c7ed2..bdb03e8c 100644 --- a/lib/content_management/view/content_management_page.dart +++ b/lib/content_management/view/content_management_page.dart @@ -135,14 +135,27 @@ class _ContentManagementPageState extends State ), BlocListener( listenWhen: (previous, current) => - previous.snackbarMessage != current.snackbarMessage && - current.snackbarMessage != null, + previous.itemPendingDeletion != current.itemPendingDeletion && + current.itemPendingDeletion != null, listener: (context, state) { + final item = state.itemPendingDeletion!; + String itemType; + String itemName; + if (item is Headline) { + itemType = l10n.headlines; + itemName = item.title; + } else if (item is Topic) { + itemType = l10n.topics; + itemName = item.name; + } else { + itemType = l10n.sources; + itemName = (item as Source).name; + } ScaffoldMessenger.of(context) ..hideCurrentSnackBar() ..showSnackBar( SnackBar( - content: Text(state.snackbarMessage!), + content: Text(l10n.itemDeletedSnackbar(itemType, itemName)), action: SnackBarAction( label: l10n.undo, onPressed: () { From 2befa955078d6ef22e229f0090e05fe95c8e6232 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 09:30:05 +0100 Subject: [PATCH 22/30] feat(pending_deletions_service): add messageBuilder to requestDeletion - Add message field to DeletionEvent class - Add messageBuilder parameter to requestDeletion method - Implement message building in PendingDeletionsServiceImpl - Update _PendingDeletion class to include message field --- .../services/pending_deletions_service.dart | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/shared/services/pending_deletions_service.dart b/lib/shared/services/pending_deletions_service.dart index 2084811c..e4bd682c 100644 --- a/lib/shared/services/pending_deletions_service.dart +++ b/lib/shared/services/pending_deletions_service.dart @@ -22,7 +22,12 @@ enum DeletionStatus { @immutable class DeletionEvent extends Equatable { /// {@macro deletion_event} - const DeletionEvent(this.id, this.status, {this.item}); + const DeletionEvent( + this.id, + this.status, { + this.item, + this.message, + }); /// The unique identifier of the item. final String id; @@ -34,8 +39,11 @@ class DeletionEvent extends Equatable { /// This is typically provided when a deletion is undone. final T? item; + /// An optional message associated with the event, e.g., for snackbars. + final String? message; + @override - List get props => [id, status, item]; + List get props => [id, status, item, message]; } /// {@template pending_deletions_service} @@ -60,10 +68,12 @@ abstract class PendingDeletionsService { /// - [item]: The item to be deleted. Must have an `id` property. /// - [repository]: The `DataRepository` responsible for deleting the item. /// - [undoDuration]: The duration to wait before confirming the deletion. + /// - [messageBuilder]: An optional function to build a localized message for the UI. void requestDeletion({ required T item, required DataRepository repository, required Duration undoDuration, + String Function()? messageBuilder, }); /// Cancels a pending deletion for the item with the given [id]. @@ -106,6 +116,7 @@ class PendingDeletionsServiceImpl implements PendingDeletionsService { required T item, required DataRepository repository, required Duration undoDuration, + String Function()? messageBuilder, }) { // The item must have an 'id' property. final id = (item as dynamic).id as String; @@ -133,7 +144,12 @@ class PendingDeletionsServiceImpl implements PendingDeletionsService { } }); - _pendingDeletionTimers[id] = _PendingDeletion(timer: timer, item: item); + final message = messageBuilder?.call(); + _pendingDeletionTimers[id] = _PendingDeletion( + timer: timer, + item: item, + message: message, + ); } @override @@ -174,11 +190,16 @@ class PendingDeletionsServiceImpl implements PendingDeletionsService { /// A private class to hold the timer and the item for a pending deletion. @immutable class _PendingDeletion extends Equatable { - const _PendingDeletion({required this.timer, required this.item}); + const _PendingDeletion({ + required this.timer, + required this.item, + this.message, + }); final Timer timer; final T item; + final String? message; @override - List get props => [timer, item]; + List get props => [timer, item, message]; } From 2fe7cf0ef4d8af302233ddf91fd96a0e763e027c Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 09:35:54 +0100 Subject: [PATCH 23/30] refactor(content_management): add case for DeletionStatus.requested in switch statement - Add a case for DeletionStatus.requested in the switch statement - Include a comment explaining that this case is now handled by the optimistic UI update in specific delete handlers - Mention that the itemPendingDeletion is set in those handlers, which the UI uses to build the snackbar message --- lib/content_management/bloc/content_management_bloc.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/content_management/bloc/content_management_bloc.dart b/lib/content_management/bloc/content_management_bloc.dart index 3b76b851..fffe2933 100644 --- a/lib/content_management/bloc/content_management_bloc.dart +++ b/lib/content_management/bloc/content_management_bloc.dart @@ -800,6 +800,12 @@ class ContentManagementBloc Emitter emit, ) async { switch (event.event.status) { + case DeletionStatus.requested: + // This case is now handled by the optimistic UI update in the + // specific delete handlers (e.g., _onDeleteHeadlineForeverRequested). + // The itemPendingDeletion is set there, which the UI uses to build + // the snackbar message. + break; case DeletionStatus.confirmed: // If deletion is confirmed, clear pending status. // The item was already optimistically removed from the list. From 6c3c35cc441cb39b3ee75178f02c9e80ad3e6d05 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 09:36:18 +0100 Subject: [PATCH 24/30] feat(pending_deletions_service): add requested state and event for pending deletions - Add DeletionStatus.requested enum value to represent a pending deletion request - Implement functionality to immediately notify listeners when a deletion is requested - Update PendingDeletionsServiceImpl to send a DeletionEvent with requested status upon request --- lib/shared/services/pending_deletions_service.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/shared/services/pending_deletions_service.dart b/lib/shared/services/pending_deletions_service.dart index e4bd682c..2428a018 100644 --- a/lib/shared/services/pending_deletions_service.dart +++ b/lib/shared/services/pending_deletions_service.dart @@ -7,6 +7,9 @@ import 'package:logging/logging.dart'; /// Represents the status of a pending deletion. enum DeletionStatus { + /// The deletion has been requested and is pending confirmation. + requested, + /// The deletion has been confirmed and executed. confirmed, @@ -150,6 +153,15 @@ class PendingDeletionsServiceImpl implements PendingDeletionsService { item: item, message: message, ); + + // Immediately notify listeners that a deletion has been requested. + _deletionEventController.add( + DeletionEvent( + id, + DeletionStatus.requested, + message: message, + ), + ); } @override From 2e3fbd60dbe7e74b70da845f04a91c27aa65cfdd Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 09:41:34 +0100 Subject: [PATCH 25/30] feat(l10n): add Arabic translations for new labels - Add Arabic translations for 'headline', 'topic', and 'source' - Include descriptions for new labels in both Arabic and English files - Maintain consistency in translation style and formatting --- lib/l10n/arb/app_ar.arb | 12 ++++++++++++ lib/l10n/arb/app_en.arb | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index d0a943db..2cc4a31f 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -107,14 +107,26 @@ "@headlines": { "description": "تسمية الصفحة الفرعية للعناوين الرئيسية" }, + "headline": "العنوان الرئيسي", + "@headline": { + "description": "تسمية العنوان الرئيسي" + }, "topics": "المواضيع", "@topics": { "description": "تسمية الصفحة الفرعية للمواضيع" }, + "topic": "الموضوع", + "@topic": { + "description": "تسمية الموضوع" + }, "sources": "المصادر", "@sources": { "description": "تسمية الصفحة الفرعية للمصادر" }, + "source": "المصدر", + "@source": { + "description": "تسمية المصدر" + }, "appConfiguration": "إعدادات التطبيق", "@appConfiguration": { "description": "تسمية عنصر التنقل لإعدادات التطبيق" diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index f98b40ae..e140719f 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -107,14 +107,26 @@ "@headlines": { "description": "Label for the headlines subpage" }, + "headline": "Headline", + "@headline": { + "description": "Label for the a singular headline" + }, "topics": "Topics", "@topics": { "description": "Label for the topics subpage" }, + "topic": "Topic", + "@topic": { + "description": "Label for the a singular topic" + }, "sources": "Sources", "@sources": { "description": "Label for the sources subpage" }, + "source": "Source", + "@source": { + "description": "Label for the a singular source" + }, "appConfiguration": "App Configuration", "@appConfiguration": { "description": "Label for the app configuration navigation item" From aa6ef2cf233907ecf347369ab3b1670b3b19e5c3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 09:41:47 +0100 Subject: [PATCH 26/30] build(l10n): sync. --- lib/l10n/app_localizations.dart | 18 ++++++++++++++++++ lib/l10n/app_localizations_ar.dart | 9 +++++++++ lib/l10n/app_localizations_en.dart | 9 +++++++++ 3 files changed, 36 insertions(+) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index ffda61f8..d257178b 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -236,18 +236,36 @@ abstract class AppLocalizations { /// **'Headlines'** String get headlines; + /// Label for the a singular headline + /// + /// In en, this message translates to: + /// **'Headline'** + String get headline; + /// Label for the topics subpage /// /// In en, this message translates to: /// **'Topics'** String get topics; + /// Label for the a singular topic + /// + /// In en, this message translates to: + /// **'Topic'** + String get topic; + /// Label for the sources subpage /// /// In en, this message translates to: /// **'Sources'** String get sources; + /// Label for the a singular source + /// + /// In en, this message translates to: + /// **'Source'** + String get source; + /// Label for the app configuration navigation item /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 450b8ce1..af30110b 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -89,12 +89,21 @@ class AppLocalizationsAr extends AppLocalizations { @override String get headlines => 'العناوين الرئيسية'; + @override + String get headline => 'العنوان الرئيسي'; + @override String get topics => 'المواضيع'; + @override + String get topic => 'الموضوع'; + @override String get sources => 'المصادر'; + @override + String get source => 'المصدر'; + @override String get appConfiguration => 'إعدادات التطبيق'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index fda7948e..858749af 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -88,12 +88,21 @@ class AppLocalizationsEn extends AppLocalizations { @override String get headlines => 'Headlines'; + @override + String get headline => 'Headline'; + @override String get topics => 'Topics'; + @override + String get topic => 'Topic'; + @override String get sources => 'Sources'; + @override + String get source => 'Source'; + @override String get appConfiguration => 'App Configuration'; From 96ff3e5f9fb953be7128e971af43f7b8968c46a3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 09:41:55 +0100 Subject: [PATCH 27/30] fix(content_management): correct item type naming in content management - Change 'headlines' to 'headline' - Change 'topics' to 'topic' - Change 'sources' to 'source' --- lib/content_management/view/content_management_page.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/content_management/view/content_management_page.dart b/lib/content_management/view/content_management_page.dart index bdb03e8c..60f5c572 100644 --- a/lib/content_management/view/content_management_page.dart +++ b/lib/content_management/view/content_management_page.dart @@ -142,13 +142,13 @@ class _ContentManagementPageState extends State String itemType; String itemName; if (item is Headline) { - itemType = l10n.headlines; + itemType = l10n.headline; itemName = item.title; } else if (item is Topic) { - itemType = l10n.topics; + itemType = l10n.topic; itemName = item.name; } else { - itemType = l10n.sources; + itemType = l10n.source; itemName = (item as Source).name; } ScaffoldMessenger.of(context) From a3fd5d35c98d01ff6ead196ac7d41868b356d75f Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 10:22:25 +0100 Subject: [PATCH 28/30] fix(l10n): improve confirmation dialog messages for item actions - Add placeholders for item type in confirmation dialog messages - Update translations for publish, archive, restore, and delete actions - Improve clarity of messages, especially for delete action --- lib/l10n/arb/app_ar.arb | 80 ++++++++++++++++++++++++++++++++--------- lib/l10n/arb/app_en.arb | 80 ++++++++++++++++++++++++++++++++--------- 2 files changed, 128 insertions(+), 32 deletions(-) diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 2cc4a31f..96a8e14e 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2757,37 +2757,85 @@ "@breakingNewsFilterDescription": { "description": "Subtitle for the breaking news filter switch" }, - "publishItemTitle": "نشر العنصر؟", + "publishItemTitle": "نشر {itemType}؟", "@publishItemTitle": { - "description": "Confirmation dialog title for publishing an item." + "description": "Confirmation dialog title for publishing an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "Headline" + } + } }, - "publishItemContent": "هل أنت متأكد أنك تريد نشر هذا العنصر؟ سيصبح مرئيًا للعامة.", + "publishItemContent": "هل أنت متأكد أنك تريد نشر هذا الـ {itemType}؟ سيصبح مرئيًا للعامة.", "@publishItemContent": { - "description": "Confirmation dialog content for publishing an item." + "description": "Confirmation dialog content for publishing an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "headline" + } + } }, - "archiveItemTitle": "أرشفة العنصر؟", + "archiveItemTitle": "أرشفة {itemType}؟", "@archiveItemTitle": { - "description": "Confirmation dialog title for archiving an item." + "description": "Confirmation dialog title for archiving an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "Headline" + } + } }, - "archiveItemContent": "هل أنت متأكد أنك تريد أرشفة هذا العنصر؟ سيتم إخفاؤه عن العرض العام.", + "archiveItemContent": "هل أنت متأكد أنك تريد أرشفة هذا الـ {itemType}؟ سيتم إخفاؤه عن العرض العام.", "@archiveItemContent": { - "description": "Confirmation dialog content for archiving an item." + "description": "Confirmation dialog content for archiving an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "headline" + } + } }, - "restoreItemTitle": "استعادة العنصر؟", + "restoreItemTitle": "استعادة {itemType}؟", "@restoreItemTitle": { - "description": "Confirmation dialog title for restoring an item." + "description": "Confirmation dialog title for restoring an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "Headline" + } + } }, - "restoreItemContent": "هل أنت متأكد أنك تريد استعادة هذا العنصر؟ سيصبح نشطًا ومرئيًا للعامة مرة أخرى.", + "restoreItemContent": "هل أنت متأكد أنك تريد استعادة هذا الـ {itemType}؟ سيصبح نشطًا ومرئيًا للعامة مرة أخرى.", "@restoreItemContent": { - "description": "Confirmation dialog content for restoring an item." + "description": "Confirmation dialog content for restoring an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "headline" + } + } }, - "deleteItemTitle": "حذف العنصر؟", + "deleteItemTitle": "حذف {itemType}؟", "@deleteItemTitle": { - "description": "Confirmation dialog title for deleting an item." + "description": "Confirmation dialog title for deleting an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "Headline" + } + } }, - "deleteItemContent": "هل أنت متأكد أنك تريد حذف هذا العنصر؟.", + "deleteItemContent": "هل أنت متأكد أنك تريد حذف هذا الـ {itemType}؟ يمكن التراجع عن هذا الإجراء لفترة قصيرة.", "@deleteItemContent": { - "description": "Confirmation dialog content for deleting an item." + "description": "Confirmation dialog content for deleting an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "headline" + } + } }, "itemDeletedSnackbar": "تم حذف {itemType} \"{itemName}\".", "@itemDeletedSnackbar": { diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index e140719f..8cb9fbee 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2753,37 +2753,85 @@ "@breakingNewsFilterDescription": { "description": "Subtitle for the breaking news filter switch" }, - "publishItemTitle": "Republish Item?", + "publishItemTitle": "Publish {itemType}?", "@publishItemTitle": { - "description": "Confirmation dialog title for publishing an item." + "description": "Confirmation dialog title for publishing an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "Headline" + } + } }, - "publishItemContent": "Are you sure you want to republish this item? It will become publicly visible.", + "publishItemContent": "Are you sure you want to publish this {itemType}? It will become publicly visible.", "@publishItemContent": { - "description": "Confirmation dialog content for publishing an item." + "description": "Confirmation dialog content for publishing an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "headline" + } + } }, - "archiveItemTitle": "Archive Item?", + "archiveItemTitle": "Archive {itemType}?", "@archiveItemTitle": { - "description": "Confirmation dialog title for archiving an item." + "description": "Confirmation dialog title for archiving an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "Headline" + } + } }, - "archiveItemContent": "Are you sure you want to archive this item? It will be hidden from public view.", + "archiveItemContent": "Are you sure you want to archive this {itemType}? It will be hidden from public view.", "@archiveItemContent": { - "description": "Confirmation dialog content for archiving an item." + "description": "Confirmation dialog content for archiving an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "headline" + } + } }, - "restoreItemTitle": "Restore Item?", + "restoreItemTitle": "Restore {itemType}?", "@restoreItemTitle": { - "description": "Confirmation dialog title for restoring an item." + "description": "Confirmation dialog title for restoring an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "Headline" + } + } }, - "restoreItemContent": "Are you sure you want to restore this item? It will become active and publicly visible again.", + "restoreItemContent": "Are you sure you want to restore this {itemType}? It will become active and publicly visible again.", "@restoreItemContent": { - "description": "Confirmation dialog content for restoring an item." + "description": "Confirmation dialog content for restoring an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "headline" + } + } }, - "deleteItemTitle": "Delete Item?", + "deleteItemTitle": "Delete {itemType}?", "@deleteItemTitle": { - "description": "Confirmation dialog title for deleting an item." + "description": "Confirmation dialog title for deleting an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "Headline" + } + } }, - "deleteItemContent": "Are you sure you want to delete this item?.", + "deleteItemContent": "Are you sure you want to delete this {itemType}? ", "@deleteItemContent": { - "description": "Confirmation dialog content for deleting an item." + "description": "Confirmation dialog content for deleting an item.", + "placeholders": { + "itemType": { + "type": "String", + "example": "headline" + } + } }, "itemDeletedSnackbar": "{itemType} \"{itemName}\" deleted.", "@itemDeletedSnackbar": { From 50ac9b509a542114974b5327c76d438d2f4b4938 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 10:22:44 +0100 Subject: [PATCH 29/30] build(l10n): sync --- lib/l10n/app_localizations.dart | 32 +++++++++++++-------------- lib/l10n/app_localizations_ar.dart | 35 ++++++++++++++++++++---------- lib/l10n/app_localizations_en.dart | 35 ++++++++++++++++++++---------- 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index d257178b..bee5a5ce 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -4073,50 +4073,50 @@ abstract class AppLocalizations { /// Confirmation dialog title for publishing an item. /// /// In en, this message translates to: - /// **'Republish Item?'** - String get publishItemTitle; + /// **'Publish {itemType}?'** + String publishItemTitle(String itemType); /// Confirmation dialog content for publishing an item. /// /// In en, this message translates to: - /// **'Are you sure you want to republish this item? It will become publicly visible.'** - String get publishItemContent; + /// **'Are you sure you want to publish this {itemType}? It will become publicly visible.'** + String publishItemContent(String itemType); /// Confirmation dialog title for archiving an item. /// /// In en, this message translates to: - /// **'Archive Item?'** - String get archiveItemTitle; + /// **'Archive {itemType}?'** + String archiveItemTitle(String itemType); /// Confirmation dialog content for archiving an item. /// /// In en, this message translates to: - /// **'Are you sure you want to archive this item? It will be hidden from public view.'** - String get archiveItemContent; + /// **'Are you sure you want to archive this {itemType}? It will be hidden from public view.'** + String archiveItemContent(String itemType); /// Confirmation dialog title for restoring an item. /// /// In en, this message translates to: - /// **'Restore Item?'** - String get restoreItemTitle; + /// **'Restore {itemType}?'** + String restoreItemTitle(String itemType); /// Confirmation dialog content for restoring an item. /// /// In en, this message translates to: - /// **'Are you sure you want to restore this item? It will become active and publicly visible again.'** - String get restoreItemContent; + /// **'Are you sure you want to restore this {itemType}? It will become active and publicly visible again.'** + String restoreItemContent(String itemType); /// Confirmation dialog title for deleting an item. /// /// In en, this message translates to: - /// **'Delete Item?'** - String get deleteItemTitle; + /// **'Delete {itemType}?'** + String deleteItemTitle(String itemType); /// Confirmation dialog content for deleting an item. /// /// In en, this message translates to: - /// **'Are you sure you want to delete this item?.'** - String get deleteItemContent; + /// **'Are you sure you want to delete this {itemType}? '** + String deleteItemContent(String itemType); /// Snackbar message shown after an item is deleted, with an undo option. /// diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index af30110b..38c0f965 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -2185,31 +2185,44 @@ class AppLocalizationsAr extends AppLocalizations { 'إظهار عناوين الأخبار العاجلة فقط'; @override - String get publishItemTitle => 'نشر العنصر؟'; + String publishItemTitle(String itemType) { + return 'نشر $itemType؟'; + } @override - String get publishItemContent => - 'هل أنت متأكد أنك تريد نشر هذا العنصر؟ سيصبح مرئيًا للعامة.'; + String publishItemContent(String itemType) { + return 'هل أنت متأكد أنك تريد نشر هذا الـ $itemType؟ سيصبح مرئيًا للعامة.'; + } @override - String get archiveItemTitle => 'أرشفة العنصر؟'; + String archiveItemTitle(String itemType) { + return 'أرشفة $itemType؟'; + } @override - String get archiveItemContent => - 'هل أنت متأكد أنك تريد أرشفة هذا العنصر؟ سيتم إخفاؤه عن العرض العام.'; + String archiveItemContent(String itemType) { + return 'هل أنت متأكد أنك تريد أرشفة هذا الـ $itemType؟ سيتم إخفاؤه عن العرض العام.'; + } @override - String get restoreItemTitle => 'استعادة العنصر؟'; + String restoreItemTitle(String itemType) { + return 'استعادة $itemType؟'; + } @override - String get restoreItemContent => - 'هل أنت متأكد أنك تريد استعادة هذا العنصر؟ سيصبح نشطًا ومرئيًا للعامة مرة أخرى.'; + String restoreItemContent(String itemType) { + return 'هل أنت متأكد أنك تريد استعادة هذا الـ $itemType؟ سيصبح نشطًا ومرئيًا للعامة مرة أخرى.'; + } @override - String get deleteItemTitle => 'حذف العنصر؟'; + String deleteItemTitle(String itemType) { + return 'حذف $itemType؟'; + } @override - String get deleteItemContent => 'هل أنت متأكد أنك تريد حذف هذا العنصر؟.'; + String deleteItemContent(String itemType) { + return 'هل أنت متأكد أنك تريد حذف هذا الـ $itemType؟ يمكن التراجع عن هذا الإجراء لفترة قصيرة.'; + } @override String itemDeletedSnackbar(String itemType, String itemName) { diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 858749af..69ab05ff 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2191,31 +2191,44 @@ class AppLocalizationsEn extends AppLocalizations { 'Show only breaking news headlines'; @override - String get publishItemTitle => 'Republish Item?'; + String publishItemTitle(String itemType) { + return 'Publish $itemType?'; + } @override - String get publishItemContent => - 'Are you sure you want to republish this item? It will become publicly visible.'; + String publishItemContent(String itemType) { + return 'Are you sure you want to publish this $itemType? It will become publicly visible.'; + } @override - String get archiveItemTitle => 'Archive Item?'; + String archiveItemTitle(String itemType) { + return 'Archive $itemType?'; + } @override - String get archiveItemContent => - 'Are you sure you want to archive this item? It will be hidden from public view.'; + String archiveItemContent(String itemType) { + return 'Are you sure you want to archive this $itemType? It will be hidden from public view.'; + } @override - String get restoreItemTitle => 'Restore Item?'; + String restoreItemTitle(String itemType) { + return 'Restore $itemType?'; + } @override - String get restoreItemContent => - 'Are you sure you want to restore this item? It will become active and publicly visible again.'; + String restoreItemContent(String itemType) { + return 'Are you sure you want to restore this $itemType? It will become active and publicly visible again.'; + } @override - String get deleteItemTitle => 'Delete Item?'; + String deleteItemTitle(String itemType) { + return 'Delete $itemType?'; + } @override - String get deleteItemContent => 'Are you sure you want to delete this item?.'; + String deleteItemContent(String itemType) { + return 'Are you sure you want to delete this $itemType? '; + } @override String itemDeletedSnackbar(String itemType, String itemName) { From d21bebe84b575486148e0b76f5139937cdd358d0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 13 Dec 2025 10:23:13 +0100 Subject: [PATCH 30/30] fix(content): update confirmation dialog messages for different content types - Introduce type-specific confirmation dialogs for publish, archive, restore, and delete actions - Replace generic item-related messages with type (headline, topic, or source) specific messages - Improve user experience by providing more context in confirmation dialogs --- .../widgets/content_action_buttons.dart | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/content_management/widgets/content_action_buttons.dart b/lib/content_management/widgets/content_action_buttons.dart index 109926f0..0b7bd98d 100644 --- a/lib/content_management/widgets/content_action_buttons.dart +++ b/lib/content_management/widgets/content_action_buttons.dart @@ -201,12 +201,21 @@ class ContentActionButtons extends StatelessWidget { String itemId, AppLocalizations l10n, ) { + String itemType; + if (item is Headline) { + itemType = l10n.headline.toLowerCase(); + } else if (item is Topic) { + itemType = l10n.topic.toLowerCase(); + } else { + itemType = l10n.source.toLowerCase(); + } + switch (action) { case 'publish': _showConfirmationDialog( context: context, - title: l10n.publishItemTitle, - content: l10n.publishItemContent, + title: l10n.publishItemTitle(itemType), + content: l10n.publishItemContent(itemType), confirmText: l10n.publish, onConfirm: () { if (item is Headline) { @@ -227,8 +236,8 @@ class ContentActionButtons extends StatelessWidget { case 'archive': _showConfirmationDialog( context: context, - title: l10n.archiveItemTitle, - content: l10n.archiveItemContent, + title: l10n.archiveItemTitle(itemType), + content: l10n.archiveItemContent(itemType), confirmText: l10n.archive, onConfirm: () { if (item is Headline) { @@ -249,8 +258,8 @@ class ContentActionButtons extends StatelessWidget { case 'restore': _showConfirmationDialog( context: context, - title: l10n.restoreItemTitle, - content: l10n.restoreItemContent, + title: l10n.restoreItemTitle(itemType), + content: l10n.restoreItemContent(itemType), confirmText: l10n.restore, onConfirm: () { if (item is Headline) { @@ -271,8 +280,8 @@ class ContentActionButtons extends StatelessWidget { case 'delete': _showConfirmationDialog( context: context, - title: l10n.deleteItemTitle, - content: l10n.deleteItemContent, + title: l10n.deleteItemTitle(itemType), + content: l10n.deleteItemContent(itemType), confirmText: l10n.deleteForever, onConfirm: () { if (item is Headline) {