diff --git a/lib/app/bloc/app_bloc.dart b/lib/app/bloc/app_bloc.dart index bf43a96..5e57ab6 100644 --- a/lib/app/bloc/app_bloc.dart +++ b/lib/app/bloc/app_bloc.dart @@ -82,13 +82,18 @@ class AppBloc extends Bloc { displaySettings: const DisplaySettings( baseTheme: AppBaseTheme.system, accentTheme: AppAccentTheme.defaultBlue, - fontFamily: 'SystemDefault', - textScaleFactor: AppTextScaleFactor.medium, - fontWeight: AppFontWeight.regular, + fontFamily: 'SystemDefault', + textScaleFactor: AppTextScaleFactor.medium, + fontWeight: AppFontWeight.regular, + ), + language: languagesFixturesData.firstWhere( + (l) => l.code == 'en', + orElse: () => throw StateError( + 'Default language "en" not found in language fixtures.', ), - language: 'en', - feedPreferences: const FeedDisplayPreferences( - headlineDensity: HeadlineDensity.standard, + ), + feedPreferences: const FeedDisplayPreferences( + headlineDensity: HeadlineDensity.standard, headlineImageStyle: HeadlineImageStyle.largeThumbnail, showSourceInHeadlineFeed: true, showPublishDateInHeadlineFeed: true, diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 45bb839..26109da 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -233,7 +233,7 @@ class _AppViewState extends State<_AppView> { AppBaseTheme.dark => ThemeMode.dark, _ => ThemeMode.system, }, - locale: language != null ? Locale(language) : null, + locale: language != null ? Locale(language.code) : null, ), ), ), diff --git a/lib/app_configuration/view/app_configuration_page.dart b/lib/app_configuration/view/app_configuration_page.dart index 2f15050..5290168 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -158,12 +158,12 @@ class _AppConfigurationPageState extends State ], ), ExpansionTile( - title: Text(l10n.feedActionsTitle), + title: Text(l10n.feedDecoratorsTitle), childrenPadding: const EdgeInsets.symmetric( horizontal: AppSpacing.xxl, ), children: [ - _buildAccountActionConfigSection(context, remoteConfig), + _buildFeedDecoratorConfigSection(context, remoteConfig), ], ), ], @@ -361,6 +361,48 @@ class _AppConfigurationPageState extends State ); } + Widget _buildFeedDecoratorConfigSection( + BuildContext context, + RemoteConfig remoteConfig, + ) { + final l10n = AppLocalizationsX(context).l10n; + final decoratorConfigs = remoteConfig.feedDecoratorConfig.entries.toList(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.feedDecoratorsDescription, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + ), + ), + const SizedBox(height: AppSpacing.lg), + for (final decoratorEntry in decoratorConfigs) + ExpansionTile( + title: Text( + decoratorEntry.key.name.toUpperCase(), + ), + childrenPadding: const EdgeInsets.symmetric( + horizontal: AppSpacing.xxl, + ), + children: [ + _FeedDecoratorForm( + decoratorType: decoratorEntry.key, + remoteConfig: remoteConfig, + onConfigChanged: (newConfig) { + context.read().add( + AppConfigurationFieldChanged(remoteConfig: newConfig), + ); + }, + buildIntField: _buildIntField, + ), + ], + ), + ], + ); + } + Widget _buildAdConfigSection( BuildContext context, RemoteConfig remoteConfig, @@ -434,61 +476,6 @@ class _AppConfigurationPageState extends State ); } - Widget _buildAccountActionConfigSection( - BuildContext context, - RemoteConfig remoteConfig, - ) { - final l10n = AppLocalizationsX(context).l10n; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l10n.feedActionsDescription, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), - ), - ), - const SizedBox(height: AppSpacing.lg), - ExpansionTile( - title: Text(l10n.guestUserTab), - childrenPadding: const EdgeInsets.symmetric( - horizontal: AppSpacing.xxl, - ), - children: [ - _AccountActionConfigForm( - userRole: AppUserRole.guestUser, - remoteConfig: remoteConfig, - onConfigChanged: (newConfig) { - context.read().add( - AppConfigurationFieldChanged(remoteConfig: newConfig), - ); - }, - buildIntField: _buildIntField, - ), - ], - ), - ExpansionTile( - title: Text(l10n.standardUserAdTab), - childrenPadding: const EdgeInsets.symmetric( - horizontal: AppSpacing.xxl, - ), - children: [ - _AccountActionConfigForm( - userRole: AppUserRole.standardUser, - remoteConfig: remoteConfig, - onConfigChanged: (newConfig) { - context.read().add( - AppConfigurationFieldChanged(remoteConfig: newConfig), - ); - }, - buildIntField: _buildIntField, - ), - ], - ), - ], - ); - } - Widget _buildMaintenanceSection( BuildContext context, RemoteConfig remoteConfig, @@ -949,6 +936,200 @@ class _UserPreferenceLimitsFormState extends State<_UserPreferenceLimitsForm> { } } +class _FeedDecoratorForm extends StatefulWidget { + const _FeedDecoratorForm({ + required this.decoratorType, + required this.remoteConfig, + required this.onConfigChanged, + required this.buildIntField, + }); + + final FeedDecoratorType decoratorType; + final RemoteConfig remoteConfig; + final ValueChanged onConfigChanged; + final Widget Function( + BuildContext context, { + required String label, + required String description, + required int value, + required ValueChanged onChanged, + TextEditingController? controller, + }) buildIntField; + + @override + State<_FeedDecoratorForm> createState() => _FeedDecoratorFormState(); +} + +class _FeedDecoratorFormState extends State<_FeedDecoratorForm> { + late final TextEditingController _itemsToDisplayController; + late final Map _roleControllers; + + @override + void initState() { + super.initState(); + _initializeControllers(); + } + + @override + void didUpdateWidget(covariant _FeedDecoratorForm oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.remoteConfig.feedDecoratorConfig[widget.decoratorType] != + oldWidget.remoteConfig.feedDecoratorConfig[widget.decoratorType]) { + _updateControllers(); + } + } + + void _initializeControllers() { + final decoratorConfig = + widget.remoteConfig.feedDecoratorConfig[widget.decoratorType]!; + _itemsToDisplayController = TextEditingController( + text: decoratorConfig.itemsToDisplay?.toString() ?? '', + ); + + _roleControllers = { + for (final role in AppUserRole.values) + role: TextEditingController( + text: decoratorConfig.visibleTo[role]?.daysBetweenViews.toString() ?? + '', + ), + }; + } + + void _updateControllers() { + final decoratorConfig = + widget.remoteConfig.feedDecoratorConfig[widget.decoratorType]!; + _itemsToDisplayController.text = + decoratorConfig.itemsToDisplay?.toString() ?? ''; + for (final role in AppUserRole.values) { + _roleControllers[role]?.text = + decoratorConfig.visibleTo[role]?.daysBetweenViews.toString() ?? ''; + } + } + + @override + void dispose() { + _itemsToDisplayController.dispose(); + for (final controller in _roleControllers.values) { + controller.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizationsX(context).l10n; + final decoratorConfig = + widget.remoteConfig.feedDecoratorConfig[widget.decoratorType]!; + + return Column( + children: [ + SwitchListTile( + title: Text(l10n.enabledLabel), + value: decoratorConfig.enabled, + onChanged: (value) { + final newDecoratorConfig = decoratorConfig.copyWith(enabled: value); + final newFeedDecoratorConfig = + Map.from( + widget.remoteConfig.feedDecoratorConfig, + )..[widget.decoratorType] = newDecoratorConfig; + widget.onConfigChanged( + widget.remoteConfig.copyWith( + feedDecoratorConfig: newFeedDecoratorConfig, + ), + ); + }, + ), + if (decoratorConfig.category == + FeedDecoratorCategory.contentCollection) + widget.buildIntField( + context, + label: l10n.itemsToDisplayLabel, + description: l10n.itemsToDisplayDescription, + value: decoratorConfig.itemsToDisplay ?? 0, + onChanged: (value) { + final newDecoratorConfig = + decoratorConfig.copyWith(itemsToDisplay: value); + final newFeedDecoratorConfig = + Map.from( + widget.remoteConfig.feedDecoratorConfig, + )..[widget.decoratorType] = newDecoratorConfig; + widget.onConfigChanged( + widget.remoteConfig.copyWith( + feedDecoratorConfig: newFeedDecoratorConfig, + ), + ); + }, + controller: _itemsToDisplayController, + ), + ExpansionTile( + title: Text(l10n.roleSpecificSettingsTitle), + children: AppUserRole.values.map((role) { + final roleConfig = decoratorConfig.visibleTo[role]; + return CheckboxListTile( + title: Text(role.name), + value: roleConfig != null, + onChanged: (value) { + final newVisibleTo = + Map.from( + decoratorConfig.visibleTo, + ); + if (value == true) { + newVisibleTo[role] = + const FeedDecoratorRoleConfig(daysBetweenViews: 7); + } else { + newVisibleTo.remove(role); + } + final newDecoratorConfig = + decoratorConfig.copyWith(visibleTo: newVisibleTo); + final newFeedDecoratorConfig = + Map.from( + widget.remoteConfig.feedDecoratorConfig, + )..[widget.decoratorType] = newDecoratorConfig; + widget.onConfigChanged( + widget.remoteConfig.copyWith( + feedDecoratorConfig: newFeedDecoratorConfig, + ), + ); + }, + secondary: SizedBox( + width: 100, + child: widget.buildIntField( + context, + label: l10n.daysBetweenViewsLabel, + description: '', + value: roleConfig?.daysBetweenViews ?? 0, + onChanged: (value) { + if (roleConfig != null) { + final newRoleConfig = + roleConfig.copyWith(daysBetweenViews: value); + final newVisibleTo = + Map.from( + decoratorConfig.visibleTo, + )..[role] = newRoleConfig; + final newDecoratorConfig = + decoratorConfig.copyWith(visibleTo: newVisibleTo); + final newFeedDecoratorConfig = + Map.from( + widget.remoteConfig.feedDecoratorConfig, + )..[widget.decoratorType] = newDecoratorConfig; + widget.onConfigChanged( + widget.remoteConfig.copyWith( + feedDecoratorConfig: newFeedDecoratorConfig, + ), + ); + } + }, + controller: _roleControllers[role], + ), + ), + ); + }).toList(), + ), + ], + ); + } +} + class _AdConfigForm extends StatefulWidget { const _AdConfigForm({ required this.userRole, @@ -1201,131 +1382,3 @@ class _AdConfigFormState extends State<_AdConfigForm> { } } } - -class _AccountActionConfigForm extends StatefulWidget { - const _AccountActionConfigForm({ - required this.userRole, - required this.remoteConfig, - required this.onConfigChanged, - required this.buildIntField, - }); - - final AppUserRole userRole; - final RemoteConfig remoteConfig; - final ValueChanged onConfigChanged; - final Widget Function( - BuildContext context, { - required String label, - required String description, - required int value, - required ValueChanged onChanged, - TextEditingController? controller, - }) - buildIntField; - - @override - State<_AccountActionConfigForm> createState() => - _AccountActionConfigFormState(); -} - -class _AccountActionConfigFormState extends State<_AccountActionConfigForm> { - late final Map _controllers; - - @override - void initState() { - super.initState(); - _controllers = _initializeControllers(); - } - - @override - void didUpdateWidget(covariant _AccountActionConfigForm oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.remoteConfig.accountActionConfig != - oldWidget.remoteConfig.accountActionConfig) { - _updateControllers(); - } - } - - Map _initializeControllers() { - final config = widget.remoteConfig.accountActionConfig; - final daysMap = _getDaysMap(config); - return { - for (final type in FeedActionType.values) - type: TextEditingController(text: (daysMap[type] ?? 0).toString()), - }; - } - - void _updateControllers() { - final config = widget.remoteConfig.accountActionConfig; - final daysMap = _getDaysMap(config); - for (final type in FeedActionType.values) { - _controllers[type]?.text = (daysMap[type] ?? 0).toString(); - } - } - - Map _getDaysMap(AccountActionConfig config) { - switch (widget.userRole) { - case AppUserRole.guestUser: - return config.guestDaysBetweenActions; - case AppUserRole.standardUser: - return config.standardUserDaysBetweenActions; - case AppUserRole.premiumUser: - return {}; - } - } - - @override - void dispose() { - for (final controller in _controllers.values) { - controller.dispose(); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final accountActionConfig = widget.remoteConfig.accountActionConfig; - final relevantActionTypes = _getDaysMap(accountActionConfig).keys.toList(); - final l10n = AppLocalizationsX(context).l10n; - - return Column( - children: relevantActionTypes.map((actionType) { - final localizedActionType = switch (actionType) { - FeedActionType.linkAccount => l10n.feedActionTypeLinkAccount, - FeedActionType.rateApp => l10n.feedActionTypeRateApp, - FeedActionType.followTopics => l10n.feedActionTypeFollowTopics, - FeedActionType.followSources => l10n.feedActionTypeFollowSources, - FeedActionType.upgrade => l10n.feedActionTypeUpgrade, - FeedActionType.enableNotifications => - l10n.feedActionTypeEnableNotifications, - }; - return widget.buildIntField( - context, - label: '$localizedActionType ${l10n.daysSuffix}', - description: l10n.daysBetweenPromptDescription(localizedActionType), - value: _getDaysMap(accountActionConfig)[actionType] ?? 0, - onChanged: (value) { - final currentMap = Map.from( - _getDaysMap(accountActionConfig), - ); - final updatedMap = Map.from(currentMap) - ..[actionType] = value; - - final newConfig = widget.userRole == AppUserRole.guestUser - ? accountActionConfig.copyWith( - guestDaysBetweenActions: updatedMap, - ) - : accountActionConfig.copyWith( - standardUserDaysBetweenActions: updatedMap, - ); - - widget.onConfigChanged( - widget.remoteConfig.copyWith(accountActionConfig: newConfig), - ); - }, - controller: _controllers[actionType], - ); - }).toList(), - ); - } -} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 82af566..d237ee0 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -314,6 +314,18 @@ abstract class AppLocalizations { /// **'Configure how often to inject action widgets (e.g., \'Rate App\') into the feed.'** String get feedActionsDescription; + /// Title for the Feed Decorators section + /// + /// In en, this message translates to: + /// **'Feed Decorators'** + String get feedDecoratorsTitle; + + /// Description for the Feed Decorators section + /// + /// In en, this message translates to: + /// **'Configure how content is decorated and presented in the feed for different user roles.'** + String get feedDecoratorsDescription; + /// Title for the Advertisement Settings section /// /// In en, this message translates to: @@ -344,10 +356,10 @@ abstract class AppLocalizations { /// **'Force App Update'** String get forceUpdateTitle; - /// Description for Force Update Configuration section + /// Description for the Force App Update 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.'** + /// **'Configure mandatory app updates for users.'** String get forceUpdateDescription; /// Tab title for Force Update @@ -1760,11 +1772,41 @@ abstract class AppLocalizations { /// **'Deleted \'\'{title}\'\'.'** String headlineDeleted(String title); - /// No description provided for @undo. + /// Label for undo button /// /// In en, this message translates to: /// **'Undo'** String get undo; + + /// Label for enabled switch + /// + /// In en, this message translates to: + /// **'Enabled'** + String get enabledLabel; + + /// Label for items to display input + /// + /// In en, this message translates to: + /// **'Items to Display'** + String get itemsToDisplayLabel; + + /// Description for items to display input + /// + /// In en, this message translates to: + /// **'Number of items to display in this decorator.'** + String get itemsToDisplayDescription; + + /// Title for role specific settings section + /// + /// In en, this message translates to: + /// **'Role Specific Settings'** + String get roleSpecificSettingsTitle; + + /// Label for days between views input + /// + /// In en, this message translates to: + /// **'Days Between Views'** + String get daysBetweenViewsLabel; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index ba60da2..c99af05 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -131,6 +131,13 @@ class AppLocalizationsAr extends AppLocalizations { String get feedActionsDescription => 'تكوين عدد مرات إدراج ودجات الإجراءات (مثل \'تقييم التطبيق\') في الموجز.'; + @override + String get feedDecoratorsTitle => 'زينة الموجز'; + + @override + String get feedDecoratorsDescription => + 'تكوين كيفية تزيين وعرض المحتوى في الموجز لأدوار المستخدمين المختلفة.'; + @override String get adSettingsTitle => 'إعدادات الإعلانات'; @@ -150,7 +157,7 @@ class AppLocalizationsAr extends AppLocalizations { @override String get forceUpdateDescription => - 'تتحكم هذه الإعدادات في فرض إصدار التطبيق. سيتم إجبار المستخدمين الذين يستخدمون إصدارات أقل من الحد الأدنى المسموح به على التحديث.'; + 'تكوين تحديثات التطبيق الإلزامية للمستخدمين.'; @override String get forceUpdateTab => 'تحديث إجباري'; @@ -782,7 +789,7 @@ class AppLocalizationsAr extends AppLocalizations { String get appStatusMaintenance => 'صيانة'; @override - String get appStatusOperational => 'تشغيلي'; + String get appStatusOperational => 'يعاري'; @override String get isUnderMaintenanceLabel => 'تحت الصيانة'; @@ -930,4 +937,20 @@ class AppLocalizationsAr extends AppLocalizations { @override String get undo => 'تراجع'; + + @override + String get enabledLabel => 'مفعل'; + + @override + String get itemsToDisplayLabel => 'العناصر المعروضة'; + + @override + String get itemsToDisplayDescription => + 'عدد العناصر التي تظهر في هذه الزينة.'; + + @override + String get roleSpecificSettingsTitle => 'إعدادات مخصصة للدور'; + + @override + String get daysBetweenViewsLabel => 'الأيام بين المشاهدات'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 2779c0a..4b2bc8c 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -130,6 +130,13 @@ class AppLocalizationsEn extends AppLocalizations { String get feedActionsDescription => 'Configure how often to inject action widgets (e.g., \'Rate App\') into the feed.'; + @override + String get feedDecoratorsTitle => 'Feed Decorators'; + + @override + String get feedDecoratorsDescription => + 'Configure how content is decorated and presented in the feed for different user roles.'; + @override String get adSettingsTitle => 'Advertisement Settings'; @@ -149,7 +156,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get forceUpdateDescription => - 'These settings control app version enforcement. Users on versions below the minimum allowed will be forced to update.'; + 'Configure mandatory app updates for users.'; @override String get forceUpdateTab => 'Force Update'; @@ -929,4 +936,20 @@ class AppLocalizationsEn extends AppLocalizations { @override String get undo => 'Undo'; + + @override + String get enabledLabel => 'Enabled'; + + @override + String get itemsToDisplayLabel => 'Items to Display'; + + @override + String get itemsToDisplayDescription => + 'Number of items to display in this decorator.'; + + @override + String get roleSpecificSettingsTitle => 'Role Specific Settings'; + + @override + String get daysBetweenViewsLabel => 'Days Between Views'; } diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index b68b35c..ce7f73c 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -159,6 +159,14 @@ "@feedActionsDescription": { "description": "وصف قسم إجراءات الموجز" }, + "feedDecoratorsTitle": "زينة الموجز", + "@feedDecoratorsTitle": { + "description": "عنوان قسم زينة الموجز" + }, + "feedDecoratorsDescription": "تكوين كيفية تزيين وعرض المحتوى في الموجز لأدوار المستخدمين المختلفة.", + "@feedDecoratorsDescription": { + "description": "وصف قسم زينة الموجز" + }, "adSettingsTitle": "إعدادات الإعلانات", "@adSettingsTitle": { "description": "عنوان قسم إعدادات الإعلانات" @@ -412,10 +420,6 @@ "@forceUpdateConfigurationTitle": { "description": "عنوان قسم إعدادات التحديث الإجباري" }, - "forceUpdateDescription": "تتحكم هذه الإعدادات في فرض إصدار التطبيق. سيتم إجبار المستخدمين الذين يستخدمون إصدارات أقل من الحد الأدنى المسموح به على التحديث.", - "@forceUpdateDescription": { - "description": "وصف قسم إعدادات التحديث الإجباري" - }, "minAllowedAppVersionLabel": "الحد الأدنى المسموح به لإصدار التطبيق", "@minAllowedAppVersionLabel": { "description": "تسمية الحد الأدنى المسموح به لإصدار التطبيق" @@ -964,9 +968,9 @@ "@appStatusMaintenance": { "description": "نص حالة التطبيق 'صيانة'" }, - "appStatusOperational": "تشغيلي", + "appStatusOperational": "يعاري", "@appStatusOperational": { - "description": "نص حالة التطبيق 'تشغيلي'" + "description": "نص حالة التطبيق 'يعاري'" }, "isUnderMaintenanceLabel": "تحت الصيانة", "@isUnderMaintenanceLabel": { @@ -1160,5 +1164,28 @@ } } }, - "undo": "تراجع" + "undo": "تراجع", + "@undo": { + "description": "تلميح لزر التراجع" + }, + "enabledLabel": "مفعل", + "@enabledLabel": { + "description": "تسمية مفتاح التفعيل" + }, + "itemsToDisplayLabel": "العناصر المعروضة", + "@itemsToDisplayLabel": { + "description": "تسمية حقل العناصر المعروضة" + }, + "itemsToDisplayDescription": "عدد العناصر التي تظهر في هذه الزينة.", + "@itemsToDisplayDescription": { + "description": "وصف حقل العناصر المعروضة" + }, + "roleSpecificSettingsTitle": "إعدادات مخصصة للدور", + "@roleSpecificSettingsTitle": { + "description": "عنوان قسم الإعدادات المخصصة للدور" + }, + "daysBetweenViewsLabel": "الأيام بين المشاهدات", + "@daysBetweenViewsLabel": { + "description": "تسمية حقل الأيام بين المشاهدات" + } } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 1120c81..7635d49 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -159,6 +159,14 @@ "@feedActionsDescription": { "description": "Description for the Feed Actions section" }, + "feedDecoratorsTitle": "Feed Decorators", + "@feedDecoratorsTitle": { + "description": "Title for the Feed Decorators section" + }, + "feedDecoratorsDescription": "Configure how content is decorated and presented in the feed for different user roles.", + "@feedDecoratorsDescription": { + "description": "Description for the Feed Decorators section" + }, "adSettingsTitle": "Advertisement Settings", "@adSettingsTitle": { "description": "Title for the Advertisement Settings section" @@ -412,10 +420,6 @@ "@forceUpdateConfigurationTitle": { "description": "Title for Force Update Configuration section" }, - "forceUpdateDescription": "These settings control app version enforcement. Users on versions below the minimum allowed will be forced to update.", - "@forceUpdateDescription": { - "description": "Description for Force Update Configuration section" - }, "minAllowedAppVersionLabel": "Minimum Allowed App Version", "@minAllowedAppVersionLabel": { "description": "Label for Minimum Allowed App Version" @@ -996,10 +1000,6 @@ "@androidUpdateUrlLabel": { "description": "Label for Android Update URL" }, - "androidUpdateUrlDescription": "URL to the app on the Google Play Store.", - "@androidUpdateUrlDescription": { - "description": "Description for Android Update URL" - }, "followedItemsLimitLabel": "Followed Items Limit", "@followedItemsLimitLabel": { "description": "Label for Followed Items Limit" @@ -1159,5 +1159,28 @@ } } }, - "undo": "Undo" + "undo": "Undo", + "@undo": { + "description": "Label for undo button" + }, + "enabledLabel": "Enabled", + "@enabledLabel": { + "description": "Label for enabled switch" + }, + "itemsToDisplayLabel": "Items to Display", + "@itemsToDisplayLabel": { + "description": "Label for items to display input" + }, + "itemsToDisplayDescription": "Number of items to display in this decorator.", + "@itemsToDisplayDescription": { + "description": "Description for items to display input" + }, + "roleSpecificSettingsTitle": "Role Specific Settings", + "@roleSpecificSettingsTitle": { + "description": "Title for role specific settings section" + }, + "daysBetweenViewsLabel": "Days Between Views", + "@daysBetweenViewsLabel": { + "description": "Label for days between views input" + } } diff --git a/lib/settings/bloc/settings_bloc.dart b/lib/settings/bloc/settings_bloc.dart index d7335cd..5604494 100644 --- a/lib/settings/bloc/settings_bloc.dart +++ b/lib/settings/bloc/settings_bloc.dart @@ -35,6 +35,8 @@ class SettingsBloc extends Bloc { ); emit(SettingsLoadSuccess(userAppSettings: userAppSettings)); } on NotFoundException { + // If settings are not found, create default settings for the user. + // This ensures that a user always has a valid settings object. final defaultSettings = UserAppSettings( id: event.userId!, displaySettings: const DisplaySettings( @@ -44,7 +46,12 @@ class SettingsBloc extends Bloc { textScaleFactor: AppTextScaleFactor.medium, fontWeight: AppFontWeight.regular, ), - language: 'en', + language: languagesFixturesData.firstWhere( + (l) => l.code == 'en', + orElse: () => throw StateError( + 'Default language "en" not found in language fixtures.', + ), + ), feedPreferences: const FeedDisplayPreferences( headlineDensity: HeadlineDensity.standard, headlineImageStyle: HeadlineImageStyle.largeThumbnail, diff --git a/lib/settings/bloc/settings_event.dart b/lib/settings/bloc/settings_event.dart index 3f732de..5bbcfa3 100644 --- a/lib/settings/bloc/settings_event.dart +++ b/lib/settings/bloc/settings_event.dart @@ -97,8 +97,7 @@ final class SettingsLanguageChanged extends SettingsEvent { /// {@macro settings_language_changed} const SettingsLanguageChanged(this.language); - /// The new language. - final AppLanguage language; + final Language language; @override List get props => [language]; diff --git a/lib/settings/view/settings_page.dart b/lib/settings/view/settings_page.dart index b1abec4..d5ce7a4 100644 --- a/lib/settings/view/settings_page.dart +++ b/lib/settings/view/settings_page.dart @@ -413,7 +413,7 @@ class _LanguageSelectionList extends StatelessWidget { }); /// The currently selected language. - final AppLanguage currentLanguage; + final Language currentLanguage; /// The localized strings for the application. final AppLocalizations l10n; @@ -421,13 +421,13 @@ class _LanguageSelectionList extends StatelessWidget { @override Widget build(BuildContext context) { return ListView.builder( - itemCount: _supportedLanguages.length, + itemCount: languagesFixturesData.length, itemBuilder: (context, index) { - final language = _supportedLanguages[index]; + final language = languagesFixturesData[index]; final isSelected = language == currentLanguage; return ListTile( title: Text( - _getLanguageName(language, l10n), + language.name, style: Theme.of(context).textTheme.titleMedium, ), trailing: isSelected @@ -436,25 +436,12 @@ class _LanguageSelectionList extends StatelessWidget { onTap: () { if (!isSelected) { context.read().add( - SettingsLanguageChanged(language), - ); + SettingsLanguageChanged(language), + ); } }, ); }, ); } - - String _getLanguageName(AppLanguage language, AppLocalizations l10n) { - switch (language) { - case 'en': - return l10n.englishLanguage; - case 'ar': - return l10n.arabicLanguage; - default: - return language; - } - } - - static const List _supportedLanguages = ['en', 'ar']; } diff --git a/pubspec.lock b/pubspec.lock index d40ea3f..882ffcf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -32,7 +32,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: f48dc0555b1a28ed3c51215f3a93403dbbc46ea0 + resolved-ref: "9c25c6bff76c554e5220368536fcb9370758f48f" url: "https://github.com/flutter-news-app-full-source-code/auth-inmemory" source: git version: "0.0.0" @@ -90,7 +90,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: f648500814ec636a2930498756029d29b363194a + resolved-ref: "55f27de67c71ecf0406fb8838b496fa8ed9f4d9c" url: "https://github.com/flutter-news-app-full-source-code/core.git" source: git version: "0.0.0" @@ -166,10 +166,10 @@ packages: dependency: transitive description: name: dio - sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 url: "https://pub.dev" source: hosted - version: "5.8.0+1" + version: "5.9.0" dio_web_adapter: dependency: transitive description: @@ -285,10 +285,10 @@ packages: dependency: transitive description: name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" http_client: dependency: "direct main" description: @@ -364,6 +364,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" nested: dependency: transitive description: @@ -472,10 +480,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" + sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e" url: "https://pub.dev" source: hosted - version: "2.4.10" + version: "2.4.11" shared_preferences_foundation: dependency: transitive description: