From e94180e158fe3a4e838a1091c156e047da16f201 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 14 Jul 2025 18:39:40 +0100 Subject: [PATCH 1/3] refactor(app_config): improve app configuration UI - Added tabbed interface for better organization. - Replaced ExpansionTiles with TabBarView. - Improved section descriptions for clarity. - Updated UI for better user experience. - Added maintenance and force update sections. --- .../view/app_configuration_page.dart | 333 +++++++++++------- 1 file changed, 200 insertions(+), 133 deletions(-) diff --git a/lib/app_configuration/view/app_configuration_page.dart b/lib/app_configuration/view/app_configuration_page.dart index 41e40e5..e5affb5 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -23,15 +23,20 @@ class AppConfigurationPage extends StatefulWidget { State createState() => _AppConfigurationPageState(); } -class _AppConfigurationPageState extends State { +class _AppConfigurationPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + @override void initState() { super.initState(); + _tabController = TabController(length: 3, vsync: this); context.read().add(const AppConfigurationLoaded()); } @override void dispose() { + _tabController.dispose(); super.dispose(); } @@ -45,19 +50,36 @@ class _AppConfigurationPageState extends State { style: Theme.of(context).textTheme.headlineSmall, ), bottom: PreferredSize( - preferredSize: const Size.fromHeight(kToolbarHeight + AppSpacing.lg), - child: Padding( - padding: const EdgeInsets.only( - left: AppSpacing.lg, - right: AppSpacing.lg, - bottom: AppSpacing.lg, - ), - child: Text( - l10n.appConfigurationPageDescription, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, + preferredSize: const Size.fromHeight( + kTextTabBarHeight + AppSpacing.lg, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + left: AppSpacing.lg, + right: AppSpacing.lg, + bottom: AppSpacing.lg, + ), + child: Text( + l10n.appConfigurationPageDescription, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), ), - ), + TabBar( + controller: _tabController, + tabAlignment: TabAlignment.start, + isScrollable: true, + tabs: const [ + Tab(text: 'Feed'), + Tab(text: 'Advertisements'), + Tab(text: 'General'), + ], + ), + ], ), ), ), @@ -119,43 +141,55 @@ class _AppConfigurationPageState extends State { } else if (state.status == AppConfigurationStatus.success && state.remoteConfig != null) { final remoteConfig = state.remoteConfig!; - return ListView( - padding: const EdgeInsets.all(AppSpacing.lg), + return TabBarView( + controller: _tabController, children: [ - ExpansionTile( - title: Text(l10n.userContentLimitsTab), - childrenPadding: const EdgeInsets.symmetric( - horizontal: AppSpacing.xxl, - ), + ListView( + padding: const EdgeInsets.all(AppSpacing.lg), children: [ - _buildUserPreferenceLimitsSection(context, remoteConfig), - ], - ), - ExpansionTile( - title: Text(l10n.adSettingsTab), - childrenPadding: const EdgeInsets.symmetric( - horizontal: AppSpacing.xxl, - ), - children: [ - _buildAdConfigSection(context, remoteConfig), + ExpansionTile( + title: const Text('User Content & Feed Limits'), + childrenPadding: const EdgeInsets.symmetric( + horizontal: AppSpacing.xxl, + ), + children: [ + _buildUserPreferenceLimitsSection( + context, + remoteConfig, + ), + ], + ), + ExpansionTile( + title: const Text('In-App Action Prompts'), + childrenPadding: const EdgeInsets.symmetric( + horizontal: AppSpacing.xxl, + ), + children: [ + _buildAccountActionConfigSection(context, remoteConfig), + ], + ), ], ), - ExpansionTile( - title: Text(l10n.inAppPromptsTab), - childrenPadding: const EdgeInsets.symmetric( - horizontal: AppSpacing.xxl, - ), + ListView( + padding: const EdgeInsets.all(AppSpacing.lg), children: [ - _buildAccountActionConfigSection(context, remoteConfig), + ExpansionTile( + title: const Text('Advertisement Settings'), + childrenPadding: const EdgeInsets.symmetric( + horizontal: AppSpacing.xxl, + ), + children: [ + _buildAdConfigSection(context, remoteConfig), + ], + ), ], ), - ExpansionTile( - title: Text(l10n.appOperationalStatusTab), - childrenPadding: const EdgeInsets.symmetric( - horizontal: AppSpacing.xxl, - ), + ListView( + padding: const EdgeInsets.all(AppSpacing.lg), children: [ - _buildAppStatusSection(context, remoteConfig), + _buildMaintenanceSection(context, remoteConfig), + const SizedBox(height: AppSpacing.lg), + _buildForceUpdateSection(context, remoteConfig), ], ), ], @@ -263,7 +297,7 @@ class _AppConfigurationPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - l10n.userContentLimitsDescription, + 'Set limits on followed items and saved headlines for each user tier.', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ), @@ -342,7 +376,7 @@ class _AppConfigurationPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - l10n.adSettingsDescription, + 'Manage ad frequency and placement for different user roles.', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ), @@ -421,7 +455,7 @@ class _AppConfigurationPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - l10n.inAppPromptsDescription, + 'Configure how often to show prompts for actions like rating the app.', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ), @@ -471,109 +505,142 @@ class _AppConfigurationPageState extends State { ); } - Widget _buildAppStatusSection( + Widget _buildMaintenanceSection( BuildContext context, RemoteConfig remoteConfig, ) { final l10n = context.l10n; - return SingleChildScrollView( - padding: const EdgeInsets.all(AppSpacing.lg), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l10n.appOperationalStatusWarning, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.error, - fontWeight: FontWeight.bold, + return ExpansionTile( + title: const Text('Maintenance Mode'), + childrenPadding: const EdgeInsets.symmetric( + horizontal: AppSpacing.xxl, + vertical: AppSpacing.md, + ), + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Enable to show a maintenance screen to all users.', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + ), ), - ), - const SizedBox(height: AppSpacing.lg), - SwitchListTile( - title: Text(l10n.isUnderMaintenanceLabel), - subtitle: Text(l10n.isUnderMaintenanceDescription), - value: remoteConfig.appStatus.isUnderMaintenance, - onChanged: (value) { - context.read().add( - AppConfigurationFieldChanged( - remoteConfig: remoteConfig.copyWith( - appStatus: remoteConfig.appStatus.copyWith( - isUnderMaintenance: value, + const SizedBox(height: AppSpacing.lg), + SwitchListTile( + title: Text(l10n.isUnderMaintenanceLabel), + subtitle: Text(l10n.isUnderMaintenanceDescription), + value: remoteConfig.appStatus.isUnderMaintenance, + onChanged: (value) { + context.read().add( + AppConfigurationFieldChanged( + remoteConfig: remoteConfig.copyWith( + appStatus: remoteConfig.appStatus.copyWith( + isUnderMaintenance: value, + ), ), ), - ), - ); - }, - ), - _buildTextField( - context, - label: l10n.latestAppVersionLabel, - description: l10n.latestAppVersionDescription, - value: remoteConfig.appStatus.latestAppVersion, - onChanged: (value) { - context.read().add( - AppConfigurationFieldChanged( - remoteConfig: remoteConfig.copyWith( - appStatus: remoteConfig.appStatus.copyWith( - latestAppVersion: value, + ); + }, + ), + ], + ), + ], + ); + } + + Widget _buildForceUpdateSection( + BuildContext context, + RemoteConfig remoteConfig, + ) { + final l10n = context.l10n; + return ExpansionTile( + title: const Text('Force App Update'), + childrenPadding: const EdgeInsets.symmetric( + horizontal: AppSpacing.xxl, + vertical: AppSpacing.md, + ), + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Configure mandatory app updates for users.', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + ), + ), + const SizedBox(height: AppSpacing.lg), + _buildTextField( + context, + label: l10n.latestAppVersionLabel, + description: l10n.latestAppVersionDescription, + value: remoteConfig.appStatus.latestAppVersion, + onChanged: (value) { + context.read().add( + AppConfigurationFieldChanged( + remoteConfig: remoteConfig.copyWith( + appStatus: remoteConfig.appStatus.copyWith( + latestAppVersion: value, + ), ), ), - ), - ); - }, - ), - SwitchListTile( - title: Text(l10n.isLatestVersionOnlyLabel), - subtitle: Text(l10n.isLatestVersionOnlyDescription), - value: remoteConfig.appStatus.isLatestVersionOnly, - onChanged: (value) { - context.read().add( - AppConfigurationFieldChanged( - remoteConfig: remoteConfig.copyWith( - appStatus: remoteConfig.appStatus.copyWith( - isLatestVersionOnly: value, + ); + }, + ), + SwitchListTile( + title: Text(l10n.isLatestVersionOnlyLabel), + subtitle: Text(l10n.isLatestVersionOnlyDescription), + value: remoteConfig.appStatus.isLatestVersionOnly, + onChanged: (value) { + context.read().add( + AppConfigurationFieldChanged( + remoteConfig: remoteConfig.copyWith( + appStatus: remoteConfig.appStatus.copyWith( + isLatestVersionOnly: value, + ), ), ), - ), - ); - }, - ), - _buildTextField( - context, - label: l10n.iosUpdateUrlLabel, - description: l10n.iosUpdateUrlDescription, - value: remoteConfig.appStatus.iosUpdateUrl, - onChanged: (value) { - context.read().add( - AppConfigurationFieldChanged( - remoteConfig: remoteConfig.copyWith( - appStatus: remoteConfig.appStatus.copyWith( - iosUpdateUrl: value, + ); + }, + ), + _buildTextField( + context, + label: l10n.iosUpdateUrlLabel, + description: l10n.iosUpdateUrlDescription, + value: remoteConfig.appStatus.iosUpdateUrl, + onChanged: (value) { + context.read().add( + AppConfigurationFieldChanged( + remoteConfig: remoteConfig.copyWith( + appStatus: remoteConfig.appStatus.copyWith( + iosUpdateUrl: value, + ), ), ), - ), - ); - }, - ), - _buildTextField( - context, - label: l10n.androidUpdateUrlLabel, - description: l10n.androidUpdateUrlDescription, - value: remoteConfig.appStatus.androidUpdateUrl, - onChanged: (value) { - context.read().add( - AppConfigurationFieldChanged( - remoteConfig: remoteConfig.copyWith( - appStatus: remoteConfig.appStatus.copyWith( - androidUpdateUrl: value, + ); + }, + ), + _buildTextField( + context, + label: l10n.androidUpdateUrlLabel, + description: l10n.androidUpdateUrlDescription, + value: remoteConfig.appStatus.androidUpdateUrl, + onChanged: (value) { + context.read().add( + AppConfigurationFieldChanged( + remoteConfig: remoteConfig.copyWith( + appStatus: remoteConfig.appStatus.copyWith( + androidUpdateUrl: value, + ), ), ), - ), - ); - }, - ), - ], - ), + ); + }, + ), + ], + ), + ], ); } From d0fbac5abc1a77c294a1534e263096f951d61129 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 14 Jul 2025 18:45:28 +0100 Subject: [PATCH 2/3] docs(l10n): update app configuration descriptions - Improved descriptions for clarity - Renamed tabs for better UX - Added new sections for settings - Removed redundant descriptions - Updated translations for all languages --- lib/l10n/app_localizations.dart | 104 +++++++++++++++++++---------- lib/l10n/app_localizations_ar.dart | 58 ++++++++++------ lib/l10n/app_localizations_en.dart | 58 ++++++++++------ lib/l10n/arb/app_ar.arb | 72 +++++++++++++------- lib/l10n/arb/app_en.arb | 76 +++++++++++++-------- 5 files changed, 239 insertions(+), 129 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 1866402..92e7518 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -251,7 +251,7 @@ abstract class AppLocalizations { /// Description for the App Configuration page /// /// In en, this message translates to: - /// **'Configure global settings for the mobile application, including user content limits, ad display rules, in-app prompts, operational status, and force update parameters.'** + /// **'Manage global settings for the mobile app, from content limits to operational status.'** String get appConfigurationPageDescription; /// Label for the settings navigation item @@ -266,29 +266,83 @@ abstract class AppLocalizations { /// **'App Configuration'** String get appConfigurationPageTitle; - /// Tab title for User Content Limits + /// Tab title for Feed settings /// /// In en, this message translates to: - /// **'User Content Limits'** - String get userContentLimitsTab; + /// **'Feed'** + String get feedTab; - /// Tab title for Ad Settings + /// Tab title for Advertisements settings /// /// In en, this message translates to: - /// **'Ad Settings'** - String get adSettingsTab; + /// **'Advertisements'** + String get advertisementsTab; - /// Tab title for In-App Prompts + /// Tab title for General settings /// /// In en, this message translates to: - /// **'In-App Prompts'** - String get inAppPromptsTab; + /// **'General'** + String get generalTab; - /// Tab title for App Operational Status + /// Title for the User Content & Feed Limits section /// /// In en, this message translates to: - /// **'App Operational Status'** - String get appOperationalStatusTab; + /// **'User Content & Feed Limits'** + String get userContentLimitsTitle; + + /// Description for the User Content & Feed Limits section + /// + /// In en, this message translates to: + /// **'Set limits on followed items and saved headlines for each user tier.'** + String get userContentLimitsDescription; + + /// Title for the Feed Actions section + /// + /// In en, this message translates to: + /// **'Feed Actions'** + String get feedActionsTitle; + + /// Description for the Feed Actions section + /// + /// In en, this message translates to: + /// **'Configure how often to inject action widgets (e.g., \'Rate App\') into the feed.'** + String get feedActionsDescription; + + /// Title for the Advertisement Settings section + /// + /// In en, this message translates to: + /// **'Advertisement Settings'** + String get adSettingsTitle; + + /// Description for the Ad Settings section + /// + /// In en, this message translates to: + /// **'Manage ad frequency and placement for different user roles.'** + String get adSettingsDescription; + + /// Title for the Maintenance Mode section + /// + /// In en, this message translates to: + /// **'Maintenance Mode'** + String get maintenanceModeTitle; + + /// Description for the Maintenance Mode section + /// + /// In en, this message translates to: + /// **'Enable to show a maintenance screen to all users.'** + String get maintenanceModeDescription; + + /// Title for the Force App Update section + /// + /// In en, this message translates to: + /// **'Force App Update'** + String get forceUpdateTitle; + + /// Description for Force Update Configuration section + /// + /// In en, this message translates to: + /// **'These settings control app version enforcement. Users on versions below the minimum allowed will be forced to update.'** + String get forceUpdateDescription; /// Tab title for Force Update /// @@ -374,12 +428,6 @@ abstract class AppLocalizations { /// **'Confirm Save'** String get confirmSaveButton; - /// Description for User Content Limits section - /// - /// In en, this message translates to: - /// **'These settings define the maximum number of countries, news sources, topics, and saved headlines a user can follow or save. Limits vary by user type (Guest, Standard, Premium) and directly impact what content users can curate.'** - String get userContentLimitsDescription; - /// Tab title for Guest user role /// /// In en, this message translates to: @@ -470,12 +518,6 @@ abstract class AppLocalizations { /// **'Maximum number of headlines a Premium user can save.'** String get premiumSavedHeadlinesLimitDescription; - /// Description for Ad Settings section - /// - /// In en, this message translates to: - /// **'These settings control how advertisements are displayed within the app\'s news feed, with different rules for Guest, Standard, and Premium users. \"Ad Frequency\" determines how often an ad can appear, while \"Ad Placement Interval\" sets how many news items must be shown before the very first ad appears.'** - String get adSettingsDescription; - /// Tab title for Standard User in Ad Settings /// /// In en, this message translates to: @@ -590,12 +632,6 @@ abstract class AppLocalizations { /// **'Number of articles a Premium user needs to read before a full-screen interstitial ad is shown.'** String get premiumUserArticlesBeforeInterstitialAdsDescription; - /// Description for In-App Prompts section - /// - /// In en, this message translates to: - /// **'These settings control how often special in-app messages or \"prompts\" are shown to users in their news feed. These prompts encourage actions like linking an account (for guests) or upgrading to a premium subscription (for authenticated users). The frequency varies by user type.'** - String get inAppPromptsDescription; - /// Warning message for changing app operational status /// /// In en, this message translates to: @@ -644,12 +680,6 @@ abstract class AppLocalizations { /// **'Force Update Configuration'** String get forceUpdateConfigurationTitle; - /// Description for Force Update Configuration section - /// - /// In en, this message translates to: - /// **'These settings control app version enforcement. Users on versions below the minimum allowed will be forced to update.'** - String get forceUpdateDescription; - /// Label for Minimum Allowed App Version /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index f541448..e20f55a 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -95,7 +95,7 @@ class AppLocalizationsAr extends AppLocalizations { @override String get appConfigurationPageDescription => - 'تكوين الإعدادات العامة للتطبيق المحمول، بما في ذلك حدود محتوى المستخدم، وقواعد عرض الإعلانات، والتنبيهات داخل التطبيق، وحالة التشغيل، ومعلمات التحديث الإجباري.'; + 'إدارة الإعدادات العامة لتطبيق الهاتف، من حدود المحتوى إلى الحالة التشغيلية.'; @override String get settings => 'الإعدادات'; @@ -104,16 +104,48 @@ class AppLocalizationsAr extends AppLocalizations { String get appConfigurationPageTitle => 'إعدادات التطبيق'; @override - String get userContentLimitsTab => 'حدود محتوى المستخدم'; + String get feedTab => 'الموجز'; @override - String get adSettingsTab => 'إعدادات الإعلانات'; + String get advertisementsTab => 'الإعلانات'; @override - String get inAppPromptsTab => 'تنبيهات داخل التطبيق'; + String get generalTab => 'عام'; @override - String get appOperationalStatusTab => 'حالة تشغيل التطبيق'; + String get userContentLimitsTitle => 'حدود المحتوى والموجز للمستخدم'; + + @override + String get userContentLimitsDescription => + 'تعيين حدود للعناصر المتابعة والعناوين المحفوظة لكل فئة من فئات المستخدمين.'; + + @override + String get feedActionsTitle => 'إجراءات الموجز'; + + @override + String get feedActionsDescription => + 'تكوين عدد مرات إدراج ودجات الإجراءات (مثل \'تقييم التطبيق\') في الموجز.'; + + @override + String get adSettingsTitle => 'إعدادات الإعلانات'; + + @override + String get adSettingsDescription => + 'إدارة تكرار وموضع الإعلانات لأدوار المستخدمين المختلفة.'; + + @override + String get maintenanceModeTitle => 'وضع الصيانة'; + + @override + String get maintenanceModeDescription => + 'تمكين لعرض شاشة صيانة لجميع المستخدمين.'; + + @override + String get forceUpdateTitle => 'فرض تحديث التطبيق'; + + @override + String get forceUpdateDescription => + 'تتحكم هذه الإعدادات في فرض إصدار التطبيق. سيتم إجبار المستخدمين الذين يستخدمون إصدارات أقل من الحد الأدنى المسموح به على التحديث.'; @override String get forceUpdateTab => 'تحديث إجباري'; @@ -162,10 +194,6 @@ class AppLocalizationsAr extends AppLocalizations { @override String get confirmSaveButton => 'تأكيد الحفظ'; - @override - String get userContentLimitsDescription => - 'تحدد هذه الإعدادات الحد الأقصى لعدد البلدان ومصادر الأخبار والمواضيع والعناوين المحفوظة التي يمكن للمستخدم متابعتها أو حفظها. تختلف الحدود حسب نوع المستخدم (ضيف، عادي، مميز) وتؤثر بشكل مباشر على المحتوى الذي يمكن للمستخدمين تنسيقه.'; - @override String get guestUserTab => 'ضيف'; @@ -221,10 +249,6 @@ class AppLocalizationsAr extends AppLocalizations { String get premiumSavedHeadlinesLimitDescription => 'الحد الأقصى لعدد العناوين الرئيسية التي يمكن للمستخدم المميز حفظها.'; - @override - String get adSettingsDescription => - 'تتحكم هذه الإعدادات في كيفية عرض الإعلانات ضمن موجز الأخبار في التطبيق، مع قواعد مختلفة للمستخدمين الضيوف والعاديين والمميزين. تحدد \"تكرار الإعلان\" عدد مرات ظهور الإعلان، بينما تحدد \"فترة وضع الإعلان\" عدد عناصر الأخبار التي يجب عرضها قبل ظهور الإعلان الأول.'; - @override String get standardUserAdTab => 'مستخدم عادي'; @@ -296,10 +320,6 @@ class AppLocalizationsAr extends AppLocalizations { String get premiumUserArticlesBeforeInterstitialAdsDescription => 'عدد المقالات التي يحتاج المستخدم المميز لقراءتها قبل عرض إعلان بيني بملء الشاشة.'; - @override - String get inAppPromptsDescription => - 'تتحكم هذه الإعدادات في عدد مرات عرض الرسائل الخاصة داخل التطبيق أو \"التنبيهات\" للمستخدمين في موجز الأخبار الخاص بهم. تشجع هذه التنبيهات على إجراءات مثل ربط حساب (للضيوف) أو الترقية إلى اشتراك مميز (للمستخدمين الموثقين). يختلف التكرار حسب نوع المستخدم.'; - @override String get appOperationalStatusWarning => 'تحذير: قد يؤثر تغيير حالة تشغيل التطبيق على جميع المستخدمين. استخدم بحذر شديد.'; @@ -328,10 +348,6 @@ class AppLocalizationsAr extends AppLocalizations { @override String get forceUpdateConfigurationTitle => 'إعدادات التحديث الإجباري'; - @override - String get forceUpdateDescription => - 'تتحكم هذه الإعدادات في فرض إصدار التطبيق. سيتم إجبار المستخدمين الذين يستخدمون إصدارات أقل من الحد الأدنى المسموح به على التحديث.'; - @override String get minAllowedAppVersionLabel => 'الحد الأدنى المسموح به لإصدار التطبيق'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index df9bb49..bf977e7 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -94,7 +94,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get appConfigurationPageDescription => - 'Configure global settings for the mobile application, including user content limits, ad display rules, in-app prompts, operational status, and force update parameters.'; + 'Manage global settings for the mobile app, from content limits to operational status.'; @override String get settings => 'Settings'; @@ -103,16 +103,48 @@ class AppLocalizationsEn extends AppLocalizations { String get appConfigurationPageTitle => 'App Configuration'; @override - String get userContentLimitsTab => 'User Content Limits'; + String get feedTab => 'Feed'; @override - String get adSettingsTab => 'Ad Settings'; + String get advertisementsTab => 'Advertisements'; @override - String get inAppPromptsTab => 'In-App Prompts'; + String get generalTab => 'General'; @override - String get appOperationalStatusTab => 'App Operational Status'; + String get userContentLimitsTitle => 'User Content & Feed Limits'; + + @override + String get userContentLimitsDescription => + 'Set limits on followed items and saved headlines for each user tier.'; + + @override + String get feedActionsTitle => 'Feed Actions'; + + @override + String get feedActionsDescription => + 'Configure how often to inject action widgets (e.g., \'Rate App\') into the feed.'; + + @override + String get adSettingsTitle => 'Advertisement Settings'; + + @override + String get adSettingsDescription => + 'Manage ad frequency and placement for different user roles.'; + + @override + String get maintenanceModeTitle => 'Maintenance Mode'; + + @override + String get maintenanceModeDescription => + 'Enable to show a maintenance screen to all users.'; + + @override + String get forceUpdateTitle => 'Force App Update'; + + @override + String get forceUpdateDescription => + 'These settings control app version enforcement. Users on versions below the minimum allowed will be forced to update.'; @override String get forceUpdateTab => 'Force Update'; @@ -163,10 +195,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get confirmSaveButton => 'Confirm Save'; - @override - String get userContentLimitsDescription => - 'These settings define the maximum number of countries, news sources, topics, and saved headlines a user can follow or save. Limits vary by user type (Guest, Standard, Premium) and directly impact what content users can curate.'; - @override String get guestUserTab => 'Guest'; @@ -220,10 +248,6 @@ class AppLocalizationsEn extends AppLocalizations { String get premiumSavedHeadlinesLimitDescription => 'Maximum number of headlines a Premium user can save.'; - @override - String get adSettingsDescription => - 'These settings control how advertisements are displayed within the app\'s news feed, with different rules for Guest, Standard, and Premium users. \"Ad Frequency\" determines how often an ad can appear, while \"Ad Placement Interval\" sets how many news items must be shown before the very first ad appears.'; - @override String get standardUserAdTab => 'Standard User'; @@ -294,10 +318,6 @@ class AppLocalizationsEn extends AppLocalizations { String get premiumUserArticlesBeforeInterstitialAdsDescription => 'Number of articles a Premium user needs to read before a full-screen interstitial ad is shown.'; - @override - String get inAppPromptsDescription => - 'These settings control how often special in-app messages or \"prompts\" are shown to users in their news feed. These prompts encourage actions like linking an account (for guests) or upgrading to a premium subscription (for authenticated users). The frequency varies by user type.'; - @override String get appOperationalStatusWarning => 'WARNING: Changing the app\'s operational status can affect all users. Use with extreme caution.'; @@ -326,10 +346,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get forceUpdateConfigurationTitle => 'Force Update Configuration'; - @override - String get forceUpdateDescription => - 'These settings control app version enforcement. Users on versions below the minimum allowed will be forced to update.'; - @override String get minAllowedAppVersionLabel => 'Minimum Allowed App Version'; diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 78402ef..3a43f8a 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -109,7 +109,7 @@ "@appConfiguration": { "description": "تسمية عنصر التنقل لإعدادات التطبيق" }, - "appConfigurationPageDescription": "تكوين الإعدادات العامة للتطبيق المحمول، بما في ذلك حدود محتوى المستخدم، وقواعد عرض الإعلانات، والتنبيهات داخل التطبيق، وحالة التشغيل، ومعلمات التحديث الإجباري.", + "appConfigurationPageDescription": "إدارة الإعدادات العامة لتطبيق الهاتف، من حدود المحتوى إلى الحالة التشغيلية.", "@appConfigurationPageDescription": { "description": "وصف صفحة إعدادات التطبيق" }, @@ -121,21 +121,57 @@ "@appConfigurationPageTitle": { "description": "عنوان صفحة إعدادات التطبيق" }, - "userContentLimitsTab": "حدود محتوى المستخدم", - "@userContentLimitsTab": { - "description": "عنوان تبويب حدود محتوى المستخدم" + "feedTab": "الموجز", + "@feedTab": { + "description": "عنوان تبويب إعدادات الموجز" }, - "adSettingsTab": "إعدادات الإعلانات", - "@adSettingsTab": { + "advertisementsTab": "الإعلانات", + "@advertisementsTab": { "description": "عنوان تبويب إعدادات الإعلانات" }, - "inAppPromptsTab": "تنبيهات داخل التطبيق", - "@inAppPromptsTab": { - "description": "عنوان تبويب تنبيهات داخل التطبيق" + "generalTab": "عام", + "@generalTab": { + "description": "عنوان تبويب الإعدادات العامة" }, - "appOperationalStatusTab": "حالة تشغيل التطبيق", - "@appOperationalStatusTab": { - "description": "عنوان تبويب حالة تشغيل التطبيق" + "userContentLimitsTitle": "حدود المحتوى والموجز للمستخدم", + "@userContentLimitsTitle": { + "description": "عنوان قسم حدود المحتوى والموجز للمستخدم" + }, + "userContentLimitsDescription": "تعيين حدود للعناصر المتابعة والعناوين المحفوظة لكل فئة من فئات المستخدمين.", + "@userContentLimitsDescription": { + "description": "وصف قسم حدود المحتوى والموجز للمستخدم" + }, + "feedActionsTitle": "إجراءات الموجز", + "@feedActionsTitle": { + "description": "عنوان قسم إجراءات الموجز" + }, + "feedActionsDescription": "تكوين عدد مرات إدراج ودجات الإجراءات (مثل 'تقييم التطبيق') في الموجز.", + "@feedActionsDescription": { + "description": "وصف قسم إجراءات الموجز" + }, + "adSettingsTitle": "إعدادات الإعلانات", + "@adSettingsTitle": { + "description": "عنوان قسم إعدادات الإعلانات" + }, + "adSettingsDescription": "إدارة تكرار وموضع الإعلانات لأدوار المستخدمين المختلفة.", + "@adSettingsDescription": { + "description": "وصف قسم إعدادات الإعلانات" + }, + "maintenanceModeTitle": "وضع الصيانة", + "@maintenanceModeTitle": { + "description": "عنوان قسم وضع الصيانة" + }, + "maintenanceModeDescription": "تمكين لعرض شاشة صيانة لجميع المستخدمين.", + "@maintenanceModeDescription": { + "description": "وصف قسم وضع الصيانة" + }, + "forceUpdateTitle": "فرض تحديث التطبيق", + "@forceUpdateTitle": { + "description": "عنوان قسم فرض تحديث التطبيق" + }, + "forceUpdateDescription": "تكوين تحديثات التطبيق الإلزامية للمستخدمين.", + "@forceUpdateDescription": { + "description": "وصف قسم فرض تحديث التطبيق" }, "forceUpdateTab": "تحديث إجباري", "@forceUpdateTab": { @@ -198,10 +234,6 @@ "@confirmSaveButton": { "description": "تسمية زر تأكيد الحفظ في مربع الحوار" }, - "userContentLimitsDescription": "تحدد هذه الإعدادات الحد الأقصى لعدد البلدان ومصادر الأخبار والمواضيع والعناوين المحفوظة التي يمكن للمستخدم متابعتها أو حفظها. تختلف الحدود حسب نوع المستخدم (ضيف، عادي، مميز) وتؤثر بشكل مباشر على المحتوى الذي يمكن للمستخدمين تنسيقه.", - "@userContentLimitsDescription": { - "description": "وصف قسم حدود محتوى المستخدم" - }, "guestUserTab": "ضيف", "@guestUserTab": { "description": "عنوان تبويب دور المستخدم الضيف" @@ -262,10 +294,6 @@ "@premiumSavedHeadlinesLimitDescription": { "description": "وصف حد العناوين المحفوظة للمستخدم المميز" }, - "adSettingsDescription": "تتحكم هذه الإعدادات في كيفية عرض الإعلانات ضمن موجز الأخبار في التطبيق، مع قواعد مختلفة للمستخدمين الضيوف والعاديين والمميزين. تحدد \"تكرار الإعلان\" عدد مرات ظهور الإعلان، بينما تحدد \"فترة وضع الإعلان\" عدد عناصر الأخبار التي يجب عرضها قبل ظهور الإعلان الأول.", - "@adSettingsDescription": { - "description": "وصف قسم إعدادات الإعلانات" - }, "standardUserAdTab": "مستخدم عادي", "@standardUserAdTab": { "description": "عنوان تبويب المستخدم العادي في إعدادات الإعلانات" @@ -342,10 +370,6 @@ "@premiumUserArticlesBeforeInterstitialAdsDescription": { "description": "وصف مقالات المستخدم المميز قبل الإعلانات البينية" }, - "inAppPromptsDescription": "تتحكم هذه الإعدادات في عدد مرات عرض الرسائل الخاصة داخل التطبيق أو \"التنبيهات\" للمستخدمين في موجز الأخبار الخاص بهم. تشجع هذه التنبيهات على إجراءات مثل ربط حساب (للضيوف) أو الترقية إلى اشتراك مميز (للمستخدمين الموثقين). يختلف التكرار حسب نوع المستخدم.", - "@inAppPromptsDescription": { - "description": "وصف قسم تنبيهات داخل التطبيق" - }, "appOperationalStatusWarning": "تحذير: قد يؤثر تغيير حالة تشغيل التطبيق على جميع المستخدمين. استخدم بحذر شديد.", "@appOperationalStatusWarning": { "description": "رسالة تحذير لتغيير حالة تشغيل التطبيق" diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 5e2f455..bbe6290 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -109,7 +109,7 @@ "@appConfiguration": { "description": "Label for the app configuration navigation item" }, - "appConfigurationPageDescription": "Configure global settings for the mobile application, including user content limits, ad display rules, in-app prompts, operational status, and force update parameters.", + "appConfigurationPageDescription": "Manage global settings for the mobile app, from content limits to operational status.", "@appConfigurationPageDescription": { "description": "Description for the App Configuration page" }, @@ -121,21 +121,57 @@ "@appConfigurationPageTitle": { "description": "Title for the App Configuration page" }, - "userContentLimitsTab": "User Content Limits", - "@userContentLimitsTab": { - "description": "Tab title for User Content Limits" + "feedTab": "Feed", + "@feedTab": { + "description": "Tab title for Feed settings" }, - "adSettingsTab": "Ad Settings", - "@adSettingsTab": { - "description": "Tab title for Ad Settings" + "advertisementsTab": "Advertisements", + "@advertisementsTab": { + "description": "Tab title for Advertisements settings" }, - "inAppPromptsTab": "In-App Prompts", - "@inAppPromptsTab": { - "description": "Tab title for In-App Prompts" + "generalTab": "General", + "@generalTab": { + "description": "Tab title for General settings" }, - "appOperationalStatusTab": "App Operational Status", - "@appOperationalStatusTab": { - "description": "Tab title for App Operational Status" + "userContentLimitsTitle": "User Content & Feed Limits", + "@userContentLimitsTitle": { + "description": "Title for the User Content & Feed Limits section" + }, + "userContentLimitsDescription": "Set limits on followed items and saved headlines for each user tier.", + "@userContentLimitsDescription": { + "description": "Description for the User Content & Feed Limits section" + }, + "feedActionsTitle": "Feed Actions", + "@feedActionsTitle": { + "description": "Title for the Feed Actions section" + }, + "feedActionsDescription": "Configure how often to inject action widgets (e.g., 'Rate App') into the feed.", + "@feedActionsDescription": { + "description": "Description for the Feed Actions section" + }, + "adSettingsTitle": "Advertisement Settings", + "@adSettingsTitle": { + "description": "Title for the Advertisement Settings section" + }, + "adSettingsDescription": "Manage ad frequency and placement for different user roles.", + "@adSettingsDescription": { + "description": "Description for the Ad Settings section" + }, + "maintenanceModeTitle": "Maintenance Mode", + "@maintenanceModeTitle": { + "description": "Title for the Maintenance Mode section" + }, + "maintenanceModeDescription": "Enable to show a maintenance screen to all users.", + "@maintenanceModeDescription": { + "description": "Description for the Maintenance Mode section" + }, + "forceUpdateTitle": "Force App Update", + "@forceUpdateTitle": { + "description": "Title for the Force App Update section" + }, + "forceUpdateDescription": "Configure mandatory app updates for users.", + "@forceUpdateDescription": { + "description": "Description for the Force App Update section" }, "forceUpdateTab": "Force Update", "@forceUpdateTab": { @@ -198,10 +234,6 @@ "@confirmSaveButton": { "description": "Confirm save button label in dialog" }, - "userContentLimitsDescription": "These settings define the maximum number of countries, news sources, topics, and saved headlines a user can follow or save. Limits vary by user type (Guest, Standard, Premium) and directly impact what content users can curate.", - "@userContentLimitsDescription": { - "description": "Description for User Content Limits section" - }, "guestUserTab": "Guest", "@guestUserTab": { "description": "Tab title for Guest user role" @@ -262,10 +294,6 @@ "@premiumSavedHeadlinesLimitDescription": { "description": "Description for Premium Saved Headlines Limit" }, - "adSettingsDescription": "These settings control how advertisements are displayed within the app's news feed, with different rules for Guest, Standard, and Premium users. \"Ad Frequency\" determines how often an ad can appear, while \"Ad Placement Interval\" sets how many news items must be shown before the very first ad appears.", - "@adSettingsDescription": { - "description": "Description for Ad Settings section" - }, "standardUserAdTab": "Standard User", "@standardUserAdTab": { "description": "Tab title for Standard User in Ad Settings" @@ -342,10 +370,6 @@ "@premiumUserArticlesBeforeInterstitialAdsDescription": { "description": "Description for Premium User Articles Before Interstitial Ads" }, - "inAppPromptsDescription": "These settings control how often special in-app messages or \"prompts\" are shown to users in their news feed. These prompts encourage actions like linking an account (for guests) or upgrading to a premium subscription (for authenticated users). The frequency varies by user type.", - "@inAppPromptsDescription": { - "description": "Description for In-App Prompts section" - }, "appOperationalStatusWarning": "WARNING: Changing the app's operational status can affect all users. Use with extreme caution.", "@appOperationalStatusWarning": { "description": "Warning message for changing app operational status" @@ -1043,4 +1067,4 @@ "@feedActionTypeEnableNotifications": { "description": "Feed action type for enabling notifications" } -} \ No newline at end of file +} From f574b622bb28540902f4616c368994c812fba141 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 14 Jul 2025 18:50:41 +0100 Subject: [PATCH 3/3] refactor(app_config): update UI text with l10n - Replaced hardcoded strings with l10n - Improved UI readability and maintainability - Updated tab labels and descriptions - Updated expansion tile titles and descriptions - Minor formatting improvements --- .../view/app_configuration_page.dart | 124 +++++++++--------- 1 file changed, 64 insertions(+), 60 deletions(-) diff --git a/lib/app_configuration/view/app_configuration_page.dart b/lib/app_configuration/view/app_configuration_page.dart index e5affb5..6c3bb2b 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -65,18 +65,18 @@ class _AppConfigurationPageState extends State child: Text( l10n.appConfigurationPageDescription, style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), ), ), TabBar( controller: _tabController, tabAlignment: TabAlignment.start, isScrollable: true, - tabs: const [ - Tab(text: 'Feed'), - Tab(text: 'Advertisements'), - Tab(text: 'General'), + tabs: [ + Tab(text: l10n.feedTab), + Tab(text: l10n.advertisementsTab), + Tab(text: l10n.generalTab), ], ), ], @@ -94,8 +94,8 @@ class _AppConfigurationPageState extends State content: Text( l10n.appConfigSaveSuccessMessage, style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onPrimary, - ), + color: Theme.of(context).colorScheme.onPrimary, + ), ), backgroundColor: Theme.of(context).colorScheme.primary, ), @@ -113,8 +113,8 @@ class _AppConfigurationPageState extends State content: Text( state.exception!.toFriendlyMessage(context), style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onError, - ), + color: Theme.of(context).colorScheme.onError, + ), ), backgroundColor: Theme.of(context).colorScheme.error, ), @@ -148,7 +148,7 @@ class _AppConfigurationPageState extends State padding: const EdgeInsets.all(AppSpacing.lg), children: [ ExpansionTile( - title: const Text('User Content & Feed Limits'), + title: Text(l10n.userContentLimitsTitle), childrenPadding: const EdgeInsets.symmetric( horizontal: AppSpacing.xxl, ), @@ -160,7 +160,7 @@ class _AppConfigurationPageState extends State ], ), ExpansionTile( - title: const Text('In-App Action Prompts'), + title: Text(l10n.feedActionsTitle), childrenPadding: const EdgeInsets.symmetric( horizontal: AppSpacing.xxl, ), @@ -174,7 +174,7 @@ class _AppConfigurationPageState extends State padding: const EdgeInsets.all(AppSpacing.lg), children: [ ExpansionTile( - title: const Text('Advertisement Settings'), + title: Text(l10n.adSettingsTitle), childrenPadding: const EdgeInsets.symmetric( horizontal: AppSpacing.xxl, ), @@ -297,7 +297,7 @@ class _AppConfigurationPageState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Set limits on followed items and saved headlines for each user tier.', + l10n.userContentLimitsDescription, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ), @@ -376,7 +376,7 @@ class _AppConfigurationPageState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Manage ad frequency and placement for different user roles.', + l10n.adSettingsDescription, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ), @@ -455,7 +455,7 @@ class _AppConfigurationPageState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Configure how often to show prompts for actions like rating the app.', + l10n.feedActionsDescription, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ), @@ -511,7 +511,7 @@ class _AppConfigurationPageState extends State ) { final l10n = context.l10n; return ExpansionTile( - title: const Text('Maintenance Mode'), + title: Text(l10n.maintenanceModeTitle), childrenPadding: const EdgeInsets.symmetric( horizontal: AppSpacing.xxl, vertical: AppSpacing.md, @@ -521,10 +521,11 @@ class _AppConfigurationPageState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Enable to show a maintenance screen to all users.', + l10n.maintenanceModeDescription, 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), + ), ), const SizedBox(height: AppSpacing.lg), SwitchListTile( @@ -533,14 +534,14 @@ class _AppConfigurationPageState extends State value: remoteConfig.appStatus.isUnderMaintenance, onChanged: (value) { context.read().add( - AppConfigurationFieldChanged( - remoteConfig: remoteConfig.copyWith( - appStatus: remoteConfig.appStatus.copyWith( - isUnderMaintenance: value, + AppConfigurationFieldChanged( + remoteConfig: remoteConfig.copyWith( + appStatus: remoteConfig.appStatus.copyWith( + isUnderMaintenance: value, + ), + ), ), - ), - ), - ); + ); }, ), ], @@ -555,7 +556,7 @@ class _AppConfigurationPageState extends State ) { final l10n = context.l10n; return ExpansionTile( - title: const Text('Force App Update'), + title: Text(l10n.forceUpdateTitle), childrenPadding: const EdgeInsets.symmetric( horizontal: AppSpacing.xxl, vertical: AppSpacing.md, @@ -565,10 +566,11 @@ class _AppConfigurationPageState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Configure mandatory app updates for users.', + l10n.forceUpdateDescription, 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), + ), ), const SizedBox(height: AppSpacing.lg), _buildTextField( @@ -578,14 +580,14 @@ class _AppConfigurationPageState extends State value: remoteConfig.appStatus.latestAppVersion, onChanged: (value) { context.read().add( - AppConfigurationFieldChanged( - remoteConfig: remoteConfig.copyWith( - appStatus: remoteConfig.appStatus.copyWith( - latestAppVersion: value, + AppConfigurationFieldChanged( + remoteConfig: remoteConfig.copyWith( + appStatus: remoteConfig.appStatus.copyWith( + latestAppVersion: value, + ), + ), ), - ), - ), - ); + ); }, ), SwitchListTile( @@ -594,14 +596,14 @@ class _AppConfigurationPageState extends State value: remoteConfig.appStatus.isLatestVersionOnly, onChanged: (value) { context.read().add( - AppConfigurationFieldChanged( - remoteConfig: remoteConfig.copyWith( - appStatus: remoteConfig.appStatus.copyWith( - isLatestVersionOnly: value, + AppConfigurationFieldChanged( + remoteConfig: remoteConfig.copyWith( + appStatus: remoteConfig.appStatus.copyWith( + isLatestVersionOnly: value, + ), + ), ), - ), - ), - ); + ); }, ), _buildTextField( @@ -611,14 +613,14 @@ class _AppConfigurationPageState extends State value: remoteConfig.appStatus.iosUpdateUrl, onChanged: (value) { context.read().add( - AppConfigurationFieldChanged( - remoteConfig: remoteConfig.copyWith( - appStatus: remoteConfig.appStatus.copyWith( - iosUpdateUrl: value, + AppConfigurationFieldChanged( + remoteConfig: remoteConfig.copyWith( + appStatus: remoteConfig.appStatus.copyWith( + iosUpdateUrl: value, + ), + ), ), - ), - ), - ); + ); }, ), _buildTextField( @@ -628,14 +630,14 @@ class _AppConfigurationPageState extends State value: remoteConfig.appStatus.androidUpdateUrl, onChanged: (value) { context.read().add( - AppConfigurationFieldChanged( - remoteConfig: remoteConfig.copyWith( - appStatus: remoteConfig.appStatus.copyWith( - androidUpdateUrl: value, + AppConfigurationFieldChanged( + remoteConfig: remoteConfig.copyWith( + appStatus: remoteConfig.appStatus.copyWith( + androidUpdateUrl: value, + ), + ), ), - ), - ), - ); + ); }, ), ], @@ -1329,7 +1331,9 @@ class _AccountActionConfigFormState extends State<_AccountActionConfigForm> { ), value: _getDaysMap(accountActionConfig)[actionType] ?? 0, onChanged: (value) { - final currentMap = _getDaysMap(accountActionConfig); + final currentMap = Map.from( + _getDaysMap(accountActionConfig), + ); final updatedMap = Map.from(currentMap) ..[actionType] = value;