From b35b6007b4d9135a5f04c35b9ecb28b090991155 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 07:39:31 +0100 Subject: [PATCH 01/43] feat(l10n): add translations for feed item click behavior settings - Add Arabic and English translations for new settings related to feed item click behavior - Include titles, descriptions, and options for internal and external browser navigation --- lib/l10n/arb/app_ar.arb | 16 ++++++++++++++++ lib/l10n/arb/app_en.arb | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 3659fbe2..8d71aa9b 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2069,5 +2069,21 @@ "appLegalInformationTitle": "المعلومات القانونية والعامة", "@appLegalInformationTitle": { "description": "عنوان قسم المعلومات القانونية والعامة القابل للتوسيع." + }, + "feedItemClickBehaviorTitle": "سلوك النقر على عنصر الموجز", + "@feedItemClickBehaviorTitle": { + "description": "عنوان إعداد سلوك النقر على عنصر الموجز." + }, + "feedItemClickBehaviorDescription": "اختر ما يحدث عندما ينقر المستخدم على عنوان في الموجز.", + "@feedItemClickbehaviorDescription": { + "description": "وصف إعداد سلوك النقر على عنصر الموجز." + }, + "feedItemClickBehaviorInternalNavigation": "متصفح داخل التطبيق", + "@feedItemClickBehaviorInternalNavigation": { + "description": "خيار لفتح الروابط في المتصفح الداخلي للتطبيق." + }, + "feedItemClickBehaviorExternalNavigation": "متصفح النظام", + "@feedItemClickBehaviorExternalNavigation": { + "description": "خيار لفتح الروابط في متصفح النظام الافتراضي للجهاز." } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 31c693d7..04f57e68 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2065,5 +2065,21 @@ "appLegalInformationTitle": "Legal & General Information", "@appLegalInformationTitle": { "description": "Title for the Legal & General Information expansion tile." + }, + "feedItemClickBehaviorTitle": "Feed Item Click Behavior", + "@feedItemClickBehaviorTitle": { + "description": "Title for the feed item click behavior setting." + }, + "feedItemClickBehaviorDescription": "Choose what happens when a user clicks on a headline in the feed.", + "@feedItemClickbehaviorDescription": { + "description": "Description for the feed item click behavior setting." + }, + "feedItemClickBehaviorInternalNavigation": "In-App Browser", + "@feedItemClickBehaviorInternalNavigation": { + "description": "Option for opening links in the app's internal browser." + }, + "feedItemClickBehaviorExternalNavigation": "System Browser", + "@feedItemClickBehaviorExternalNavigation": { + "description": "Option for opening links in the device's default system browser." } } \ No newline at end of file From 7acd5984a5f94fa9191ba6327fc7599fccfd3fac Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 07:39:57 +0100 Subject: [PATCH 02/43] build(l10n): generate --- lib/l10n/app_localizations.dart | 24 ++++++++++++++++++++++++ lib/l10n/app_localizations_ar.dart | 13 +++++++++++++ lib/l10n/app_localizations_en.dart | 13 +++++++++++++ 3 files changed, 50 insertions(+) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index f1fd3547..31094530 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3073,6 +3073,30 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Legal & General Information'** String get appLegalInformationTitle; + + /// Title for the feed item click behavior setting. + /// + /// In en, this message translates to: + /// **'Feed Item Click Behavior'** + String get feedItemClickBehaviorTitle; + + /// No description provided for @feedItemClickBehaviorDescription. + /// + /// In en, this message translates to: + /// **'Choose what happens when a user clicks on a headline in the feed.'** + String get feedItemClickBehaviorDescription; + + /// Option for opening links in the app's internal browser. + /// + /// In en, this message translates to: + /// **'In-App Browser'** + String get feedItemClickBehaviorInternalNavigation; + + /// Option for opening links in the device's default system browser. + /// + /// In en, this message translates to: + /// **'System Browser'** + String get feedItemClickBehaviorExternalNavigation; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index e2099be5..da3343b8 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -1649,4 +1649,17 @@ class AppLocalizationsAr extends AppLocalizations { @override String get appLegalInformationTitle => 'المعلومات القانونية والعامة'; + + @override + String get feedItemClickBehaviorTitle => 'سلوك النقر على عنصر الموجز'; + + @override + String get feedItemClickBehaviorDescription => + 'اختر ما يحدث عندما ينقر المستخدم على عنوان في الموجز.'; + + @override + String get feedItemClickBehaviorInternalNavigation => 'متصفح داخل التطبيق'; + + @override + String get feedItemClickBehaviorExternalNavigation => 'متصفح النظام'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 7db3aeaf..fcb191e7 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1652,4 +1652,17 @@ class AppLocalizationsEn extends AppLocalizations { @override String get appLegalInformationTitle => 'Legal & General Information'; + + @override + String get feedItemClickBehaviorTitle => 'Feed Item Click Behavior'; + + @override + String get feedItemClickBehaviorDescription => + 'Choose what happens when a user clicks on a headline in the feed.'; + + @override + String get feedItemClickBehaviorInternalNavigation => 'In-App Browser'; + + @override + String get feedItemClickBehaviorExternalNavigation => 'System Browser'; } From 72a5ad47585fb365aa569fea042204aa85ba1bd5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 07:40:29 +0100 Subject: [PATCH 03/43] feat(l10n): add extension to localize FeedItemClickBehavior enum - Create new extension file for FeedItemClickBehavior enum --- .../feed_item_click_behavior_l10n.dart | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 lib/shared/extensions/feed_item_click_behavior_l10n.dart diff --git a/lib/shared/extensions/feed_item_click_behavior_l10n.dart b/lib/shared/extensions/feed_item_click_behavior_l10n.dart new file mode 100644 index 00000000..8802976e --- /dev/null +++ b/lib/shared/extensions/feed_item_click_behavior_l10n.dart @@ -0,0 +1,20 @@ +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; + +/// Extension to localize the [FeedItemClickBehavior] enum. +extension FeedItemClickBehaviorL10n on FeedItemClickBehavior { + /// Returns the localized string representation of the + /// [FeedItemClickBehavior]. + String l10n(BuildContext context) { + final l10n = AppLocalizationsX(context).l10n; + switch (this) { + case FeedItemClickBehavior.internalNavigation: + return l10n.feedItemClickBehaviorInternalNavigation; + case FeedItemClickBehavior.externalNavigation: + return l10n.feedItemClickBehaviorExternalNavigation; + case FeedItemClickBehavior.defaultBehavior: + return ''; // Mobile client related, should not be displayed in the dashboard UI. + } + } +} From 99d7a5637f721989ee4fc70e8524211e2893b2b0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 07:41:21 +0100 Subject: [PATCH 04/43] feat(app_configuration): add feed item click behavior settings - Add segmented button for selecting feed item click behavior - Implement onSelectionChanged callback to update remote config - Reorganize feed-related settings under a single expansion tile --- .../view/tabs/features_configuration_tab.dart | 127 +++++++++++++----- 1 file changed, 94 insertions(+), 33 deletions(-) diff --git a/lib/app_configuration/view/tabs/features_configuration_tab.dart b/lib/app_configuration/view/tabs/features_configuration_tab.dart index fac84584..a6d0dab7 100644 --- a/lib/app_configuration/view/tabs/features_configuration_tab.dart +++ b/lib/app_configuration/view/tabs/features_configuration_tab.dart @@ -8,6 +8,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/widgets/push_notification_settings_form.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/extensions/feed_decorator_type_l10n.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/feed_item_click_behavior_l10n.dart'; import 'package:ui_kit/ui_kit.dart'; /// {@template features_configuration_tab} @@ -115,54 +116,114 @@ class _FeaturesConfigurationTabState extends State { ), const SizedBox(height: AppSpacing.lg), - // Feed Decorators + // Feed ValueListenableBuilder( valueListenable: _expandedTileIndex, builder: (context, expandedIndex, child) { const tileIndex = 2; return ExpansionTile( - key: ValueKey('feedDecoratorsTile_$expandedIndex'), - title: Text(l10n.feedDecoratorsTitle), - childrenPadding: const EdgeInsetsDirectional.only( - start: AppSpacing.lg, - top: AppSpacing.md, - bottom: AppSpacing.md, - ), - expandedCrossAxisAlignment: CrossAxisAlignment.start, + key: ValueKey('feedTile_$expandedIndex'), + title: Text(l10n.feedTab), onExpansionChanged: (isExpanded) { _expandedTileIndex.value = isExpanded ? tileIndex : null; }, initiallyExpanded: expandedIndex == tileIndex, children: [ - Text( - l10n.feedDecoratorsDescription, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of( - context, - ).colorScheme.onSurface.withOpacity(0.7), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.lg, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: AppSpacing.md), + Text( + l10n.feedItemClickBehaviorTitle, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: AppSpacing.xs), + Text( + l10n.feedItemClickBehaviorDescription, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurface.withOpacity(0.7), + ), + ), + const SizedBox(height: AppSpacing.md), + SegmentedButton( + segments: FeedItemClickBehavior.values + .where( + (b) => b != FeedItemClickBehavior.defaultBehavior, + ) + .map( + (behavior) => + ButtonSegment( + value: behavior, + label: Text(behavior.l10n(context)), + ), + ) + .toList(), + selected: { + widget.remoteConfig.features.feed.itemClickBehavior, + }, + onSelectionChanged: (newSelection) { + widget.onConfigChanged( + widget.remoteConfig.copyWith( + features: widget.remoteConfig.features.copyWith( + feed: widget.remoteConfig.features.feed + .copyWith( + itemClickBehavior: newSelection.first, + ), + ), + ), + ); + }, + ), + ], ), ), const SizedBox(height: AppSpacing.lg), - for (final decoratorType in FeedDecoratorType.values) - Padding( - padding: const EdgeInsets.only(bottom: AppSpacing.md), - child: ExpansionTile( - title: Text(decoratorType.l10n(context)), - childrenPadding: const EdgeInsetsDirectional.only( - start: AppSpacing.xl, - top: AppSpacing.md, - bottom: AppSpacing.md, + ExpansionTile( + title: Text(l10n.feedDecoratorsTitle), + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.feedDecoratorsDescription, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurface.withOpacity(0.7), ), - expandedCrossAxisAlignment: CrossAxisAlignment.start, - children: [ - FeedDecoratorForm( - decoratorType: decoratorType, - remoteConfig: widget.remoteConfig, - onConfigChanged: widget.onConfigChanged, - ), - ], ), - ), + const SizedBox(height: AppSpacing.lg), + for (final decoratorType in FeedDecoratorType.values) + Padding( + padding: const EdgeInsets.only(bottom: AppSpacing.md), + child: ExpansionTile( + title: Text(decoratorType.l10n(context)), + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.xl, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, + children: [ + FeedDecoratorForm( + decoratorType: decoratorType, + remoteConfig: widget.remoteConfig, + onConfigChanged: widget.onConfigChanged, + ), + ], + ), + ), + ], + ), ], ); }, From e979e8ddb7936115463c8b1cc3266fd786b5bc53 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 07:49:55 +0100 Subject: [PATCH 05/43] refactor(l10n): clarify descriptions for feed item click behavior - Update Arabic and English descriptions for feed item click behavior setting - Specify that the setting relates to browser selection when tapping a headline - Rename "Feed Item Click Behavior" to "Headline Click Behavior" for clarity --- lib/l10n/arb/app_ar.arb | 4 ++-- lib/l10n/arb/app_en.arb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 8d71aa9b..1b27de3a 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2070,11 +2070,11 @@ "@appLegalInformationTitle": { "description": "عنوان قسم المعلومات القانونية والعامة القابل للتوسيع." }, - "feedItemClickBehaviorTitle": "سلوك النقر على عنصر الموجز", + "feedItemClickBehaviorTitle": "سلوك النقر على العنوان", "@feedItemClickBehaviorTitle": { "description": "عنوان إعداد سلوك النقر على عنصر الموجز." }, - "feedItemClickBehaviorDescription": "اختر ما يحدث عندما ينقر المستخدم على عنوان في الموجز.", + "feedItemClickBehaviorDescription": "اختر المتصفح الذي يفتح عند نقر المستخدم على عنوان في الموجز.", "@feedItemClickbehaviorDescription": { "description": "وصف إعداد سلوك النقر على عنصر الموجز." }, diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 04f57e68..5f682106 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2066,11 +2066,11 @@ "@appLegalInformationTitle": { "description": "Title for the Legal & General Information expansion tile." }, - "feedItemClickBehaviorTitle": "Feed Item Click Behavior", + "feedItemClickBehaviorTitle": "Headline Click Behavior", "@feedItemClickBehaviorTitle": { "description": "Title for the feed item click behavior setting." }, - "feedItemClickBehaviorDescription": "Choose what happens when a user clicks on a headline in the feed.", + "feedItemClickBehaviorDescription": "Select which browser opens when a user taps on a headline in the feed.", "@feedItemClickbehaviorDescription": { "description": "Description for the feed item click behavior setting." }, From 5c7323273a7ddd747f08accd333fb49f070c97d3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 07:50:19 +0100 Subject: [PATCH 06/43] build(l10n); generate --- lib/l10n/app_localizations.dart | 4 ++-- lib/l10n/app_localizations_ar.dart | 4 ++-- lib/l10n/app_localizations_en.dart | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 31094530..231de2fe 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3077,13 +3077,13 @@ abstract class AppLocalizations { /// Title for the feed item click behavior setting. /// /// In en, this message translates to: - /// **'Feed Item Click Behavior'** + /// **'Headline Click Behavior'** String get feedItemClickBehaviorTitle; /// No description provided for @feedItemClickBehaviorDescription. /// /// In en, this message translates to: - /// **'Choose what happens when a user clicks on a headline in the feed.'** + /// **'Select which browser opens when a user taps on a headline in the feed.'** String get feedItemClickBehaviorDescription; /// Option for opening links in the app's internal browser. diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index da3343b8..ff077dc9 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -1651,11 +1651,11 @@ class AppLocalizationsAr extends AppLocalizations { String get appLegalInformationTitle => 'المعلومات القانونية والعامة'; @override - String get feedItemClickBehaviorTitle => 'سلوك النقر على عنصر الموجز'; + String get feedItemClickBehaviorTitle => 'سلوك النقر على العنوان'; @override String get feedItemClickBehaviorDescription => - 'اختر ما يحدث عندما ينقر المستخدم على عنوان في الموجز.'; + 'اختر المتصفح الذي يفتح عند نقر المستخدم على عنوان في الموجز.'; @override String get feedItemClickBehaviorInternalNavigation => 'متصفح داخل التطبيق'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index fcb191e7..8b301263 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1654,11 +1654,11 @@ class AppLocalizationsEn extends AppLocalizations { String get appLegalInformationTitle => 'Legal & General Information'; @override - String get feedItemClickBehaviorTitle => 'Feed Item Click Behavior'; + String get feedItemClickBehaviorTitle => 'Headline Click Behavior'; @override String get feedItemClickBehaviorDescription => - 'Choose what happens when a user clicks on a headline in the feed.'; + 'Select which browser opens when a user taps on a headline in the feed.'; @override String get feedItemClickBehaviorInternalNavigation => 'In-App Browser'; From 09380800ffdde14aa6d0023036848fcb77a38a60 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 07:50:29 +0100 Subject: [PATCH 07/43] refactor(app_configuration): improve layout using ExpansionTile - Replace Padding widget with ExpansionTile for better expandable content structure - Adjust padding and alignment for improved visual consistency - Simplify widget hierarchy, reducing unnecessary nesting --- .../view/tabs/features_configuration_tab.dart | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/app_configuration/view/tabs/features_configuration_tab.dart b/lib/app_configuration/view/tabs/features_configuration_tab.dart index a6d0dab7..3aa57105 100644 --- a/lib/app_configuration/view/tabs/features_configuration_tab.dart +++ b/lib/app_configuration/view/tabs/features_configuration_tab.dart @@ -129,29 +129,27 @@ class _FeaturesConfigurationTabState extends State { }, initiallyExpanded: expandedIndex == tileIndex, children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: AppSpacing.lg, + ExpansionTile( + title: Text(l10n.feedItemClickBehaviorTitle), + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: AppSpacing.md), - Text( - l10n.feedItemClickBehaviorTitle, - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: AppSpacing.xs), - Text( - l10n.feedItemClickBehaviorDescription, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of( - context, - ).colorScheme.onSurface.withOpacity(0.7), - ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.feedItemClickBehaviorDescription, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurface.withOpacity(0.7), ), - const SizedBox(height: AppSpacing.md), - SegmentedButton( + ), + const SizedBox(height: AppSpacing.lg), + Align( + alignment: AlignmentDirectional.centerStart, + child: SegmentedButton( segments: FeedItemClickBehavior.values .where( (b) => b != FeedItemClickBehavior.defaultBehavior, @@ -180,8 +178,8 @@ class _FeaturesConfigurationTabState extends State { ); }, ), - ], - ), + ), + ], ), const SizedBox(height: AppSpacing.lg), ExpansionTile( From fef7552d2a87961fd385460e6b7f5ea02b247570 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 07:51:38 +0100 Subject: [PATCH 08/43] fix(l10n): refine feed item click behavior title translations - Update Arabic translation from "Headline Click Behavior" to "Feed Item Click Behavior" - Update English description to match the new title --- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_ar.dart | 2 +- lib/l10n/app_localizations_en.dart | 2 +- lib/l10n/arb/app_ar.arb | 2 +- lib/l10n/arb/app_en.arb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 231de2fe..1ce1e21e 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3077,7 +3077,7 @@ abstract class AppLocalizations { /// Title for the feed item click behavior setting. /// /// In en, this message translates to: - /// **'Headline Click Behavior'** + /// **'Feed Item Click Behavior'** String get feedItemClickBehaviorTitle; /// No description provided for @feedItemClickBehaviorDescription. diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index ff077dc9..d54fd5bc 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -1651,7 +1651,7 @@ class AppLocalizationsAr extends AppLocalizations { String get appLegalInformationTitle => 'المعلومات القانونية والعامة'; @override - String get feedItemClickBehaviorTitle => 'سلوك النقر على العنوان'; + String get feedItemClickBehaviorTitle => 'سلوك النقر على عنصر الموجز'; @override String get feedItemClickBehaviorDescription => diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 8b301263..281a3a6f 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1654,7 +1654,7 @@ class AppLocalizationsEn extends AppLocalizations { String get appLegalInformationTitle => 'Legal & General Information'; @override - String get feedItemClickBehaviorTitle => 'Headline Click Behavior'; + String get feedItemClickBehaviorTitle => 'Feed Item Click Behavior'; @override String get feedItemClickBehaviorDescription => diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 1b27de3a..07deb3fb 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2070,7 +2070,7 @@ "@appLegalInformationTitle": { "description": "عنوان قسم المعلومات القانونية والعامة القابل للتوسيع." }, - "feedItemClickBehaviorTitle": "سلوك النقر على العنوان", + "feedItemClickBehaviorTitle": "سلوك النقر على عنصر الموجز", "@feedItemClickBehaviorTitle": { "description": "عنوان إعداد سلوك النقر على عنصر الموجز." }, diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 5f682106..270fa0c3 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2066,7 +2066,7 @@ "@appLegalInformationTitle": { "description": "Title for the Legal & General Information expansion tile." }, - "feedItemClickBehaviorTitle": "Headline Click Behavior", + "feedItemClickBehaviorTitle": "Feed Item Click Behavior", "@feedItemClickBehaviorTitle": { "description": "Title for the feed item click behavior setting." }, From 7c4e5f77ad2388d69540b52f33ec344362173699 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 08:16:24 +0100 Subject: [PATCH 09/43] feat(app_configuration): enhance visual structure of expansion tiles - Add consistent padding to expansion tile children - Align expansion tile content to start - Improve layout consistency across configuration tabs --- .../view/tabs/app_configuration_tab.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/app_configuration/view/tabs/app_configuration_tab.dart b/lib/app_configuration/view/tabs/app_configuration_tab.dart index fd5d81f9..484e0cf0 100644 --- a/lib/app_configuration/view/tabs/app_configuration_tab.dart +++ b/lib/app_configuration/view/tabs/app_configuration_tab.dart @@ -81,6 +81,12 @@ class _AppConfigurationTabState extends State { _expandedTileIndex.value = isExpanded ? tileIndex : null; }, initiallyExpanded: expandedIndex == tileIndex, + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, children: [ UpdateConfigForm( remoteConfig: widget.remoteConfig, @@ -104,6 +110,12 @@ class _AppConfigurationTabState extends State { _expandedTileIndex.value = isExpanded ? tileIndex : null; }, initiallyExpanded: expandedIndex == tileIndex, + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, children: [ GeneralAppConfigForm( remoteConfig: widget.remoteConfig, From 911e0a3910e452cb286c2d794cd3199b10e5aa23 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 08:38:15 +0100 Subject: [PATCH 10/43] style(app_configuration): enhance ExpansionTile appearance - Add consistent children padding to ExpansionTiles - Set uniform expandedCrossAxisAlignment for all ExpansionTiles - Improve visual consistency across different configuration sections --- .../view/tabs/features_configuration_tab.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/app_configuration/view/tabs/features_configuration_tab.dart b/lib/app_configuration/view/tabs/features_configuration_tab.dart index 3aa57105..18a17739 100644 --- a/lib/app_configuration/view/tabs/features_configuration_tab.dart +++ b/lib/app_configuration/view/tabs/features_configuration_tab.dart @@ -67,6 +67,12 @@ class _FeaturesConfigurationTabState extends State { _expandedTileIndex.value = isExpanded ? tileIndex : null; }, initiallyExpanded: expandedIndex == tileIndex, + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, children: [ AdConfigForm( remoteConfig: widget.remoteConfig, @@ -105,6 +111,12 @@ class _FeaturesConfigurationTabState extends State { _expandedTileIndex.value = isExpanded ? tileIndex : null; }, initiallyExpanded: expandedIndex == tileIndex, + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, children: [ PushNotificationSettingsForm( remoteConfig: widget.remoteConfig, @@ -128,6 +140,12 @@ class _FeaturesConfigurationTabState extends State { _expandedTileIndex.value = isExpanded ? tileIndex : null; }, initiallyExpanded: expandedIndex == tileIndex, + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, children: [ ExpansionTile( title: Text(l10n.feedItemClickBehaviorTitle), From 406cc90fbb6515da9cd7f48cfd9ad03a681205a1 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 08:38:27 +0100 Subject: [PATCH 11/43] feat(app_configuration): add TabBar for ad platform selection - Replace ExpansionTile with TabBar for selecting ad platforms - Implement TabController for handling tab navigation - Update ad unit identifier fields to use TabBarView - Adjust layout and styling for improved usability --- .../widgets/ad_platform_config_form.dart | 260 ++++++++++-------- 1 file changed, 140 insertions(+), 120 deletions(-) diff --git a/lib/app_configuration/widgets/ad_platform_config_form.dart b/lib/app_configuration/widgets/ad_platform_config_form.dart index 475dd00d..fbd011d6 100644 --- a/lib/app_configuration/widgets/ad_platform_config_form.dart +++ b/lib/app_configuration/widgets/ad_platform_config_form.dart @@ -27,15 +27,30 @@ class AdPlatformConfigForm extends StatefulWidget { State createState() => _AdPlatformConfigFormState(); } -class _AdPlatformConfigFormState extends State { +class _AdPlatformConfigFormState extends State + with SingleTickerProviderStateMixin { late AdPlatformType _selectedPlatform; late Map> - _platformAdIdentifierControllers; + _platformAdIdentifierControllers; + late final TabController _tabController; @override void initState() { super.initState(); _selectedPlatform = widget.remoteConfig.features.ads.primaryAdPlatform; + _tabController = TabController( + length: AdPlatformType.values.length, + vsync: this, + ); + _tabController.index = AdPlatformType.values.indexOf(_selectedPlatform); + + _tabController.addListener(() { + if (!_tabController.indexIsChanging) { + setState( + () => _selectedPlatform = AdPlatformType.values[_tabController.index], + ); + } + }); _initializeControllers(); } @@ -53,33 +68,18 @@ class _AdPlatformConfigFormState extends State { for (final platform in AdPlatformType.values) platform: { 'nativeAdId': TextEditingController( - text: - widget - .remoteConfig - .features - .ads - .platformAdIdentifiers[platform] - ?.nativeAdId ?? + text: widget.remoteConfig.features.ads + .platformAdIdentifiers[platform]?.nativeAdId ?? '', ), 'bannerAdId': TextEditingController( - text: - widget - .remoteConfig - .features - .ads - .platformAdIdentifiers[platform] - ?.bannerAdId ?? + text: widget.remoteConfig.features.ads + .platformAdIdentifiers[platform]?.bannerAdId ?? '', ), 'interstitialAdId': TextEditingController( - text: - widget - .remoteConfig - .features - .ads - .platformAdIdentifiers[platform] - ?.interstitialAdId ?? + text: widget.remoteConfig.features.ads + .platformAdIdentifiers[platform]?.interstitialAdId ?? '', ), }, @@ -117,6 +117,7 @@ class _AdPlatformConfigFormState extends State { @override void dispose() { + _tabController.dispose(); for (final platformControllers in _platformAdIdentifierControllers.values) { for (final controller in platformControllers.values) { controller.dispose(); @@ -133,65 +134,55 @@ class _AdPlatformConfigFormState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Primary Ad Platform Selection - ExpansionTile( - title: Text(l10n.primaryAdPlatformTitle), - childrenPadding: const EdgeInsetsDirectional.only( - start: AppSpacing.lg, - top: AppSpacing.md, - bottom: AppSpacing.md, - ), - expandedCrossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l10n.primaryAdPlatformDescription, - style: Theme.of(context).textTheme.bodySmall?.copyWith( + Text( + l10n.primaryAdPlatformTitle, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: AppSpacing.xs), + Text( + l10n.primaryAdPlatformDescription, + style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ), - textAlign: TextAlign.start, + textAlign: TextAlign.start, + ), + const SizedBox(height: AppSpacing.lg), + Align( + alignment: AlignmentDirectional.centerStart, + child: SegmentedButton( + style: SegmentedButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.zero, + ), ), - const SizedBox(height: AppSpacing.lg), - Align( - alignment: AlignmentDirectional.centerStart, - child: SegmentedButton( - style: SegmentedButton.styleFrom( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.zero, + segments: AdPlatformType.values + .map( + (type) => ButtonSegment( + value: type, + label: Text(type.l10n(context)), ), - ), - segments: AdPlatformType.values - .where( - (type) => type != AdPlatformType.demo, - ) - .map( - (type) => ButtonSegment( - value: type, - label: Text(type.l10n(context)), - ), - ) - .toList(), - selected: {_selectedPlatform}, - onSelectionChanged: (newSelection) { - setState(() { - _selectedPlatform = newSelection.first; - }); - widget.onConfigChanged( - widget.remoteConfig.copyWith( - features: widget.remoteConfig.features.copyWith( - ads: adConfig.copyWith( - primaryAdPlatform: newSelection.first, - ), - ), + ) + .toList(), + selected: {_selectedPlatform}, + onSelectionChanged: (newSelection) { + setState(() { + _selectedPlatform = newSelection.first; + _tabController.index = + AdPlatformType.values.indexOf(_selectedPlatform); + }); + widget.onConfigChanged( + widget.remoteConfig.copyWith( + features: widget.remoteConfig.features.copyWith( + ads: adConfig.copyWith( + primaryAdPlatform: newSelection.first, ), - ); - }, - ), - ), - ], + ), + ), + ); + }, + ), ), const SizedBox(height: AppSpacing.lg), - - // Ad Unit Identifiers ExpansionTile( title: Text(l10n.adUnitIdentifiersTitle), childrenPadding: const EdgeInsetsDirectional.only( @@ -201,23 +192,50 @@ class _AdPlatformConfigFormState extends State { ), expandedCrossAxisAlignment: CrossAxisAlignment.start, children: [ + Align( + alignment: AlignmentDirectional.centerStart, + child: SizedBox( + height: kTextTabBarHeight, + child: TabBar( + controller: _tabController, + tabAlignment: TabAlignment.start, + isScrollable: true, + tabs: AdPlatformType.values + .map((platform) => Tab(text: platform.l10n(context))) + .toList(), + ), + ), + ), + const SizedBox(height: AppSpacing.lg), Text( l10n.adUnitIdentifiersDescription, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), - ), + color: Theme.of(context) + .colorScheme + .onSurface + .withOpacity(0.7), + ), textAlign: TextAlign.start, ), const SizedBox(height: AppSpacing.lg), - _buildAdUnitIdentifierFields( - context, - l10n, - _selectedPlatform, - adConfig, + SizedBox( + height: 300, // Adjust height as needed for the content + child: TabBarView( + controller: _tabController, + children: AdPlatformType.values + .map( + (platform) => _buildAdUnitIdentifierFields( + context, + l10n, + platform, + adConfig, + ), + ) + .toList(), + ), ), ], ), - const SizedBox(height: AppSpacing.lg), ], ); } @@ -234,12 +252,10 @@ class _AdPlatformConfigFormState extends State { void updatePlatformIdentifiers(String key, String? value) { final newIdentifiers = platformIdentifiers.copyWith( - nativeAdId: key == 'nativeAdId' - ? value - : platformIdentifiers.nativeAdId, - bannerAdId: key == 'bannerAdId' - ? value - : platformIdentifiers.bannerAdId, + nativeAdId: + key == 'nativeAdId' ? value : platformIdentifiers.nativeAdId, + bannerAdId: + key == 'bannerAdId' ? value : platformIdentifiers.bannerAdId, interstitialAdId: key == 'interstitialAdId' ? value : platformIdentifiers.interstitialAdId, @@ -247,12 +263,12 @@ class _AdPlatformConfigFormState extends State { final newPlatformAdIdentifiers = Map.from( - config.platformAdIdentifiers, - )..update( - platform, - (_) => newIdentifiers, - ifAbsent: () => newIdentifiers, - ); + config.platformAdIdentifiers, + )..update( + platform, + (_) => newIdentifiers, + ifAbsent: () => newIdentifiers, + ); widget.onConfigChanged( widget.remoteConfig.copyWith( @@ -265,31 +281,35 @@ class _AdPlatformConfigFormState extends State { ); } - return Column( - children: [ - AppConfigTextField( - label: l10n.nativeAdIdLabel, - description: l10n.nativeAdIdDescription, - value: platformIdentifiers.nativeAdId, - onChanged: (value) => updatePlatformIdentifiers('nativeAdId', value), - controller: controllers['nativeAdId'], - ), - AppConfigTextField( - label: l10n.bannerAdIdLabel, - description: l10n.bannerAdIdDescription, - value: platformIdentifiers.bannerAdId, - onChanged: (value) => updatePlatformIdentifiers('bannerAdId', value), - controller: controllers['bannerAdId'], - ), - AppConfigTextField( - label: l10n.interstitialAdIdLabel, - description: l10n.interstitialAdIdDescription, - value: platformIdentifiers.interstitialAdId, - onChanged: (value) => - updatePlatformIdentifiers('interstitialAdId', value), - controller: controllers['interstitialAdId'], - ), - ], + return SingleChildScrollView( + child: Column( + children: [ + AppConfigTextField( + label: l10n.nativeAdIdLabel, + description: l10n.nativeAdIdDescription, + value: platformIdentifiers.nativeAdId, + onChanged: (value) => + updatePlatformIdentifiers('nativeAdId', value), + controller: controllers['nativeAdId'], + ), + AppConfigTextField( + label: l10n.bannerAdIdLabel, + description: l10n.bannerAdIdDescription, + value: platformIdentifiers.bannerAdId, + onChanged: (value) => + updatePlatformIdentifiers('bannerAdId', value), + controller: controllers['bannerAdId'], + ), + AppConfigTextField( + label: l10n.interstitialAdIdLabel, + description: l10n.interstitialAdIdDescription, + value: platformIdentifiers.interstitialAdId, + onChanged: (value) => + updatePlatformIdentifiers('interstitialAdId', value), + controller: controllers['interstitialAdId'], + ), + ], + ), ); } } From 4176332c4f7a00c429c8ab5d80dfcfc8a88aa837 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 08:38:41 +0100 Subject: [PATCH 12/43] style(app_configuration): improve layout of expansion tile children - Add children padding to create more space between ads and align with account settings - Set expanded cross axis alignment to start to align children to the left --- lib/app_configuration/widgets/feed_ad_settings_form.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/app_configuration/widgets/feed_ad_settings_form.dart b/lib/app_configuration/widgets/feed_ad_settings_form.dart index 26629b3a..d0c53081 100644 --- a/lib/app_configuration/widgets/feed_ad_settings_form.dart +++ b/lib/app_configuration/widgets/feed_ad_settings_form.dart @@ -138,6 +138,12 @@ class _FeedAdSettingsFormState extends State return ExpansionTile( title: Text(l10n.feedAdSettingsTitle), + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, children: [ SwitchListTile( title: Text(l10n.enableFeedAdsLabel), From c6e6d6080356f6d237ce962d8604eae9e175bef5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 08:38:53 +0100 Subject: [PATCH 13/43] fix(app_configuration): improve feed decorator form layout - Replace Padding with Expanded and SingleChildScrollView for daysBetweenViews field - Enhances scrollability and layout flexibility in feed decorator form --- .../widgets/feed_decorator_form.dart | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/lib/app_configuration/widgets/feed_decorator_form.dart b/lib/app_configuration/widgets/feed_decorator_form.dart index fae03eb4..7d334fd7 100644 --- a/lib/app_configuration/widgets/feed_decorator_form.dart +++ b/lib/app_configuration/widgets/feed_decorator_form.dart @@ -283,41 +283,43 @@ class _FeedDecoratorFormState extends State : null, ), if (roleConfig != null) - Padding( - padding: const EdgeInsets.symmetric( - horizontal: AppSpacing.lg, - vertical: AppSpacing.sm, - ), - child: AppConfigIntField( - label: l10n.daysBetweenViewsLabel, - description: l10n.daysBetweenViewsDescription, - value: roleConfig.daysBetweenViews, - onChanged: (value) { - final newRoleConfig = roleConfig.copyWith( - daysBetweenViews: value, - ); - final newVisibleTo = - Map.from( - decoratorConfig.visibleTo, - )..[role] = newRoleConfig; - final newDecoratorConfig = decoratorConfig.copyWith( - visibleTo: newVisibleTo, - ); - final newDecorators = - Map.from( - widget.remoteConfig.features.feed.decorators, - )..[widget.decoratorType] = newDecoratorConfig; - widget.onConfigChanged( - widget.remoteConfig.copyWith( - features: widget.remoteConfig.features.copyWith( - feed: widget.remoteConfig.features.feed.copyWith( - decorators: newDecorators, + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.lg, + vertical: AppSpacing.sm, + ), + child: AppConfigIntField( + label: l10n.daysBetweenViewsLabel, + description: l10n.daysBetweenViewsDescription, + value: roleConfig.daysBetweenViews, + onChanged: (value) { + final newRoleConfig = roleConfig.copyWith( + daysBetweenViews: value, + ); + final newVisibleTo = + Map.from( + decoratorConfig.visibleTo, + )..[role] = newRoleConfig; + final newDecoratorConfig = decoratorConfig.copyWith( + visibleTo: newVisibleTo, + ); + final newDecorators = + Map.from( + widget.remoteConfig.features.feed.decorators, + )..[widget.decoratorType] = newDecoratorConfig; + widget.onConfigChanged( + widget.remoteConfig.copyWith( + features: widget.remoteConfig.features.copyWith( + feed: widget.remoteConfig.features.feed.copyWith( + decorators: newDecorators, + ), ), ), - ), - ); - }, - controller: _roleControllers[role], + ); + }, + controller: _roleControllers[role], + ), ), ), ], From d6d1fc21903e9b9014094d37f9de785f582b0bb2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 08:39:09 +0100 Subject: [PATCH 14/43] style(app_configuration): enhance navigation ad settings form layout - Add children padding to ExpansionTile for better visual spacing - Align expansion tile content to start for improved readability --- .../widgets/navigation_ad_settings_form.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/app_configuration/widgets/navigation_ad_settings_form.dart b/lib/app_configuration/widgets/navigation_ad_settings_form.dart index 9dc382bd..93c22258 100644 --- a/lib/app_configuration/widgets/navigation_ad_settings_form.dart +++ b/lib/app_configuration/widgets/navigation_ad_settings_form.dart @@ -115,6 +115,12 @@ class _NavigationAdSettingsFormState extends State return ExpansionTile( title: Text(l10n.navigationAdConfigTitle), + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, children: [ SwitchListTile( title: Text(l10n.enableNavigationAdsLabel), From 1faa4b858c3552c9b3eadc79478c2422099cfaf4 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 08:40:02 +0100 Subject: [PATCH 15/43] feat(app_configuration): remove demo option from ad platform config - Remove AdPlatformType.demo from segments, tabs, --- .../widgets/ad_platform_config_form.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/app_configuration/widgets/ad_platform_config_form.dart b/lib/app_configuration/widgets/ad_platform_config_form.dart index fbd011d6..c1e9ac04 100644 --- a/lib/app_configuration/widgets/ad_platform_config_form.dart +++ b/lib/app_configuration/widgets/ad_platform_config_form.dart @@ -156,6 +156,9 @@ class _AdPlatformConfigFormState extends State ), ), segments: AdPlatformType.values + .where( + (type) => type != AdPlatformType.demo, + ) .map( (type) => ButtonSegment( value: type, @@ -201,6 +204,9 @@ class _AdPlatformConfigFormState extends State tabAlignment: TabAlignment.start, isScrollable: true, tabs: AdPlatformType.values + .where( + (type) => type != AdPlatformType.demo, + ) .map((platform) => Tab(text: platform.l10n(context))) .toList(), ), @@ -223,6 +229,9 @@ class _AdPlatformConfigFormState extends State child: TabBarView( controller: _tabController, children: AdPlatformType.values + .where( + (type) => type != AdPlatformType.demo, + ) .map( (platform) => _buildAdUnitIdentifierFields( context, From b47bba9ddfcebbd5bd2944eaf1415e28f6e14bde Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 08:40:17 +0100 Subject: [PATCH 16/43] refactor(app_configuration): remove SingleChildScrollView from push notification settings - Remove SingleChildScrollView and replace it with a Column - Adjust padding and scrolling behavior in the parent widget - This change improves the layout structure and may enhance performance --- .../push_notification_settings_form.dart | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/lib/app_configuration/widgets/push_notification_settings_form.dart b/lib/app_configuration/widgets/push_notification_settings_form.dart index 8c418d14..38db1155 100644 --- a/lib/app_configuration/widgets/push_notification_settings_form.dart +++ b/lib/app_configuration/widgets/push_notification_settings_form.dart @@ -29,31 +29,28 @@ class PushNotificationSettingsForm extends StatelessWidget { final features = remoteConfig.features; final pushConfig = features.pushNotifications; - return SingleChildScrollView( - padding: const EdgeInsets.all(AppSpacing.lg), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SwitchListTile( - title: Text(l10n.pushNotificationSystemStatusTitle), - subtitle: Text(l10n.pushNotificationSystemStatusDescription), - value: pushConfig.enabled, - onChanged: (value) { - onConfigChanged( - remoteConfig.copyWith( - features: features.copyWith( - pushNotifications: pushConfig.copyWith(enabled: value), - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SwitchListTile( + title: Text(l10n.pushNotificationSystemStatusTitle), + subtitle: Text(l10n.pushNotificationSystemStatusDescription), + value: pushConfig.enabled, + onChanged: (value) { + onConfigChanged( + remoteConfig.copyWith( + features: features.copyWith( + pushNotifications: pushConfig.copyWith(enabled: value), ), - ); - }, - ), - const SizedBox(height: AppSpacing.lg), - _buildPrimaryProviderSection(context, l10n, pushConfig), - const SizedBox(height: AppSpacing.lg), - _buildDeliveryTypesSection(context, l10n, pushConfig), - ], - ), + ), + ); + }, + ), + const SizedBox(height: AppSpacing.lg), + _buildPrimaryProviderSection(context, l10n, pushConfig), + const SizedBox(height: AppSpacing.lg), + _buildDeliveryTypesSection(context, l10n, pushConfig), + ], ); } From df7bae577aef8ac40a7dedc89c20afe362b6fb10 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 08:49:46 +0100 Subject: [PATCH 17/43] style(ad_config_form): add horizontal padding to form content - Import ui_kit package for consistent styling - Add horizontal padding of 16px (AppSpacing.md) to the formstyle(ad content _config_form- Improve): wrap layout consistency form in with other padding for widgets consistent spacing in - Add horizontal the padding app of medium size --- .../widgets/ad_config_form.dart | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/app_configuration/widgets/ad_config_form.dart b/lib/app_configuration/widgets/ad_config_form.dart index a74323e8..dcf64ead 100644 --- a/lib/app_configuration/widgets/ad_config_form.dart +++ b/lib/app_configuration/widgets/ad_config_form.dart @@ -1,6 +1,7 @@ import 'package:core/core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; +import 'package:ui_kit/ui_kit.dart'; /// {@template ad_config_form} /// A form widget for configuring global ad settings. @@ -27,23 +28,26 @@ class AdConfigForm extends StatelessWidget { final ads = features.ads; final l10n = AppLocalizationsX(context).l10n; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SwitchListTile( - title: Text(l10n.enableGlobalAdsLabel), - value: ads.enabled, - onChanged: (value) { - onConfigChanged( - remoteConfig.copyWith( - features: features.copyWith( - ads: ads.copyWith(enabled: value), + return Padding( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SwitchListTile( + title: Text(l10n.enableGlobalAdsLabel), + value: ads.enabled, + onChanged: (value) { + onConfigChanged( + remoteConfig.copyWith( + features: features.copyWith( + ads: ads.copyWith(enabled: value), + ), ), - ), - ); - }, - ), - ], + ); + }, + ), + ], + ), ); } } From ba6f28de87d15e1a53a87d33531b5d8f5b2b5b4a Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 08:50:15 +0100 Subject: [PATCH 18/43] refactor(app_configuration): improve ad platform config form UI and code structure - Replace nested AdPlatformType map initialization with a more readable format - Add ExpansionTile for better organization of ad platform configuration sections - Adjust padding and layout for improved visual hierarchy - Refactor code to improve readability and maintainability --- .../widgets/ad_platform_config_form.dart | 155 ++++++++++-------- 1 file changed, 88 insertions(+), 67 deletions(-) diff --git a/lib/app_configuration/widgets/ad_platform_config_form.dart b/lib/app_configuration/widgets/ad_platform_config_form.dart index c1e9ac04..ff776fd5 100644 --- a/lib/app_configuration/widgets/ad_platform_config_form.dart +++ b/lib/app_configuration/widgets/ad_platform_config_form.dart @@ -31,7 +31,7 @@ class _AdPlatformConfigFormState extends State with SingleTickerProviderStateMixin { late AdPlatformType _selectedPlatform; late Map> - _platformAdIdentifierControllers; + _platformAdIdentifierControllers; late final TabController _tabController; @override @@ -68,18 +68,33 @@ class _AdPlatformConfigFormState extends State for (final platform in AdPlatformType.values) platform: { 'nativeAdId': TextEditingController( - text: widget.remoteConfig.features.ads - .platformAdIdentifiers[platform]?.nativeAdId ?? + text: + widget + .remoteConfig + .features + .ads + .platformAdIdentifiers[platform] + ?.nativeAdId ?? '', ), 'bannerAdId': TextEditingController( - text: widget.remoteConfig.features.ads - .platformAdIdentifiers[platform]?.bannerAdId ?? + text: + widget + .remoteConfig + .features + .ads + .platformAdIdentifiers[platform] + ?.bannerAdId ?? '', ), 'interstitialAdId': TextEditingController( - text: widget.remoteConfig.features.ads - .platformAdIdentifiers[platform]?.interstitialAdId ?? + text: + widget + .remoteConfig + .features + .ads + .platformAdIdentifiers[platform] + ?.interstitialAdId ?? '', ), }, @@ -134,56 +149,63 @@ class _AdPlatformConfigFormState extends State return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - l10n.primaryAdPlatformTitle, - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: AppSpacing.xs), - Text( - l10n.primaryAdPlatformDescription, - style: Theme.of(context).textTheme.bodySmall?.copyWith( + ExpansionTile( + title: Text(l10n.primaryAdPlatformTitle), + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.primaryAdPlatformDescription, + style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ), - textAlign: TextAlign.start, - ), - const SizedBox(height: AppSpacing.lg), - Align( - alignment: AlignmentDirectional.centerStart, - child: SegmentedButton( - style: SegmentedButton.styleFrom( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.zero, - ), + textAlign: TextAlign.start, ), - segments: AdPlatformType.values - .where( - (type) => type != AdPlatformType.demo, - ) - .map( - (type) => ButtonSegment( - value: type, - label: Text(type.l10n(context)), - ), - ) - .toList(), - selected: {_selectedPlatform}, - onSelectionChanged: (newSelection) { - setState(() { - _selectedPlatform = newSelection.first; - _tabController.index = - AdPlatformType.values.indexOf(_selectedPlatform); - }); - widget.onConfigChanged( - widget.remoteConfig.copyWith( - features: widget.remoteConfig.features.copyWith( - ads: adConfig.copyWith( - primaryAdPlatform: newSelection.first, - ), + const SizedBox(height: AppSpacing.lg), + Align( + alignment: AlignmentDirectional.centerStart, + child: SegmentedButton( + style: SegmentedButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.zero, ), ), - ); - }, - ), + segments: AdPlatformType.values + .where( + (type) => type != AdPlatformType.demo, + ) + .map( + (type) => ButtonSegment( + value: type, + label: Text(type.l10n(context)), + ), + ) + .toList(), + selected: {_selectedPlatform}, + onSelectionChanged: (newSelection) { + setState(() { + _selectedPlatform = newSelection.first; + _tabController.index = AdPlatformType.values.indexOf( + _selectedPlatform, + ); + }); + widget.onConfigChanged( + widget.remoteConfig.copyWith( + features: widget.remoteConfig.features.copyWith( + ads: adConfig.copyWith( + primaryAdPlatform: newSelection.first, + ), + ), + ), + ); + }, + ), + ), + ], ), const SizedBox(height: AppSpacing.lg), ExpansionTile( @@ -216,11 +238,8 @@ class _AdPlatformConfigFormState extends State Text( l10n.adUnitIdentifiersDescription, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.7), - ), + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + ), textAlign: TextAlign.start, ), const SizedBox(height: AppSpacing.lg), @@ -261,10 +280,12 @@ class _AdPlatformConfigFormState extends State void updatePlatformIdentifiers(String key, String? value) { final newIdentifiers = platformIdentifiers.copyWith( - nativeAdId: - key == 'nativeAdId' ? value : platformIdentifiers.nativeAdId, - bannerAdId: - key == 'bannerAdId' ? value : platformIdentifiers.bannerAdId, + nativeAdId: key == 'nativeAdId' + ? value + : platformIdentifiers.nativeAdId, + bannerAdId: key == 'bannerAdId' + ? value + : platformIdentifiers.bannerAdId, interstitialAdId: key == 'interstitialAdId' ? value : platformIdentifiers.interstitialAdId, @@ -272,12 +293,12 @@ class _AdPlatformConfigFormState extends State final newPlatformAdIdentifiers = Map.from( - config.platformAdIdentifiers, - )..update( - platform, - (_) => newIdentifiers, - ifAbsent: () => newIdentifiers, - ); + config.platformAdIdentifiers, + )..update( + platform, + (_) => newIdentifiers, + ifAbsent: () => newIdentifiers, + ); widget.onConfigChanged( widget.remoteConfig.copyWith( From 4558eaf86ee49a6d66ee7f9f48f8a0fa6a75044a Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 08:50:54 +0100 Subject: [PATCH 19/43] refactor(localization): simplify ad platform titles - Update "primaryAdPlatformTitle" in both app_ar.arb and app_en.arb - Remove "Selection" from the title as it's already implied by the --- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_ar.dart | 2 +- lib/l10n/app_localizations_en.dart | 2 +- lib/l10n/arb/app_ar.arb | 2 +- lib/l10n/arb/app_en.arb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 1ce1e21e..3dc11060 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1937,7 +1937,7 @@ abstract class AppLocalizations { /// Title for the Primary Ad Platform Selection section /// /// In en, this message translates to: - /// **'Primary Ad Platform Selection'** + /// **'Primary Ad Platform'** String get primaryAdPlatformTitle; /// Description for the Primary Ad Platform Selection section diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index d54fd5bc..9aaec327 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -1023,7 +1023,7 @@ class AppLocalizationsAr extends AppLocalizations { String get adPlatformConfigurationTitle => 'إعدادات منصة الإعلان'; @override - String get primaryAdPlatformTitle => 'اختيار منصة الإعلانات الأساسية'; + String get primaryAdPlatformTitle => 'منصة الإعلانات الأساسية'; @override String get primaryAdPlatformDescription => diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 281a3a6f..bc6f6c31 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1021,7 +1021,7 @@ class AppLocalizationsEn extends AppLocalizations { String get adPlatformConfigurationTitle => 'Ad Platform Configuration'; @override - String get primaryAdPlatformTitle => 'Primary Ad Platform Selection'; + String get primaryAdPlatformTitle => 'Primary Ad Platform'; @override String get primaryAdPlatformDescription => diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 07deb3fb..2defe5e0 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -1282,7 +1282,7 @@ "@adPlatformConfigurationTitle": { "description": "عنوان قسم إعدادات منصة الإعلانات" }, - "primaryAdPlatformTitle": "اختيار منصة الإعلانات الأساسية", + "primaryAdPlatformTitle": "منصة الإعلانات الأساسية", "@primaryAdPlatformTitle": { "description": "عنوان قسم اختيار منصة الإعلانات الأساسية" }, diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 270fa0c3..8a9df76f 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1282,7 +1282,7 @@ "@adPlatformConfigurationTitle": { "description": "Title for the Ad Platform Configuration section" }, - "primaryAdPlatformTitle": "Primary Ad Platform Selection", + "primaryAdPlatformTitle": "Primary Ad Platform", "@primaryAdPlatformTitle": { "description": "Title for the Primary Ad Platform Selection section" }, From 8f0d21a8425faa397f95b2dfcf9286f837116069 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 08:55:12 +0100 Subject: [PATCH 20/43] refactor(app_configuration): remove horizontal padding from ad config form Removed the Padding widget with horizontal padding and adjusted the layout by changing the parent widget from Padding to Column. --- .../widgets/ad_config_form.dart | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/lib/app_configuration/widgets/ad_config_form.dart b/lib/app_configuration/widgets/ad_config_form.dart index dcf64ead..44fdfcc9 100644 --- a/lib/app_configuration/widgets/ad_config_form.dart +++ b/lib/app_configuration/widgets/ad_config_form.dart @@ -28,26 +28,23 @@ class AdConfigForm extends StatelessWidget { final ads = features.ads; final l10n = AppLocalizationsX(context).l10n; - return Padding( - padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SwitchListTile( - title: Text(l10n.enableGlobalAdsLabel), - value: ads.enabled, - onChanged: (value) { - onConfigChanged( - remoteConfig.copyWith( - features: features.copyWith( - ads: ads.copyWith(enabled: value), - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SwitchListTile( + title: Text(l10n.enableGlobalAdsLabel), + value: ads.enabled, + onChanged: (value) { + onConfigChanged( + remoteConfig.copyWith( + features: features.copyWith( + ads: ads.copyWith(enabled: value), ), - ); - }, - ), - ], - ), + ), + ); + }, + ), + ], ); } } From 037cac5dfa992e199549114cc2b1efe18837de1d Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:12:52 +0100 Subject: [PATCH 21/43] refactor(app_configuration): improve user configuration layout and expandability - Introduce nested ExpansionTiles for better organization - Rename 'User Content Limits' to 'User Limits' for clarity - Add consistent padding and alignment to expansion tile children - Remove redundant SizedBox between expansion tiles --- .../view/tabs/user_configuration_tab.dart | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/lib/app_configuration/view/tabs/user_configuration_tab.dart b/lib/app_configuration/view/tabs/user_configuration_tab.dart index 6a28176b..058d5ccb 100644 --- a/lib/app_configuration/view/tabs/user_configuration_tab.dart +++ b/lib/app_configuration/view/tabs/user_configuration_tab.dart @@ -47,45 +47,54 @@ class _UserConfigurationTabState extends State { return ListView( padding: const EdgeInsets.all(AppSpacing.lg), children: [ - // User Content Limits (Followed Items, Saved Headlines) ValueListenableBuilder( valueListenable: _expandedTileIndex, builder: (context, expandedIndex, child) { const tileIndex = 0; return ExpansionTile( - key: ValueKey('userContentLimitsTile_$expandedIndex'), - title: Text(l10n.userContentLimitsTitle), + key: ValueKey('userLimitsTile_$expandedIndex'), + title: Text(l10n.userLimitsTitle), onExpansionChanged: (isExpanded) { _expandedTileIndex.value = isExpanded ? tileIndex : null; }, initiallyExpanded: expandedIndex == tileIndex, + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, children: [ - UserLimitsConfigForm( - remoteConfig: widget.remoteConfig, - onConfigChanged: widget.onConfigChanged, + ExpansionTile( + title: Text(l10n.userContentLimitsTitle), + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, + children: [ + UserLimitsConfigForm( + remoteConfig: widget.remoteConfig, + onConfigChanged: widget.onConfigChanged, + ), + ], ), - ], - ); - }, - ), - const SizedBox(height: AppSpacing.lg), - - // Saved Filter Limits (Headline and Source) - ValueListenableBuilder( - valueListenable: _expandedTileIndex, - builder: (context, expandedIndex, child) { - const tileIndex = 1; - return ExpansionTile( - key: ValueKey('savedFilterLimitsTile_$expandedIndex'), - title: Text(l10n.savedFeedFilterLimitsTitle), - onExpansionChanged: (isExpanded) { - _expandedTileIndex.value = isExpanded ? tileIndex : null; - }, - initiallyExpanded: expandedIndex == tileIndex, - children: [ - SavedFilterLimitsSection( - remoteConfig: widget.remoteConfig, - onConfigChanged: widget.onConfigChanged, + const SizedBox(height: AppSpacing.lg), + ExpansionTile( + title: Text(l10n.savedFeedFilterLimitsTitle), + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, + children: [ + SavedFilterLimitsSection( + remoteConfig: widget.remoteConfig, + onConfigChanged: widget.onConfigChanged, + ), + ], ), ], ); From 8cb74d662b8f0855e30abf192abccbc36fac3aa2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:13:00 +0100 Subject: [PATCH 22/43] style(app_configuration): add padding to role limit widgets - Add horizontal padding of AppSpacing.lg and vertical padding of AppSpacing.sm to SingleChildScrollView widget - Improve visual appearance and usability of saved filter limits form --- lib/app_configuration/widgets/saved_filter_limits_form.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/app_configuration/widgets/saved_filter_limits_form.dart b/lib/app_configuration/widgets/saved_filter_limits_form.dart index ad926ffa..21a242b1 100644 --- a/lib/app_configuration/widgets/saved_filter_limits_form.dart +++ b/lib/app_configuration/widgets/saved_filter_limits_form.dart @@ -221,6 +221,10 @@ class _SavedFilterLimitsFormState extends State children: AppUserRole.values.map((role) { final limits = _getLimitsForRole(role); return SingleChildScrollView( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.lg, + vertical: AppSpacing.sm, + ), child: Column( children: [ AppConfigIntField( From 1c9b2128c67694fec0e4146347ca4ced81ad2945 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:13:25 +0100 Subject: [PATCH 23/43] refactor(app_configuration): improve expansion tile layout - Wrap Column with ValueListenableBuilder to reduce widget tree depth - Adjust indentation for better readability - Maintain original functionality and design --- .../widgets/saved_filter_limits_section.dart | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/lib/app_configuration/widgets/saved_filter_limits_section.dart b/lib/app_configuration/widgets/saved_filter_limits_section.dart index 69803edf..6ef236ed 100644 --- a/lib/app_configuration/widgets/saved_filter_limits_section.dart +++ b/lib/app_configuration/widgets/saved_filter_limits_section.dart @@ -47,13 +47,13 @@ class _SavedFilterLimitsSectionState extends State { Widget build(BuildContext context) { final l10n = AppLocalizationsX(context).l10n; - return Column( - children: [ - ValueListenableBuilder( - valueListenable: _expandedTileIndex, - builder: (context, expandedIndex, child) { - const tileIndex = 0; - return ExpansionTile( + return ValueListenableBuilder( + valueListenable: _expandedTileIndex, + builder: (context, expandedIndex, child) { + const tileIndex = 0; + return Column( + children: [ + ExpansionTile( key: ValueKey('savedHeadlineFilterLimitsTile_$expandedIndex'), title: Text(l10n.savedHeadlineFilterLimitsTitle), childrenPadding: const EdgeInsetsDirectional.only( @@ -82,47 +82,47 @@ class _SavedFilterLimitsSectionState extends State { filterType: SavedFilterType.headline, ), ], - ); - }, - ), - const SizedBox(height: AppSpacing.lg), - ValueListenableBuilder( - valueListenable: _expandedTileIndex, - builder: (context, expandedIndex, child) { - const tileIndex = 1; - return ExpansionTile( - key: ValueKey('savedSourceFilterLimitsTile_$expandedIndex'), - title: Text(l10n.savedSourceFilterLimitsTitle), - childrenPadding: const EdgeInsetsDirectional.only( - start: AppSpacing.lg, - top: AppSpacing.md, - bottom: AppSpacing.md, - ), - expandedCrossAxisAlignment: CrossAxisAlignment.start, - onExpansionChanged: (isExpanded) { - _expandedTileIndex.value = isExpanded ? tileIndex : null; - }, - initiallyExpanded: expandedIndex == tileIndex, - children: [ - Text( - l10n.savedSourceFilterLimitsDescription, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of( - context, - ).colorScheme.onSurface.withOpacity(0.7), + ), + const SizedBox(height: AppSpacing.lg), + ValueListenableBuilder( + valueListenable: _expandedTileIndex, + builder: (context, expandedIndex, child) { + const tileIndex = 1; + return ExpansionTile( + key: ValueKey('savedSourceFilterLimitsTile_$expandedIndex'), + title: Text(l10n.savedSourceFilterLimitsTitle), + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, ), - ), - const SizedBox(height: AppSpacing.lg), - SavedFilterLimitsForm( - remoteConfig: widget.remoteConfig, - onConfigChanged: widget.onConfigChanged, - filterType: SavedFilterType.source, - ), - ], - ); - }, - ), - ], + expandedCrossAxisAlignment: CrossAxisAlignment.start, + onExpansionChanged: (isExpanded) { + _expandedTileIndex.value = isExpanded ? tileIndex : null; + }, + initiallyExpanded: expandedIndex == tileIndex, + children: [ + Text( + l10n.savedSourceFilterLimitsDescription, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurface.withOpacity(0.7), + ), + ), + const SizedBox(height: AppSpacing.lg), + SavedFilterLimitsForm( + remoteConfig: widget.remoteConfig, + onConfigChanged: widget.onConfigChanged, + filterType: SavedFilterType.source, + ), + ], + ); + }, + ), + ], + ); + }, ); } } From 9b77746aa7a5222600c7b24cfda69031683b1291 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:16:35 +0100 Subject: [PATCH 24/43] style(app_configuration): remove horizontal padding from limits description text - Remove Padding widget wrapping the limits description text - Directly use Text widget for limits description - Maintains vertical spacing before and after the text --- .../widgets/user_limits_config_form.dart | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/app_configuration/widgets/user_limits_config_form.dart b/lib/app_configuration/widgets/user_limits_config_form.dart index 6916eb09..afb99d3d 100644 --- a/lib/app_configuration/widgets/user_limits_config_form.dart +++ b/lib/app_configuration/widgets/user_limits_config_form.dart @@ -109,13 +109,10 @@ class _UserLimitsConfigFormState extends State return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg), - child: Text( - l10n.userContentLimitsDescription, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), - ), + Text( + l10n.userContentLimitsDescription, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ), ), const SizedBox(height: AppSpacing.lg), From 2680dfc7bc8c7f01b5d0b4fe0bcdb996c5c8603a Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:16:44 +0100 Subject: [PATCH 25/43] feat(l10n): add Arabic and English translations for user limits - Add Arabic translation for "User Limits" and its description - Add English translation for "User Limits" and its description - Maintain consistent formatting for existing translation entries --- 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 2defe5e0..df09aa41 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2085,5 +2085,9 @@ "feedItemClickBehaviorExternalNavigation": "متصفح النظام", "@feedItemClickBehaviorExternalNavigation": { "description": "خيار لفتح الروابط في متصفح النظام الافتراضي للجهاز." + }, + "userLimitsTitle": "حدود المستخدم", + "@userLimitsTitle": { + "description": "عنوان قسم حدود المستخدم القابل للتوسيع." } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8a9df76f..6d4f17e2 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2081,5 +2081,9 @@ "feedItemClickBehaviorExternalNavigation": "System Browser", "@feedItemClickBehaviorExternalNavigation": { "description": "Option for opening links in the device's default system browser." + }, + "userLimitsTitle": "User Limits", + "@userLimitsTitle": { + "description": "Title for the User Limits expansion tile." } } \ No newline at end of file From 2514fc9b303e98e6e6eee77f624dccce929f20b8 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:17:00 +0100 Subject: [PATCH 26/43] build(l10n): generated --- 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 3dc11060..c7ac17c4 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3097,6 +3097,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'System Browser'** String get feedItemClickBehaviorExternalNavigation; + + /// Title for the User Limits expansion tile. + /// + /// In en, this message translates to: + /// **'User Limits'** + String get userLimitsTitle; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 9aaec327..5fad8445 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -1662,4 +1662,7 @@ class AppLocalizationsAr extends AppLocalizations { @override String get feedItemClickBehaviorExternalNavigation => 'متصفح النظام'; + + @override + String get userLimitsTitle => 'حدود المستخدم'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index bc6f6c31..0d5c6fef 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1665,4 +1665,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get feedItemClickBehaviorExternalNavigation => 'System Browser'; + + @override + String get userLimitsTitle => 'User Limits'; } From 8572ab281c0a5425c0a0db8bb7058a5312a84dcf Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:27:54 +0100 Subject: [PATCH 27/43] refactor(app_configuration): improve notification fields layout - Add separate TabController for notification subscription tabs - Rearrange notification fields layout to be more user-friendly - Increase height of headline filter form to accommodate new layout - Update tab bar to use scrollable design for better space utilization --- .../widgets/saved_filter_limits_form.dart | 77 +++++++++++++++---- 1 file changed, 63 insertions(+), 14 deletions(-) diff --git a/lib/app_configuration/widgets/saved_filter_limits_form.dart b/lib/app_configuration/widgets/saved_filter_limits_form.dart index 21a242b1..682f892a 100644 --- a/lib/app_configuration/widgets/saved_filter_limits_form.dart +++ b/lib/app_configuration/widgets/saved_filter_limits_form.dart @@ -41,7 +41,7 @@ class SavedFilterLimitsForm extends StatefulWidget { } class _SavedFilterLimitsFormState extends State - with SingleTickerProviderStateMixin { + with TickerProviderStateMixin { late TabController _tabController; // A nested map to hold controllers: Role -> Field -> Controller @@ -54,6 +54,10 @@ class _SavedFilterLimitsFormState extends State length: AppUserRole.values.length, vsync: this, ); + _notificationTabController = TabController( + length: PushNotificationSubscriptionDeliveryType.values.length, + vsync: this, + ); _initializeControllers(); } @@ -125,6 +129,7 @@ class _SavedFilterLimitsFormState extends State @override void dispose() { _tabController.dispose(); + _notificationTabController.dispose(); for (final roleControllers in _controllers.values) { for (final controller in roleControllers.values) { controller.dispose(); @@ -215,7 +220,7 @@ class _SavedFilterLimitsFormState extends State ), const SizedBox(height: AppSpacing.lg), SizedBox( - height: isHeadlineFilter ? 400 : 250, + height: isHeadlineFilter ? 500 : 250, child: TabBarView( controller: _tabController, children: AppUserRole.values.map((role) { @@ -244,7 +249,7 @@ class _SavedFilterLimitsFormState extends State controller: _controllers[role]!['pinned'], ), if (isHeadlineFilter) - ..._buildNotificationFields(l10n, role, limits), + _buildNotificationFields(l10n, role, limits), ], ), ); @@ -255,20 +260,64 @@ class _SavedFilterLimitsFormState extends State ); } - List _buildNotificationFields( + late final TabController _notificationTabController; + + Widget _buildNotificationFields( AppLocalizations l10n, AppUserRole role, SavedFilterLimits limits, ) { - return PushNotificationSubscriptionDeliveryType.values.map((type) { - final value = limits.notificationSubscriptions?[type] ?? 0; - return AppConfigIntField( - label: l10n.notificationSubscriptionLimitLabel, - description: type.l10n(context), - value: value, - onChanged: (newValue) => _onValueChanged(role, type.name, newValue), - controller: _controllers[role]!['notification_${type.name}'], - ); - }).toList(); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: AppSpacing.lg), + Text( + l10n.notificationSubscriptionLimitLabel, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: AppSpacing.xs), + Text( + l10n.notificationSubscriptionLimitDescription, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + ), + ), + const SizedBox(height: AppSpacing.md), + Align( + alignment: AlignmentDirectional.centerStart, + child: SizedBox( + height: kTextTabBarHeight, + child: TabBar( + controller: _notificationTabController, + tabAlignment: TabAlignment.start, + isScrollable: true, + tabs: PushNotificationSubscriptionDeliveryType.values + .map((type) => Tab(text: type.l10n(context))) + .toList(), + ), + ), + ), + const SizedBox(height: AppSpacing.lg), + SizedBox( + height: 150, + child: TabBarView( + controller: _notificationTabController, + children: PushNotificationSubscriptionDeliveryType.values.map( + (type) { + final value = limits.notificationSubscriptions?[type] ?? 0; + return AppConfigIntField( + label: '', + description: '', + value: value, + onChanged: (newValue) => + _onValueChanged(role, type.name, newValue), + controller: _controllers[role]!['notification_${type.name}'], + ); + }, + ).toList(), + ), + ), + ], + ); } } From 5aaee6b4b8748eb500a22c990b81f5dc38df2639 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:35:14 +0100 Subject: [PATCH 28/43] feat(app_configuration): add localized labels and descriptions for notification types - Add _getNotificationDescription method to provide localized descriptions - Update notification fields to use localized labels and descriptions - Improve user experience by providing context-specific information for each --- .../widgets/saved_filter_limits_form.dart | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/app_configuration/widgets/saved_filter_limits_form.dart b/lib/app_configuration/widgets/saved_filter_limits_form.dart index 682f892a..46c47429 100644 --- a/lib/app_configuration/widgets/saved_filter_limits_form.dart +++ b/lib/app_configuration/widgets/saved_filter_limits_form.dart @@ -260,6 +260,21 @@ class _SavedFilterLimitsFormState extends State ); } + String _getNotificationDescription( + BuildContext context, + PushNotificationSubscriptionDeliveryType type, + ) { + final l10n = AppLocalizationsX(context).l10n; + switch (type) { + case PushNotificationSubscriptionDeliveryType.breakingOnly: + return l10n.notificationSubscriptionBreakingOnlyDescription; + case PushNotificationSubscriptionDeliveryType.dailyDigest: + return l10n.notificationSubscriptionDailyDigestDescription; + case PushNotificationSubscriptionDeliveryType.weeklyRoundup: + return l10n.notificationSubscriptionWeeklyRoundupDescription; + } + } + late final TabController _notificationTabController; Widget _buildNotificationFields( @@ -306,8 +321,8 @@ class _SavedFilterLimitsFormState extends State (type) { final value = limits.notificationSubscriptions?[type] ?? 0; return AppConfigIntField( - label: '', - description: '', + label: type.l10n(context), + description: _getNotificationDescription(context, type), value: value, onChanged: (newValue) => _onValueChanged(role, type.name, newValue), From 9e5a82b70fd79aad6c7463240ae7313e6d497e81 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:36:25 +0100 Subject: [PATCH 29/43] feat(l10n): add descriptions for notification subscription limits - Add Arabic and English descriptions for breaking news, daily digest, and weekly roundup notification subscription limits - Update app_ar.arb and app_en.arb files with new localization entries --- 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 df09aa41..70b768a4 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2089,5 +2089,17 @@ "userLimitsTitle": "حدود المستخدم", "@userLimitsTitle": { "description": "عنوان قسم حدود المستخدم القابل للتوسيع." + }, + "notificationSubscriptionBreakingOnlyDescription": "حد الاشتراكات التي ترسل تنبيهات فورية للأخبار العاجلة للعناوين المطابقة.", + "@notificationSubscriptionBreakingOnlyDescription": { + "description": "وصف لحد اشتراك إشعارات الأخبار العاجلة." + }, + "notificationSubscriptionDailyDigestDescription": "حد الاشتراكات التي ترسل ملخصًا يوميًا للعناوين المطابقة.", + "@notificationSubscriptionDailyDigestDescription": { + "description": "وصف لحد اشتراك إشعارات الملخص اليومي." + }, + "notificationSubscriptionWeeklyRoundupDescription": "حد الاشتراكات التي ترسل ملخصًا أسبوعيًا للعناوين المطابقة.", + "@notificationSubscriptionWeeklyRoundupDescription": { + "description": "وصف لحد اشتراك إشعارات الحصاد الأسبوعي." } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 6d4f17e2..f68482bc 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2085,5 +2085,17 @@ "userLimitsTitle": "User Limits", "@userLimitsTitle": { "description": "Title for the User Limits expansion tile." + }, + "notificationSubscriptionBreakingOnlyDescription": "Limit for subscriptions that send immediate alerts for matching headlines.", + "@notificationSubscriptionBreakingOnlyDescription": { + "description": "Description for the breaking news notification subscription limit." + }, + "notificationSubscriptionDailyDigestDescription": "Limit for subscriptions that send a daily summary of matching headlines.", + "@notificationSubscriptionDailyDigestDescription": { + "description": "Description for the daily digest notification subscription limit." + }, + "notificationSubscriptionWeeklyRoundupDescription": "Limit for subscriptions that send a weekly summary of matching headlines.", + "@notificationSubscriptionWeeklyRoundupDescription": { + "description": "Description for the weekly roundup notification subscription limit." } } \ No newline at end of file From c771b94d4544c15eff62e1f7ae6f3dfa15dd6c15 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:36:36 +0100 Subject: [PATCH 30/43] build(l10n): generate --- lib/l10n/app_localizations.dart | 18 ++++++++++++++++++ lib/l10n/app_localizations_ar.dart | 12 ++++++++++++ lib/l10n/app_localizations_en.dart | 12 ++++++++++++ 3 files changed, 42 insertions(+) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index c7ac17c4..3376b49a 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3103,6 +3103,24 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'User Limits'** String get userLimitsTitle; + + /// Description for the breaking news notification subscription limit. + /// + /// In en, this message translates to: + /// **'Limit for subscriptions that send immediate alerts for matching headlines.'** + String get notificationSubscriptionBreakingOnlyDescription; + + /// Description for the daily digest notification subscription limit. + /// + /// In en, this message translates to: + /// **'Limit for subscriptions that send a daily summary of matching headlines.'** + String get notificationSubscriptionDailyDigestDescription; + + /// Description for the weekly roundup notification subscription limit. + /// + /// In en, this message translates to: + /// **'Limit for subscriptions that send a weekly summary of matching headlines.'** + String get notificationSubscriptionWeeklyRoundupDescription; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 5fad8445..62e76af5 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -1665,4 +1665,16 @@ class AppLocalizationsAr extends AppLocalizations { @override String get userLimitsTitle => 'حدود المستخدم'; + + @override + String get notificationSubscriptionBreakingOnlyDescription => + 'حد الاشتراكات التي ترسل تنبيهات فورية للأخبار العاجلة للعناوين المطابقة.'; + + @override + String get notificationSubscriptionDailyDigestDescription => + 'حد الاشتراكات التي ترسل ملخصًا يوميًا للعناوين المطابقة.'; + + @override + String get notificationSubscriptionWeeklyRoundupDescription => + 'حد الاشتراكات التي ترسل ملخصًا أسبوعيًا للعناوين المطابقة.'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 0d5c6fef..4704a6a6 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1668,4 +1668,16 @@ class AppLocalizationsEn extends AppLocalizations { @override String get userLimitsTitle => 'User Limits'; + + @override + String get notificationSubscriptionBreakingOnlyDescription => + 'Limit for subscriptions that send immediate alerts for matching headlines.'; + + @override + String get notificationSubscriptionDailyDigestDescription => + 'Limit for subscriptions that send a daily summary of matching headlines.'; + + @override + String get notificationSubscriptionWeeklyRoundupDescription => + 'Limit for subscriptions that send a weekly summary of matching headlines.'; } From 540c091cdb434bb2523f43acf2bb205d17af64d7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:39:33 +0100 Subject: [PATCH 31/43] build(l10n): generate --- lib/l10n/app_localizations_ar.dart | 2 +- lib/l10n/arb/app_ar.arb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 62e76af5..1cc0d0df 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -840,7 +840,7 @@ class AppLocalizationsAr extends AppLocalizations { 'عدد مرات ظهور الإعلان لهذا الدور المستخدم (على سبيل المثال، قيمة 5 تعني أنه يمكن وضع إعلان بعد كل 5 عناصر إخبارية).'; @override - String get savedFeedFilterLimitsTitle => 'حد المرشحات المحفوظة'; + String get savedFeedFilterLimitsTitle => 'حدود المرشحات المحفوظة'; @override String get savedFeedFilterLimitsDescription => diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 70b768a4..980c8d33 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -1038,7 +1038,7 @@ "@adFrequencyDescription": { "description": "وصف تكرار الإعلان" }, - "savedFeedFilterLimitsTitle": "حد المرشحات المحفوظة", + "savedFeedFilterLimitsTitle": "حدود المرشحات المحفوظة", "@savedFeedFilterLimitsTitle": { "description": "وصف لحد مرشحات موجز الأخبار المحفوظة" }, From 5988b7435def65a109b484d46b0a04fc1c0bc202 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:56:08 +0100 Subject: [PATCH 32/43] feat(l10n): add app status and updates localization - Add Arabic and English translations for app status and updates section - Include labels and descriptions for forced updates feature - Add translations for application URLs section --- lib/l10n/arb/app_ar.arb | 24 ++++++++++++++++++++---- lib/l10n/arb/app_en.arb | 24 ++++++++++++++++++++---- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 980c8d33..bfb9d57c 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2066,10 +2066,6 @@ "@appUpdateManagementTitle": { "description": "عنوان قسم إدارة تحديثات التطبيق القابل للتوسيع." }, - "appLegalInformationTitle": "المعلومات القانونية والعامة", - "@appLegalInformationTitle": { - "description": "عنوان قسم المعلومات القانونية والعامة القابل للتوسيع." - }, "feedItemClickBehaviorTitle": "سلوك النقر على عنصر الموجز", "@feedItemClickBehaviorTitle": { "description": "عنوان إعداد سلوك النقر على عنصر الموجز." @@ -2101,5 +2097,25 @@ "notificationSubscriptionWeeklyRoundupDescription": "حد الاشتراكات التي ترسل ملخصًا أسبوعيًا للعناوين المطابقة.", "@notificationSubscriptionWeeklyRoundupDescription": { "description": "وصف لحد اشتراك إشعارات الحصاد الأسبوعي." + }, + "appStatusAndUpdatesTitle": "حالة التطبيق والتحديثات", + "@appStatusAndUpdatesTitle": { + "description": "عنوان قسم حالة التطبيق والتحديثات القابل للتوسيع." + }, + "enableForcedUpdatesLabel": "تفعيل التحديثات الإجبارية", + "@enableForcedUpdatesLabel": { + "description": "تسمية مفتاح تفعيل التحديثات الإجبارية." + }, + "enableForcedUpdatesDescription": "عند التفعيل، يمكنك تحديد الحد الأدنى للإصدار المطلوب للتطبيق.", + "@enableForcedUpdatesDescription": { + "description": "وصف مفتاح تفعيل التحديثات الإجبارية." + }, + "appUrlsTitle": "روابط التطبيق", + "@appUrlsTitle": { + "description": "عنوان قسم روابط التطبيق القابل للتوسيع." + }, + "appUrlsDescription": "إدارة الروابط الخارجية والداخلية المستخدمة داخل التطبيق.", + "@appUrlsDescription": { + "description": "وصف قسم روابط التطبيق القابل للتوسيع." } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index f68482bc..8f36639c 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2062,10 +2062,6 @@ "@appUpdateManagementTitle": { "description": "Title for the Application Update Management expansion tile." }, - "appLegalInformationTitle": "Legal & General Information", - "@appLegalInformationTitle": { - "description": "Title for the Legal & General Information expansion tile." - }, "feedItemClickBehaviorTitle": "Feed Item Click Behavior", "@feedItemClickBehaviorTitle": { "description": "Title for the feed item click behavior setting." @@ -2097,5 +2093,25 @@ "notificationSubscriptionWeeklyRoundupDescription": "Limit for subscriptions that send a weekly summary of matching headlines.", "@notificationSubscriptionWeeklyRoundupDescription": { "description": "Description for the weekly roundup notification subscription limit." + }, + "appStatusAndUpdatesTitle": "App Status & Updates", + "@appStatusAndUpdatesTitle": { + "description": "Title for the App Status & Updates expansion tile." + }, + "enableForcedUpdatesLabel": "Enable Forced Updates", + "@enableForcedUpdatesLabel": { + "description": "Label for the switch to enable forced updates." + }, + "enableForcedUpdatesDescription": "When enabled, you can specify a minimum required version for the app.", + "@enableForcedUpdatesDescription": { + "description": "Description for the switch to enable forced updates." + }, + "appUrlsTitle": "Application URLs", + "@appUrlsTitle": { + "description": "Title for the Application URLs expansion tile." + }, + "appUrlsDescription": "Manage external and internal URLs used within the application.", + "@appUrlsDescription": { + "description": "Description for the Application URLs expansion tile." } } \ No newline at end of file From b92956d3bfd42c568861e5399d955d4ef58cabf3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 09:57:07 +0100 Subject: [PATCH 33/43] build(l10n): generate --- lib/l10n/app_localizations.dart | 36 +++++++++++++++++++++++++----- lib/l10n/app_localizations_ar.dart | 20 ++++++++++++++--- lib/l10n/app_localizations_en.dart | 20 ++++++++++++++--- 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 3376b49a..1b0dd34f 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3068,12 +3068,6 @@ abstract class AppLocalizations { /// **'Application Update Management'** String get appUpdateManagementTitle; - /// Title for the Legal & General Information expansion tile. - /// - /// In en, this message translates to: - /// **'Legal & General Information'** - String get appLegalInformationTitle; - /// Title for the feed item click behavior setting. /// /// In en, this message translates to: @@ -3121,6 +3115,36 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Limit for subscriptions that send a weekly summary of matching headlines.'** String get notificationSubscriptionWeeklyRoundupDescription; + + /// Title for the App Status & Updates expansion tile. + /// + /// In en, this message translates to: + /// **'App Status & Updates'** + String get appStatusAndUpdatesTitle; + + /// Label for the switch to enable forced updates. + /// + /// In en, this message translates to: + /// **'Enable Forced Updates'** + String get enableForcedUpdatesLabel; + + /// Description for the switch to enable forced updates. + /// + /// In en, this message translates to: + /// **'When enabled, you can specify a minimum required version for the app.'** + String get enableForcedUpdatesDescription; + + /// Title for the Application URLs expansion tile. + /// + /// In en, this message translates to: + /// **'Application URLs'** + String get appUrlsTitle; + + /// Description for the Application URLs expansion tile. + /// + /// In en, this message translates to: + /// **'Manage external and internal URLs used within the application.'** + String get appUrlsDescription; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 1cc0d0df..375cc947 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -1647,9 +1647,6 @@ class AppLocalizationsAr extends AppLocalizations { @override String get appUpdateManagementTitle => 'إدارة تحديثات التطبيق'; - @override - String get appLegalInformationTitle => 'المعلومات القانونية والعامة'; - @override String get feedItemClickBehaviorTitle => 'سلوك النقر على عنصر الموجز'; @@ -1677,4 +1674,21 @@ class AppLocalizationsAr extends AppLocalizations { @override String get notificationSubscriptionWeeklyRoundupDescription => 'حد الاشتراكات التي ترسل ملخصًا أسبوعيًا للعناوين المطابقة.'; + + @override + String get appStatusAndUpdatesTitle => 'حالة التطبيق والتحديثات'; + + @override + String get enableForcedUpdatesLabel => 'تفعيل التحديثات الإجبارية'; + + @override + String get enableForcedUpdatesDescription => + 'عند التفعيل، يمكنك تحديد الحد الأدنى للإصدار المطلوب للتطبيق.'; + + @override + String get appUrlsTitle => 'روابط التطبيق'; + + @override + String get appUrlsDescription => + 'إدارة الروابط الخارجية والداخلية المستخدمة داخل التطبيق.'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 4704a6a6..20ef20eb 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1650,9 +1650,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get appUpdateManagementTitle => 'Application Update Management'; - @override - String get appLegalInformationTitle => 'Legal & General Information'; - @override String get feedItemClickBehaviorTitle => 'Feed Item Click Behavior'; @@ -1680,4 +1677,21 @@ class AppLocalizationsEn extends AppLocalizations { @override String get notificationSubscriptionWeeklyRoundupDescription => 'Limit for subscriptions that send a weekly summary of matching headlines.'; + + @override + String get appStatusAndUpdatesTitle => 'App Status & Updates'; + + @override + String get enableForcedUpdatesLabel => 'Enable Forced Updates'; + + @override + String get enableForcedUpdatesDescription => + 'When enabled, you can specify a minimum required version for the app.'; + + @override + String get appUrlsTitle => 'Application URLs'; + + @override + String get appUrlsDescription => + 'Manage external and internal URLs used within the application.'; } From c7b7a612b09143414e2e121c6ca9a4f014585c87 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 10:01:55 +0100 Subject: [PATCH 34/43] refactor(app_configuration): restructure app status and updates settings - Combine maintenance mode and app updates into a single settings section - Rename "General App Config" to "App URLs" - Reorganize settings tiles and their respective indices --- .../view/tabs/app_configuration_tab.dart | 84 +++++++++++-------- 1 file changed, 51 insertions(+), 33 deletions(-) diff --git a/lib/app_configuration/view/tabs/app_configuration_tab.dart b/lib/app_configuration/view/tabs/app_configuration_tab.dart index 484e0cf0..9b11b81d 100644 --- a/lib/app_configuration/view/tabs/app_configuration_tab.dart +++ b/lib/app_configuration/view/tabs/app_configuration_tab.dart @@ -44,39 +44,17 @@ class _AppConfigurationTabState extends State { @override Widget build(BuildContext context) { final l10n = AppLocalizationsX(context).l10n; - final appConfig = widget.remoteConfig.app; - final maintenanceConfig = appConfig.maintenance; return ListView( padding: const EdgeInsets.all(AppSpacing.lg), children: [ - // Maintenance Config as a direct SwitchListTile - SwitchListTile( - title: Text(l10n.isUnderMaintenanceLabel), - subtitle: Text(l10n.isUnderMaintenanceDescription), - value: maintenanceConfig.isUnderMaintenance, - onChanged: (value) { - widget.onConfigChanged( - widget.remoteConfig.copyWith( - app: appConfig.copyWith( - maintenance: maintenanceConfig.copyWith( - isUnderMaintenance: value, - ), - ), - ), - ); - }, - ), - const SizedBox(height: AppSpacing.lg), - - // Update Config ValueListenableBuilder( valueListenable: _expandedTileIndex, builder: (context, expandedIndex, child) { - const tileIndex = 1; + const tileIndex = 0; return ExpansionTile( - key: ValueKey('updateConfigTile_$expandedIndex'), - title: Text(l10n.appUpdateManagementTitle), + key: ValueKey('appStatusAndUpdatesTile_$expandedIndex'), + title: Text(l10n.appStatusAndUpdatesTitle), onExpansionChanged: (isExpanded) { _expandedTileIndex.value = isExpanded ? tileIndex : null; }, @@ -88,24 +66,64 @@ class _AppConfigurationTabState extends State { ), expandedCrossAxisAlignment: CrossAxisAlignment.start, children: [ - UpdateConfigForm( - remoteConfig: widget.remoteConfig, - onConfigChanged: widget.onConfigChanged, + ExpansionTile( + title: Text(l10n.maintenanceModeTitle), + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, + children: [ + SwitchListTile( + title: Text(l10n.isUnderMaintenanceLabel), + subtitle: Text(l10n.isUnderMaintenanceDescription), + value: widget + .remoteConfig + .app + .maintenance + .isUnderMaintenance, + onChanged: (value) { + widget.onConfigChanged( + widget.remoteConfig.copyWith( + app: widget.remoteConfig.app.copyWith( + maintenance: widget.remoteConfig.app.maintenance + .copyWith(isUnderMaintenance: value), + ), + ), + ); + }, + ), + ], + ), + const SizedBox(height: AppSpacing.lg), + ExpansionTile( + title: Text(l10n.appUpdateManagementTitle), + childrenPadding: const EdgeInsetsDirectional.only( + start: AppSpacing.lg, + top: AppSpacing.md, + bottom: AppSpacing.md, + ), + expandedCrossAxisAlignment: CrossAxisAlignment.start, + children: [ + UpdateConfigForm( + remoteConfig: widget.remoteConfig, + onConfigChanged: widget.onConfigChanged, + ), + ], ), ], ); }, ), const SizedBox(height: AppSpacing.lg), - - // General App Config ValueListenableBuilder( valueListenable: _expandedTileIndex, builder: (context, expandedIndex, child) { - const tileIndex = 2; + const tileIndex = 1; return ExpansionTile( - key: ValueKey('generalAppConfigTile_$expandedIndex'), - title: Text(l10n.appLegalInformationTitle), + key: ValueKey('appUrlsTile_$expandedIndex'), + title: Text(l10n.appUrlsTitle), onExpansionChanged: (isExpanded) { _expandedTileIndex.value = isExpanded ? tileIndex : null; }, From e5d0cba12cedc6329b1e27f2c62a26af8379736e Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 10:02:08 +0100 Subject: [PATCH 35/43] refactor(app_configuration): adjust layout of general app config form - Remove outer Padding widget - Change main Column to be full-size to fill the parent Row - Update description text to match the section content (app URLs) --- .../widgets/general_app_config_form.dart | 83 +++++++++---------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/lib/app_configuration/widgets/general_app_config_form.dart b/lib/app_configuration/widgets/general_app_config_form.dart index a0304987..55f388cb 100644 --- a/lib/app_configuration/widgets/general_app_config_form.dart +++ b/lib/app_configuration/widgets/general_app_config_form.dart @@ -66,54 +66,51 @@ class _GeneralAppConfigFormState extends State { final appConfig = widget.remoteConfig.app; final generalConfig = appConfig.general; - return Padding( - padding: const EdgeInsets.all(AppSpacing.lg), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l10n.generalAppConfigDescription, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.appUrlsDescription, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ), - const SizedBox(height: AppSpacing.lg), - AppConfigTextField( - label: l10n.termsOfServiceUrlLabel, - description: l10n.termsOfServiceUrlDescription, - value: generalConfig.termsOfServiceUrl, - onChanged: (value) { - widget.onConfigChanged( - widget.remoteConfig.copyWith( - app: appConfig.copyWith( - general: generalConfig.copyWith( - termsOfServiceUrl: value, - ), + ), + const SizedBox(height: AppSpacing.lg), + AppConfigTextField( + label: l10n.termsOfServiceUrlLabel, + description: l10n.termsOfServiceUrlDescription, + value: generalConfig.termsOfServiceUrl, + onChanged: (value) { + widget.onConfigChanged( + widget.remoteConfig.copyWith( + app: appConfig.copyWith( + general: generalConfig.copyWith( + termsOfServiceUrl: value, ), ), - ); - }, - controller: _termsUrlController, - ), - AppConfigTextField( - label: l10n.privacyPolicyUrlLabel, - description: l10n.privacyPolicyUrlDescription, - value: generalConfig.privacyPolicyUrl, - onChanged: (value) { - widget.onConfigChanged( - widget.remoteConfig.copyWith( - app: appConfig.copyWith( - general: generalConfig.copyWith( - privacyPolicyUrl: value, - ), + ), + ); + }, + controller: _termsUrlController, + ), + AppConfigTextField( + label: l10n.privacyPolicyUrlLabel, + description: l10n.privacyPolicyUrlDescription, + value: generalConfig.privacyPolicyUrl, + onChanged: (value) { + widget.onConfigChanged( + widget.remoteConfig.copyWith( + app: appConfig.copyWith( + general: generalConfig.copyWith( + privacyPolicyUrl: value, ), ), - ); - }, - controller: _privacyUrlController, - ), - ], - ), + ), + ); + }, + controller: _privacyUrlController, + ), + ], ); } } From e1c9ec52fc1bf13ff610a75188143b8c7d0b4a7e Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 10:02:19 +0100 Subject: [PATCH 36/43] feat(app_configuration): reorganize update config form layout - Move SwitchListTile to the top of the form - Add conditional rendering for latest version fields - Adjust padding and spacing for better visual hierarchy - Remove redundant description text for the configuration --- .../widgets/update_config_form.dart | 141 +++++++++--------- 1 file changed, 73 insertions(+), 68 deletions(-) diff --git a/lib/app_configuration/widgets/update_config_form.dart b/lib/app_configuration/widgets/update_config_form.dart index 6ddc890c..5bba1bbd 100644 --- a/lib/app_configuration/widgets/update_config_form.dart +++ b/lib/app_configuration/widgets/update_config_form.dart @@ -68,79 +68,84 @@ class _UpdateConfigFormState extends State { final appConfig = widget.remoteConfig.app; final updateConfig = appConfig.update; - return Padding( - padding: const EdgeInsets.all(AppSpacing.lg), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l10n.updateConfigDescription, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SwitchListTile( + title: Text(l10n.enableForcedUpdatesLabel), + subtitle: Text(l10n.enableForcedUpdatesDescription), + value: updateConfig.isLatestVersionOnly, + onChanged: (value) { + widget.onConfigChanged( + widget.remoteConfig.copyWith( + app: appConfig.copyWith( + update: updateConfig.copyWith(isLatestVersionOnly: value), + ), + ), + ); + }, + ), + if (updateConfig.isLatestVersionOnly) + Padding( + padding: const EdgeInsets.only( + left: AppSpacing.lg, + top: AppSpacing.md, ), - ), - const SizedBox(height: AppSpacing.lg), - AppConfigTextField( - label: l10n.latestAppVersionLabel, - description: l10n.latestAppVersionDescription, - value: updateConfig.latestAppVersion, - onChanged: (value) { - widget.onConfigChanged( - widget.remoteConfig.copyWith( - app: appConfig.copyWith( - update: updateConfig.copyWith(latestAppVersion: value), - ), + child: Column( + children: [ + AppConfigTextField( + label: l10n.latestAppVersionLabel, + description: l10n.latestAppVersionDescription, + value: updateConfig.latestAppVersion, + onChanged: (value) { + widget.onConfigChanged( + widget.remoteConfig.copyWith( + app: appConfig.copyWith( + update: updateConfig.copyWith( + latestAppVersion: value, + ), + ), + ), + ); + }, + controller: _latestVersionController, ), - ); - }, - controller: _latestVersionController, - ), - SwitchListTile( - title: Text(l10n.isLatestVersionOnlyLabel), - subtitle: Text(l10n.isLatestVersionOnlyDescription), - value: updateConfig.isLatestVersionOnly, - onChanged: (value) { - widget.onConfigChanged( - widget.remoteConfig.copyWith( - app: appConfig.copyWith( - update: updateConfig.copyWith(isLatestVersionOnly: value), - ), + AppConfigTextField( + label: l10n.iosUpdateUrlLabel, + description: l10n.iosUpdateUrlDescription, + value: updateConfig.iosUpdateUrl, + onChanged: (value) { + widget.onConfigChanged( + widget.remoteConfig.copyWith( + app: appConfig.copyWith( + update: updateConfig.copyWith(iosUpdateUrl: value), + ), + ), + ); + }, + controller: _iosUrlController, ), - ); - }, - ), - AppConfigTextField( - label: l10n.iosUpdateUrlLabel, - description: l10n.iosUpdateUrlDescription, - value: updateConfig.iosUpdateUrl, - onChanged: (value) { - widget.onConfigChanged( - widget.remoteConfig.copyWith( - app: appConfig.copyWith( - update: updateConfig.copyWith(iosUpdateUrl: value), - ), + AppConfigTextField( + label: l10n.androidUpdateUrlLabel, + description: l10n.androidUpdateUrlDescription, + value: updateConfig.androidUpdateUrl, + onChanged: (value) { + widget.onConfigChanged( + widget.remoteConfig.copyWith( + app: appConfig.copyWith( + update: updateConfig.copyWith( + androidUpdateUrl: value, + ), + ), + ), + ); + }, + controller: _androidUrlController, ), - ); - }, - controller: _iosUrlController, - ), - AppConfigTextField( - label: l10n.androidUpdateUrlLabel, - description: l10n.androidUpdateUrlDescription, - value: updateConfig.androidUpdateUrl, - onChanged: (value) { - widget.onConfigChanged( - widget.remoteConfig.copyWith( - app: appConfig.copyWith( - update: updateConfig.copyWith(androidUpdateUrl: value), - ), - ), - ); - }, - controller: _androidUrlController, + ], + ), ), - ], - ), + ], ); } } From c47e7356483549ae10cd46052787cd6a5b507f9d Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 10:06:05 +0100 Subject: [PATCH 37/43] refactor(app_configuration): rename and specialize app config form - Rename `GeneralAppConfigForm` to `AppUrlsForm` - Update class and file names to reflect more specific purpose - Modify documentation to clarify form's focus on application URLs --- .../view/tabs/app_configuration_tab.dart | 4 ++-- ...l_app_config_form.dart => app_urls_form.dart} | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) rename lib/app_configuration/widgets/{general_app_config_form.dart => app_urls_form.dart} (88%) diff --git a/lib/app_configuration/view/tabs/app_configuration_tab.dart b/lib/app_configuration/view/tabs/app_configuration_tab.dart index 9b11b81d..ffc6aea3 100644 --- a/lib/app_configuration/view/tabs/app_configuration_tab.dart +++ b/lib/app_configuration/view/tabs/app_configuration_tab.dart @@ -1,6 +1,6 @@ import 'package:core/core.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/general_app_config_form.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/app_urls_form.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/update_config_form.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; import 'package:ui_kit/ui_kit.dart'; @@ -135,7 +135,7 @@ class _AppConfigurationTabState extends State { ), expandedCrossAxisAlignment: CrossAxisAlignment.start, children: [ - GeneralAppConfigForm( + AppUrlsForm( remoteConfig: widget.remoteConfig, onConfigChanged: widget.onConfigChanged, ), diff --git a/lib/app_configuration/widgets/general_app_config_form.dart b/lib/app_configuration/widgets/app_urls_form.dart similarity index 88% rename from lib/app_configuration/widgets/general_app_config_form.dart rename to lib/app_configuration/widgets/app_urls_form.dart index 55f388cb..e9df3abc 100644 --- a/lib/app_configuration/widgets/general_app_config_form.dart +++ b/lib/app_configuration/widgets/app_urls_form.dart @@ -4,14 +4,14 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuratio import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; import 'package:ui_kit/ui_kit.dart'; -/// {@template general_app_config_form} -/// A form widget for configuring general application settings. +/// {@template app_urls_form} +/// A form widget for configuring application URLs. /// /// This form manages settings like Terms of Service and Privacy Policy URLs. /// {@endtemplate} -class GeneralAppConfigForm extends StatefulWidget { - /// {@macro general_app_config_form} - const GeneralAppConfigForm({ +class AppUrlsForm extends StatefulWidget { + /// {@macro app_urls_form} + const AppUrlsForm({ required this.remoteConfig, required this.onConfigChanged, super.key, @@ -24,10 +24,10 @@ class GeneralAppConfigForm extends StatefulWidget { final ValueChanged onConfigChanged; @override - State createState() => _GeneralAppConfigFormState(); + State createState() => _AppUrlsFormState(); } -class _GeneralAppConfigFormState extends State { +class _AppUrlsFormState extends State { late final TextEditingController _termsUrlController; late final TextEditingController _privacyUrlController; @@ -43,7 +43,7 @@ class _GeneralAppConfigFormState extends State { } @override - void didUpdateWidget(covariant GeneralAppConfigForm oldWidget) { + void didUpdateWidget(covariant AppUrlsForm oldWidget) { super.didUpdateWidget(oldWidget); if (widget.remoteConfig.app.general != oldWidget.remoteConfig.app.general) { _termsUrlController.text = From 7da99cb6e7d58c0b0c65ce357b6f26e540d4331c Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 10:22:03 +0100 Subject: [PATCH 38/43] fix(localization): clarify app maintenance and update descriptions - Update Arabic and English descriptions for "is under maintenance" and "enable forced updates" switches - Specify "mobile app" in descriptions to avoid ambiguity with other apps or services --- lib/l10n/app_localizations.dart | 4 ++-- lib/l10n/app_localizations_ar.dart | 4 ++-- lib/l10n/app_localizations_en.dart | 4 ++-- lib/l10n/arb/app_ar.arb | 4 ++-- lib/l10n/arb/app_en.arb | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 1b0dd34f..1966dbf7 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1523,7 +1523,7 @@ abstract class AppLocalizations { /// Description for the 'is under maintenance' switch /// /// In en, this message translates to: - /// **'Toggle to put the app in maintenance mode, preventing user access.'** + /// **'Toggle to put the mobile app in maintenance mode, preventing user access.'** String get isUnderMaintenanceDescription; /// Label for the 'is latest version only' switch @@ -3131,7 +3131,7 @@ abstract class AppLocalizations { /// Description for the switch to enable forced updates. /// /// In en, this message translates to: - /// **'When enabled, you can specify a minimum required version for the app.'** + /// **'When enabled, you can specify a minimum required version for the mobile app.'** String get enableForcedUpdatesDescription; /// Title for the Application URLs expansion tile. diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 375cc947..368c7d41 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -800,7 +800,7 @@ class AppLocalizationsAr extends AppLocalizations { @override String get isUnderMaintenanceDescription => - 'تبديل لوضع التطبيق في وضع الصيانة، مما يمنع وصول المستخدمين.'; + 'تبديل لوضع تطبيق الجوال في وضع الصيانة، مما يمنع وصول المستخدمين.'; @override String get isLatestVersionOnlyLabel => 'فرض أحدث إصدار فقط'; @@ -1683,7 +1683,7 @@ class AppLocalizationsAr extends AppLocalizations { @override String get enableForcedUpdatesDescription => - 'عند التفعيل، يمكنك تحديد الحد الأدنى للإصدار المطلوب للتطبيق.'; + 'عند التفعيل، يمكنك تحديد الحد الأدنى للإصدار المطلوب لتطبيق الجوال.'; @override String get appUrlsTitle => 'روابط التطبيق'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 20ef20eb..32f8ebba 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -798,7 +798,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get isUnderMaintenanceDescription => - 'Toggle to put the app in maintenance mode, preventing user access.'; + 'Toggle to put the mobile app in maintenance mode, preventing user access.'; @override String get isLatestVersionOnlyLabel => 'Force Latest Version Only'; @@ -1686,7 +1686,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get enableForcedUpdatesDescription => - 'When enabled, you can specify a minimum required version for the app.'; + 'When enabled, you can specify a minimum required version for the mobile app.'; @override String get appUrlsTitle => 'Application URLs'; diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index bfb9d57c..db57eeff 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -986,7 +986,7 @@ "@isUnderMaintenanceLabel": { "description": "تسمية مفتاح 'تحت الصيانة'" }, - "isUnderMaintenanceDescription": "تبديل لوضع التطبيق في وضع الصيانة، مما يمنع وصول المستخدمين.", + "isUnderMaintenanceDescription": "تبديل لوضع تطبيق الجوال في وضع الصيانة، مما يمنع وصول المستخدمين.", "@isUnderMaintenanceDescription": { "description": "وصف مفتاح 'تحت الصيانة'" }, @@ -2106,7 +2106,7 @@ "@enableForcedUpdatesLabel": { "description": "تسمية مفتاح تفعيل التحديثات الإجبارية." }, - "enableForcedUpdatesDescription": "عند التفعيل، يمكنك تحديد الحد الأدنى للإصدار المطلوب للتطبيق.", + "enableForcedUpdatesDescription": "عند التفعيل، يمكنك تحديد الحد الأدنى للإصدار المطلوب لتطبيق الجوال.", "@enableForcedUpdatesDescription": { "description": "وصف مفتاح تفعيل التحديثات الإجبارية." }, diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8f36639c..cef09f65 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -990,7 +990,7 @@ "@isUnderMaintenanceLabel": { "description": "Label for the 'is under maintenance' switch" }, - "isUnderMaintenanceDescription": "Toggle to put the app in maintenance mode, preventing user access.", + "isUnderMaintenanceDescription": "Toggle to put the mobile app in maintenance mode, preventing user access.", "@isUnderMaintenanceDescription": { "description": "Description for the 'is under maintenance' switch" }, @@ -2102,7 +2102,7 @@ "@enableForcedUpdatesLabel": { "description": "Label for the switch to enable forced updates." }, - "enableForcedUpdatesDescription": "When enabled, you can specify a minimum required version for the app.", + "enableForcedUpdatesDescription": "When enabled, you can specify a minimum required version for the mobile app.", "@enableForcedUpdatesDescription": { "description": "Description for the switch to enable forced updates." }, From 920dab31bcbbf84e32269509e4146189f61dcc0a Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 10:24:09 +0100 Subject: [PATCH 39/43] refactor(app_configuration): rename AppConfigurationTab to SystemConfigurationTab - Renamed `AppConfigurationTab` to `SystemConfigurationTab` to better reflect its purpose --- .../view/app_configuration_page.dart | 4 ++-- ...tion_tab.dart => system_configuration_tab.dart} | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) rename lib/app_configuration/view/tabs/{app_configuration_tab.dart => system_configuration_tab.dart} (93%) diff --git a/lib/app_configuration/view/app_configuration_page.dart b/lib/app_configuration/view/app_configuration_page.dart index 34cb31ad..4d6defee 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/bloc/app_configuration_bloc.dart'; -import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/app_configuration_tab.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/system_configuration_tab.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/features_configuration_tab.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/user_configuration_tab.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; @@ -132,7 +132,7 @@ class _AppConfigurationPageState extends State return TabBarView( controller: _tabController, children: [ - AppConfigurationTab( + SystemConfigurationTab( remoteConfig: remoteConfig, onConfigChanged: (newConfig) { context.read().add( diff --git a/lib/app_configuration/view/tabs/app_configuration_tab.dart b/lib/app_configuration/view/tabs/system_configuration_tab.dart similarity index 93% rename from lib/app_configuration/view/tabs/app_configuration_tab.dart rename to lib/app_configuration/view/tabs/system_configuration_tab.dart index ffc6aea3..bb275f45 100644 --- a/lib/app_configuration/view/tabs/app_configuration_tab.dart +++ b/lib/app_configuration/view/tabs/system_configuration_tab.dart @@ -5,15 +5,15 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuratio import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; import 'package:ui_kit/ui_kit.dart'; -/// {@template app_configuration_tab} -/// A widget representing the "App" tab in the App Configuration page. +/// {@template system_configuration_tab} +/// A widget representing the "System" tab in the App Configuration page. /// /// This tab allows configuration of application-level settings like /// maintenance mode, force updates, and general app settings. /// {@endtemplate} -class AppConfigurationTab extends StatefulWidget { - /// {@macro app_configuration_tab} - const AppConfigurationTab({ +class SystemConfigurationTab extends StatefulWidget { + /// {@macro system_configuration_tab} + const SystemConfigurationTab({ required this.remoteConfig, required this.onConfigChanged, super.key, @@ -26,10 +26,10 @@ class AppConfigurationTab extends StatefulWidget { final ValueChanged onConfigChanged; @override - State createState() => _AppConfigurationTabState(); + State createState() => _SystemConfigurationTabState(); } -class _AppConfigurationTabState extends State { +class _SystemConfigurationTabState extends State { /// Notifier for the index of the currently expanded top-level ExpansionTile. /// /// A value of `null` means no tile is expanded. From ed66b85268de6f9e07fc04e3874f653c3f368da0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 10:29:58 +0100 Subject: [PATCH 40/43] refactor(app_configuration): rename "general" to "system" in app configuration - Update tab title from "appTab" to "systemTab" - Modify Arabic and English localization files to reflect the change - Correct typo in "feedItemClickBehaviorDescription" --- lib/app_configuration/view/app_configuration_page.dart | 2 +- lib/l10n/app_localizations_ar.dart | 2 +- lib/l10n/arb/app_ar.arb | 8 ++++---- lib/l10n/arb/app_en.arb | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/app_configuration/view/app_configuration_page.dart b/lib/app_configuration/view/app_configuration_page.dart index 4d6defee..12074617 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -65,7 +65,7 @@ class _AppConfigurationPageState extends State tabAlignment: TabAlignment.start, isScrollable: true, tabs: [ - Tab(text: l10n.appTab), + Tab(text: l10n.systemTab), Tab(text: l10n.featuresTab), Tab(text: l10n.userTab), ], diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 368c7d41..ef4c2407 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -115,7 +115,7 @@ class AppLocalizationsAr extends AppLocalizations { String get advertisementsTab => 'الإعلانات'; @override - String get generalTab => 'عام'; + String get systemTab => 'النظام'; @override String get userContentLimitsTitle => 'حدود المحتوى'; diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index db57eeff..deb7c638 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -139,9 +139,9 @@ "@advertisementsTab": { "description": "عنوان تبويب إعدادات الإعلانات" }, - "generalTab": "عام", - "@generalTab": { - "description": "عنوان تبويب الإعدادات العامة" + "systemTab": "النظام", + "@systemTab": { + "description": "عنوان تبويب لإعدادات النظام" }, "userContentLimitsTitle": "حدود المحتوى", "@userContentLimitsTitle": { @@ -2071,7 +2071,7 @@ "description": "عنوان إعداد سلوك النقر على عنصر الموجز." }, "feedItemClickBehaviorDescription": "اختر المتصفح الذي يفتح عند نقر المستخدم على عنوان في الموجز.", - "@feedItemClickbehaviorDescription": { + "@feedItemClickBehaviorDescription": { "description": "وصف إعداد سلوك النقر على عنصر الموجز." }, "feedItemClickBehaviorInternalNavigation": "متصفح داخل التطبيق", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index cef09f65..10e5a602 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -139,9 +139,9 @@ "@advertisementsTab": { "description": "Tab title for Advertisements settings" }, - "generalTab": "General", - "@generalTab": { - "description": "Tab title for General settings" + "systemTab": "System", + "@systemTab": { + "description": "Tab title for system settings" }, "userContentLimitsTitle": "User Content Limits", "@userContentLimitsTitle": { @@ -2067,7 +2067,7 @@ "description": "Title for the feed item click behavior setting." }, "feedItemClickBehaviorDescription": "Select which browser opens when a user taps on a headline in the feed.", - "@feedItemClickbehaviorDescription": { + "@feedItemClickBehaviorDescription": { "description": "Description for the feed item click behavior setting." }, "feedItemClickBehaviorInternalNavigation": "In-App Browser", From e638a7a5ab8813707c0555b23247eec6caeeb87b Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 10:30:09 +0100 Subject: [PATCH 41/43] build(l10n): generate --- lib/l10n/app_localizations.dart | 8 ++++---- lib/l10n/app_localizations_en.dart | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 1966dbf7..2d935214 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -284,11 +284,11 @@ abstract class AppLocalizations { /// **'Advertisements'** String get advertisementsTab; - /// Tab title for General settings + /// Tab title for system settings /// /// In en, this message translates to: - /// **'General'** - String get generalTab; + /// **'System'** + String get systemTab; /// Title for the User Content Limits section /// @@ -3074,7 +3074,7 @@ abstract class AppLocalizations { /// **'Feed Item Click Behavior'** String get feedItemClickBehaviorTitle; - /// No description provided for @feedItemClickBehaviorDescription. + /// Description for the feed item click behavior setting. /// /// In en, this message translates to: /// **'Select which browser opens when a user taps on a headline in the feed.'** diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 32f8ebba..279785d4 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -114,7 +114,7 @@ class AppLocalizationsEn extends AppLocalizations { String get advertisementsTab => 'Advertisements'; @override - String get generalTab => 'General'; + String get systemTab => 'System'; @override String get userContentLimitsTitle => 'User Content Limits'; From eda3c0d0c1a2e87178f42ebc2d7e78d23e1f18ab Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 10:30:46 +0100 Subject: [PATCH 42/43] style: format --- lib/app_configuration/view/app_configuration_page.dart | 2 +- lib/app_configuration/widgets/ad_config_form.dart | 1 - 2 files 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 12074617..0d183a26 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/bloc/app_configuration_bloc.dart'; -import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/system_configuration_tab.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/features_configuration_tab.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/system_configuration_tab.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/user_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'; diff --git a/lib/app_configuration/widgets/ad_config_form.dart b/lib/app_configuration/widgets/ad_config_form.dart index 44fdfcc9..a74323e8 100644 --- a/lib/app_configuration/widgets/ad_config_form.dart +++ b/lib/app_configuration/widgets/ad_config_form.dart @@ -1,7 +1,6 @@ import 'package:core/core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; -import 'package:ui_kit/ui_kit.dart'; /// {@template ad_config_form} /// A form widget for configuring global ad settings. From 6a97efbe4c89c52af1d6850c9536ce785567772b Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 28 Nov 2025 10:41:10 +0100 Subject: [PATCH 43/43] refactor(app_configuration): improve text controller initialization and updates - Extract common controller initialization logic into separate methods - Implement proper cursor positioning when updating text controllers - Avoid unnecessary text updates by comparing current and new values - Apply refactoring to both AppUrlsForm and UpdateConfigForm widgets --- .../widgets/app_urls_form.dart | 35 +++++++++++++------ .../widgets/update_config_form.dart | 35 +++++++++++++------ 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/lib/app_configuration/widgets/app_urls_form.dart b/lib/app_configuration/widgets/app_urls_form.dart index e9df3abc..14a77375 100644 --- a/lib/app_configuration/widgets/app_urls_form.dart +++ b/lib/app_configuration/widgets/app_urls_form.dart @@ -34,22 +34,37 @@ class _AppUrlsFormState extends State { @override void initState() { super.initState(); - _termsUrlController = TextEditingController( - text: widget.remoteConfig.app.general.termsOfServiceUrl, - ); - _privacyUrlController = TextEditingController( - text: widget.remoteConfig.app.general.privacyPolicyUrl, - ); + final generalConfig = widget.remoteConfig.app.general; + _termsUrlController = _createController(generalConfig.termsOfServiceUrl); + _privacyUrlController = _createController(generalConfig.privacyPolicyUrl); + } + + TextEditingController _createController(String text) { + return TextEditingController(text: text) + ..selection = TextSelection.collapsed(offset: text.length); + } + + void _updateControllerText(TextEditingController controller, String text) { + if (controller.text != text) { + controller + ..text = text + ..selection = TextSelection.collapsed(offset: text.length); + } } @override void didUpdateWidget(covariant AppUrlsForm oldWidget) { super.didUpdateWidget(oldWidget); if (widget.remoteConfig.app.general != oldWidget.remoteConfig.app.general) { - _termsUrlController.text = - widget.remoteConfig.app.general.termsOfServiceUrl; - _privacyUrlController.text = - widget.remoteConfig.app.general.privacyPolicyUrl; + final generalConfig = widget.remoteConfig.app.general; + _updateControllerText( + _termsUrlController, + generalConfig.termsOfServiceUrl, + ); + _updateControllerText( + _privacyUrlController, + generalConfig.privacyPolicyUrl, + ); } } diff --git a/lib/app_configuration/widgets/update_config_form.dart b/lib/app_configuration/widgets/update_config_form.dart index 5bba1bbd..5b46975e 100644 --- a/lib/app_configuration/widgets/update_config_form.dart +++ b/lib/app_configuration/widgets/update_config_form.dart @@ -34,13 +34,22 @@ class _UpdateConfigFormState extends State { void initState() { super.initState(); final updateConfig = widget.remoteConfig.app.update; - _latestVersionController = TextEditingController( - text: updateConfig.latestAppVersion, - ); - _iosUrlController = TextEditingController(text: updateConfig.iosUpdateUrl); - _androidUrlController = TextEditingController( - text: updateConfig.androidUpdateUrl, - ); + _latestVersionController = _createController(updateConfig.latestAppVersion); + _iosUrlController = _createController(updateConfig.iosUpdateUrl); + _androidUrlController = _createController(updateConfig.androidUpdateUrl); + } + + TextEditingController _createController(String text) { + return TextEditingController(text: text) + ..selection = TextSelection.collapsed(offset: text.length); + } + + void _updateControllerText(TextEditingController controller, String text) { + if (controller.text != text) { + controller + ..text = text + ..selection = TextSelection.collapsed(offset: text.length); + } } @override @@ -48,9 +57,15 @@ class _UpdateConfigFormState extends State { super.didUpdateWidget(oldWidget); if (widget.remoteConfig.app.update != oldWidget.remoteConfig.app.update) { final updateConfig = widget.remoteConfig.app.update; - _latestVersionController.text = updateConfig.latestAppVersion; - _iosUrlController.text = updateConfig.iosUpdateUrl; - _androidUrlController.text = updateConfig.androidUpdateUrl; + _updateControllerText( + _latestVersionController, + updateConfig.latestAppVersion, + ); + _updateControllerText(_iosUrlController, updateConfig.iosUpdateUrl); + _updateControllerText( + _androidUrlController, + updateConfig.androidUpdateUrl, + ); } }