diff --git a/lib/app_configuration/widgets/ad_config_form.dart b/lib/app_configuration/widgets/ad_config_form.dart index 2ddd61d5..5415852c 100644 --- a/lib/app_configuration/widgets/ad_config_form.dart +++ b/lib/app_configuration/widgets/ad_config_form.dart @@ -3,10 +3,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; /// {@template ad_config_form} -/// A form widget for configuring ad settings based on user role. +/// A form widget for configuring global ad settings. /// -/// This widget uses a [TabBar] to allow selection of an [AppUserRole] -/// and then conditionally renders the relevant input fields for that role. +/// This widget primarily controls the global enable/disable switch for ads. /// {@endtemplate} class AdConfigForm extends StatefulWidget { /// {@macro ad_config_form} @@ -26,96 +25,7 @@ class AdConfigForm extends StatefulWidget { State createState() => _AdConfigFormState(); } -class _AdConfigFormState extends State - with SingleTickerProviderStateMixin { - late TabController _tabController; - late final Map _adFrequencyControllers; - late final Map - _adPlacementIntervalControllers; - - @override - void initState() { - super.initState(); - _tabController = TabController( - length: AppUserRole.values.length, - vsync: this, - ); - _initializeControllers(); - } - - @override - void didUpdateWidget(covariant AdConfigForm oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.remoteConfig.adConfig != oldWidget.remoteConfig.adConfig) { - _updateControllers(); - } - } - - void _initializeControllers() { - final adConfig = widget.remoteConfig.adConfig; - _adFrequencyControllers = { - for (final role in AppUserRole.values) - role: - TextEditingController( - text: _getAdFrequency(adConfig, role).toString(), - ) - ..selection = TextSelection.collapsed( - offset: _getAdFrequency(adConfig, role).toString().length, - ), - }; - _adPlacementIntervalControllers = { - for (final role in AppUserRole.values) - role: - TextEditingController( - text: _getAdPlacementInterval(adConfig, role).toString(), - ) - ..selection = TextSelection.collapsed( - offset: _getAdPlacementInterval( - adConfig, - role, - ).toString().length, - ), - }; - } - - void _updateControllers() { - final adConfig = widget.remoteConfig.adConfig; - for (final role in AppUserRole.values) { - final newFrequencyValue = _getAdFrequency(adConfig, role).toString(); - if (_adFrequencyControllers[role]?.text != newFrequencyValue) { - _adFrequencyControllers[role]?.text = newFrequencyValue; - _adFrequencyControllers[role]?.selection = TextSelection.collapsed( - offset: newFrequencyValue.length, - ); - } - - final newPlacementIntervalValue = _getAdPlacementInterval( - adConfig, - role, - ).toString(); - if (_adPlacementIntervalControllers[role]?.text != - newPlacementIntervalValue) { - _adPlacementIntervalControllers[role]?.text = newPlacementIntervalValue; - _adPlacementIntervalControllers[role]?.selection = - TextSelection.collapsed( - offset: newPlacementIntervalValue.length, - ); - } - } - } - - @override - void dispose() { - _tabController.dispose(); - for (final controller in _adFrequencyControllers.values) { - controller.dispose(); - } - for (final controller in _adPlacementIntervalControllers.values) { - controller.dispose(); - } - super.dispose(); - } - +class _AdConfigFormState extends State { @override Widget build(BuildContext context) { final adConfig = widget.remoteConfig.adConfig; @@ -138,38 +48,4 @@ class _AdConfigFormState extends State ], ); } - - int _getAdFrequency(AdConfig config, AppUserRole role) { - switch (role) { - case AppUserRole.guestUser: - return config.feedAdConfiguration.frequencyConfig.guestAdFrequency; - case AppUserRole.standardUser: - return config - .feedAdConfiguration - .frequencyConfig - .authenticatedAdFrequency; - case AppUserRole.premiumUser: - return config.feedAdConfiguration.frequencyConfig.premiumAdFrequency; - } - } - - int _getAdPlacementInterval(AdConfig config, AppUserRole role) { - switch (role) { - case AppUserRole.guestUser: - return config - .feedAdConfiguration - .frequencyConfig - .guestAdPlacementInterval; - case AppUserRole.standardUser: - return config - .feedAdConfiguration - .frequencyConfig - .authenticatedAdPlacementInterval; - case AppUserRole.premiumUser: - return config - .feedAdConfiguration - .frequencyConfig - .premiumAdPlacementInterval; - } - } } diff --git a/lib/app_configuration/widgets/article_ad_settings_form.dart b/lib/app_configuration/widgets/article_ad_settings_form.dart index 828f6be9..274aae00 100644 --- a/lib/app_configuration/widgets/article_ad_settings_form.dart +++ b/lib/app_configuration/widgets/article_ad_settings_form.dart @@ -1,8 +1,10 @@ import 'package:core/core.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/banner_ad_shape_l10n.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/in_article_ad_slot_type_l10n.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/app_user_role_l10n.dart'; import 'package:ui_kit/ui_kit.dart'; /// {@template article_ad_settings_form} @@ -28,14 +30,28 @@ class ArticleAdSettingsForm extends StatefulWidget { class _ArticleAdSettingsFormState extends State with SingleTickerProviderStateMixin { + late TabController _tabController; + @override void initState() { super.initState(); + _tabController = TabController( + length: AppUserRole.values.length, + vsync: this, + ); } @override void didUpdateWidget(covariant ArticleAdSettingsForm oldWidget) { super.didUpdateWidget(oldWidget); + // No specific controller updates needed here as the UI rebuilds based on + // the remoteConfig directly. + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); } @override @@ -134,29 +150,35 @@ class _ArticleAdSettingsFormState extends State textAlign: TextAlign.start, ), const SizedBox(height: AppSpacing.lg), - ...articleAdConfig.inArticleAdSlotConfigurations.map( - (slotConfig) => SwitchListTile( - title: Text(slotConfig.slotType.l10n(context)), - value: slotConfig.enabled, - onChanged: (value) { - final updatedSlots = articleAdConfig - .inArticleAdSlotConfigurations - .map( - (e) => e.slotType == slotConfig.slotType - ? e.copyWith(enabled: value) - : e, - ) - .toList(); - widget.onConfigChanged( - widget.remoteConfig.copyWith( - adConfig: adConfig.copyWith( - articleAdConfiguration: articleAdConfig.copyWith( - inArticleAdSlotConfigurations: updatedSlots, - ), + Align( + alignment: AlignmentDirectional.centerStart, + child: SizedBox( + height: kTextTabBarHeight, + child: TabBar( + controller: _tabController, + tabAlignment: TabAlignment.start, + isScrollable: true, + tabs: AppUserRole.values + .map((role) => Tab(text: role.l10n(context))) + .toList(), + ), + ), + ), + const SizedBox(height: AppSpacing.lg), + SizedBox( + height: 250, + child: TabBarView( + controller: _tabController, + children: AppUserRole.values + .map( + (role) => _buildRoleSpecificFields( + context, + l10n, + role, + articleAdConfig, ), - ), - ); - }, + ) + .toList(), ), ), ], @@ -164,4 +186,93 @@ class _ArticleAdSettingsFormState extends State ], ); } + + /// Builds role-specific configuration fields for in-article ad slots. + /// + /// This widget displays checkboxes for each [InArticleAdSlotType] for a + /// given [AppUserRole], allowing to enable/disable specific ad slots. + Widget _buildRoleSpecificFields( + BuildContext context, + AppLocalizations l10n, + AppUserRole role, + ArticleAdConfiguration config, + ) { + final roleSlots = config.visibleTo[role]; + + return Column( + children: [ + SwitchListTile( + title: Text(l10n.enableInArticleAdsForRoleLabel(role.l10n(context))), + value: roleSlots != null, + onChanged: (value) { + final newVisibleTo = + Map>.from( + config.visibleTo, + ); + if (value) { + // Default values when enabling for a role + newVisibleTo[role] = { + InArticleAdSlotType.aboveArticleContinueReadingButton: true, + InArticleAdSlotType.belowArticleContinueReadingButton: true, + }; + } else { + newVisibleTo.remove(role); + } + + widget.onConfigChanged( + widget.remoteConfig.copyWith( + adConfig: widget.remoteConfig.adConfig.copyWith( + articleAdConfiguration: config.copyWith( + visibleTo: newVisibleTo, + ), + ), + ), + ); + }, + ), + if (roleSlots != null) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.lg, + vertical: AppSpacing.sm, + ), + child: Column( + children: [ + // SwitchListTile for each InArticleAdSlotType + for (final slotType in InArticleAdSlotType.values) + CheckboxListTile( + title: Text(slotType.l10n(context)), + value: roleSlots[slotType] ?? false, + onChanged: (value) { + final newRoleSlots = Map.from( + roleSlots, + ); + if (value ?? false) { + newRoleSlots[slotType] = true; + } else { + newRoleSlots.remove(slotType); + } + + final newVisibleTo = + Map>.from( + config.visibleTo, + )..[role] = newRoleSlots; + + widget.onConfigChanged( + widget.remoteConfig.copyWith( + adConfig: widget.remoteConfig.adConfig.copyWith( + articleAdConfiguration: config.copyWith( + visibleTo: newVisibleTo, + ), + ), + ), + ); + }, + ), + ], + ), + ), + ], + ); + } } diff --git a/lib/app_configuration/widgets/feed_ad_settings_form.dart b/lib/app_configuration/widgets/feed_ad_settings_form.dart index 211dd5d9..202a1013 100644 --- a/lib/app_configuration/widgets/feed_ad_settings_form.dart +++ b/lib/app_configuration/widgets/feed_ad_settings_form.dart @@ -31,7 +31,13 @@ class FeedAdSettingsForm extends StatefulWidget { class _FeedAdSettingsFormState extends State with SingleTickerProviderStateMixin { late TabController _tabController; + + /// Controllers for ad frequency fields, mapped by user role. + /// These are used to manage text input for each role's ad frequency. late final Map _adFrequencyControllers; + + /// Controllers for ad placement interval fields, mapped by user role. + /// These are used to manage text input for each role's ad placement interval. late final Map _adPlacementIntervalControllers; @@ -43,48 +49,12 @@ class _FeedAdSettingsFormState extends State vsync: this, ); _initializeControllers(); - _tabController.addListener(_onTabChanged); - } - - void _onTabChanged() { - if (_tabController.indexIsChanging) return; - - final selectedRole = AppUserRole.values[_tabController.index]; - if (selectedRole == AppUserRole.premiumUser) { - final adConfig = widget.remoteConfig.adConfig; - final feedAdConfig = adConfig.feedAdConfiguration; - - // If the values for premium are not 0, update the config. - // This enforces the business rule that premium users do not see ads. - if (feedAdConfig.frequencyConfig.premiumAdFrequency != 0 || - feedAdConfig.frequencyConfig.premiumAdPlacementInterval != 0) { - final updatedFrequencyConfig = feedAdConfig.frequencyConfig.copyWith( - premiumAdFrequency: 0, - premiumAdPlacementInterval: 0, - ); - final updatedFeedAdConfig = feedAdConfig.copyWith( - frequencyConfig: updatedFrequencyConfig, - ); - widget.onConfigChanged( - widget.remoteConfig.copyWith( - adConfig: adConfig.copyWith( - feedAdConfiguration: updatedFeedAdConfig, - ), - ), - ); - } - } - } - - @override - void didUpdateWidget(covariant FeedAdSettingsForm oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.remoteConfig.adConfig.feedAdConfiguration != - oldWidget.remoteConfig.adConfig.feedAdConfiguration) { - _updateControllers(); - } + // Removed _tabController.addListener(_onTabChanged); as automatic disabling + // for premium users is no longer required. } + /// Initializes text editing controllers for each user role based on current + /// remote config values. void _initializeControllers() { final feedAdConfig = widget.remoteConfig.adConfig.feedAdConfiguration; _adFrequencyControllers = { @@ -112,6 +82,8 @@ class _FeedAdSettingsFormState extends State }; } + /// Updates text editing controllers when the widget's remote config changes. + /// This ensures the form fields reflect the latest configuration. void _updateControllers() { final feedAdConfig = widget.remoteConfig.adConfig.feedAdConfiguration; for (final role in AppUserRole.values) { @@ -138,11 +110,18 @@ class _FeedAdSettingsFormState extends State } } + @override + void didUpdateWidget(covariant FeedAdSettingsForm oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.remoteConfig.adConfig.feedAdConfiguration != + oldWidget.remoteConfig.adConfig.feedAdConfiguration) { + _updateControllers(); + } + } + @override void dispose() { - _tabController - ..removeListener(_onTabChanged) - ..dispose(); + _tabController.dispose(); for (final controller in _adFrequencyControllers.values) { controller.dispose(); } @@ -245,7 +224,6 @@ class _FeedAdSettingsFormState extends State textAlign: TextAlign.start, ), const SizedBox(height: AppSpacing.lg), - // Replaced SegmentedButton with TabBar for role selection Align( alignment: AlignmentDirectional.centerStart, child: SizedBox( @@ -261,9 +239,8 @@ class _FeedAdSettingsFormState extends State ), ), const SizedBox(height: AppSpacing.lg), - // TabBarView to display role-specific fields SizedBox( - height: 250, + height: 350, child: TabBarView( controller: _tabController, children: AppUserRole.values @@ -284,134 +261,118 @@ class _FeedAdSettingsFormState extends State ); } + /// Builds role-specific configuration fields for feed ad frequency. + /// + /// This widget displays input fields for ad frequency and placement interval + /// for a given [AppUserRole]. Widget _buildRoleSpecificFields( BuildContext context, AppLocalizations l10n, AppUserRole role, FeedAdConfiguration config, ) { - // Premium users do not see ads, so their settings are disabled. - final isEnabled = role != AppUserRole.premiumUser; + final roleConfig = config.visibleTo[role]; + // Removed isEnabled check as premium users can now be manually configured. return Column( children: [ - AppConfigIntField( - label: l10n.adFrequencyLabel, - description: l10n.adFrequencyDescription, - value: _getAdFrequency(config, role), + SwitchListTile( + title: Text(l10n.visibleToRoleLabel(role.l10n(context))), + value: roleConfig != null, onChanged: (value) { - widget.onConfigChanged( - widget.remoteConfig.copyWith( - adConfig: widget.remoteConfig.adConfig.copyWith( - feedAdConfiguration: _updateAdFrequency(config, value, role), - ), - ), + final newVisibleTo = Map.from( + config.visibleTo, ); - }, - controller: _adFrequencyControllers[role], - enabled: isEnabled, - ), - AppConfigIntField( - label: l10n.adPlacementIntervalLabel, - description: l10n.adPlacementIntervalDescription, - value: _getAdPlacementInterval(config, role), - onChanged: (value) { + if (value) { + // Default values when enabling for a role + newVisibleTo[role] = const FeedAdFrequencyConfig( + adFrequency: 5, + adPlacementInterval: 3, + ); + } else { + newVisibleTo.remove(role); + } widget.onConfigChanged( widget.remoteConfig.copyWith( adConfig: widget.remoteConfig.adConfig.copyWith( - feedAdConfiguration: _updateAdPlacementInterval( - config, - value, - role, + feedAdConfiguration: config.copyWith( + visibleTo: newVisibleTo, ), ), ), ); }, - controller: _adPlacementIntervalControllers[role], - enabled: isEnabled, ), + if (roleConfig != null) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.lg, + vertical: AppSpacing.sm, + ), + child: Column( + children: [ + AppConfigIntField( + label: l10n.adFrequencyLabel, + description: l10n.adFrequencyDescription, + value: roleConfig.adFrequency, + onChanged: (value) { + final newRoleConfig = roleConfig.copyWith( + adFrequency: value, + ); + final newVisibleTo = + Map.from( + config.visibleTo, + )..[role] = newRoleConfig; + widget.onConfigChanged( + widget.remoteConfig.copyWith( + adConfig: widget.remoteConfig.adConfig.copyWith( + feedAdConfiguration: config.copyWith( + visibleTo: newVisibleTo, + ), + ), + ), + ); + }, + controller: _adFrequencyControllers[role], + ), + AppConfigIntField( + label: l10n.adPlacementIntervalLabel, + description: l10n.adPlacementIntervalDescription, + value: roleConfig.adPlacementInterval, + onChanged: (value) { + final newRoleConfig = roleConfig.copyWith( + adPlacementInterval: value, + ); + final newVisibleTo = + Map.from( + config.visibleTo, + )..[role] = newRoleConfig; + widget.onConfigChanged( + widget.remoteConfig.copyWith( + adConfig: widget.remoteConfig.adConfig.copyWith( + feedAdConfiguration: config.copyWith( + visibleTo: newVisibleTo, + ), + ), + ), + ); + }, + controller: _adPlacementIntervalControllers[role], + ), + ], + ), + ), ], ); } + /// Retrieves the ad frequency for a specific role from the configuration. int _getAdFrequency(FeedAdConfiguration config, AppUserRole role) { - switch (role) { - case AppUserRole.guestUser: - return config.frequencyConfig.guestAdFrequency; - case AppUserRole.standardUser: - return config.frequencyConfig.authenticatedAdFrequency; - case AppUserRole.premiumUser: - return config.frequencyConfig.premiumAdFrequency; - } + return config.visibleTo[role]?.adFrequency ?? 0; } + /// Retrieves the ad placement interval for a specific role from the configuration. int _getAdPlacementInterval(FeedAdConfiguration config, AppUserRole role) { - switch (role) { - case AppUserRole.guestUser: - return config.frequencyConfig.guestAdPlacementInterval; - case AppUserRole.standardUser: - return config.frequencyConfig.authenticatedAdPlacementInterval; - case AppUserRole.premiumUser: - return config.frequencyConfig.premiumAdPlacementInterval; - } - } - - FeedAdConfiguration _updateAdFrequency( - FeedAdConfiguration config, - int value, - AppUserRole role, - ) { - switch (role) { - case AppUserRole.guestUser: - return config.copyWith( - frequencyConfig: config.frequencyConfig.copyWith( - guestAdFrequency: value, - ), - ); - case AppUserRole.standardUser: - return config.copyWith( - frequencyConfig: config.frequencyConfig.copyWith( - authenticatedAdFrequency: value, - ), - ); - case AppUserRole.premiumUser: - // Premium users should not see ads, so their frequency is always 0. - // The UI field is disabled, but this is a safeguard. - return config.copyWith( - frequencyConfig: config.frequencyConfig.copyWith( - premiumAdFrequency: 0, - ), - ); - } - } - - FeedAdConfiguration _updateAdPlacementInterval( - FeedAdConfiguration config, - int value, - AppUserRole role, - ) { - switch (role) { - case AppUserRole.guestUser: - return config.copyWith( - frequencyConfig: config.frequencyConfig.copyWith( - guestAdPlacementInterval: value, - ), - ); - case AppUserRole.standardUser: - return config.copyWith( - frequencyConfig: config.frequencyConfig.copyWith( - authenticatedAdPlacementInterval: value, - ), - ); - case AppUserRole.premiumUser: - // Premium users should not see ads, so their interval is always 0. - // The UI field is disabled, but this is a safeguard. - return config.copyWith( - frequencyConfig: config.frequencyConfig.copyWith( - premiumAdPlacementInterval: 0, - ), - ); - } + return config.visibleTo[role]?.adPlacementInterval ?? 0; } } diff --git a/lib/app_configuration/widgets/interstitial_ad_settings_form.dart b/lib/app_configuration/widgets/interstitial_ad_settings_form.dart index 24c57acc..a9f3bc2c 100644 --- a/lib/app_configuration/widgets/interstitial_ad_settings_form.dart +++ b/lib/app_configuration/widgets/interstitial_ad_settings_form.dart @@ -31,6 +31,9 @@ class InterstitialAdSettingsForm extends StatefulWidget { class _InterstitialAdSettingsFormState extends State with SingleTickerProviderStateMixin { late TabController _tabController; + + /// Controllers for transitions before showing interstitial ads, mapped by user role. + /// These are used to manage text input for each role's interstitial ad frequency. late final Map _transitionsBeforeShowingInterstitialAdsControllers; @@ -42,51 +45,12 @@ class _InterstitialAdSettingsFormState extends State vsync: this, ); _initializeControllers(); - _tabController.addListener(_onTabChanged); - } - - void _onTabChanged() { - if (_tabController.indexIsChanging) return; - - final selectedRole = AppUserRole.values[_tabController.index]; - if (selectedRole == AppUserRole.premiumUser) { - final adConfig = widget.remoteConfig.adConfig; - final interstitialAdConfig = adConfig.interstitialAdConfiguration; - - // If the value for premium is not 0, update the config. - // This enforces the business rule that premium users do not see ads. - if (interstitialAdConfig - .feedInterstitialAdFrequencyConfig - .premiumUserTransitionsBeforeShowingInterstitialAds != - 0) { - final updatedFrequencyConfig = interstitialAdConfig - .feedInterstitialAdFrequencyConfig - .copyWith( - premiumUserTransitionsBeforeShowingInterstitialAds: 0, - ); - final updatedInterstitialAdConfig = interstitialAdConfig.copyWith( - feedInterstitialAdFrequencyConfig: updatedFrequencyConfig, - ); - widget.onConfigChanged( - widget.remoteConfig.copyWith( - adConfig: adConfig.copyWith( - interstitialAdConfiguration: updatedInterstitialAdConfig, - ), - ), - ); - } - } - } - - @override - void didUpdateWidget(covariant InterstitialAdSettingsForm oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.remoteConfig.adConfig.interstitialAdConfiguration != - oldWidget.remoteConfig.adConfig.interstitialAdConfiguration) { - _updateControllers(); - } + // Removed _tabController.addListener(_onTabChanged); as automatic disabling + // for premium users is no longer required. } + /// Initializes text editing controllers for each user role based on current + /// remote config values. void _initializeControllers() { final interstitialConfig = widget.remoteConfig.adConfig.interstitialAdConfiguration; @@ -108,6 +72,8 @@ class _InterstitialAdSettingsFormState extends State }; } + /// Updates text editing controllers when the widget's remote config changes. + /// This ensures the form fields reflect the latest configuration. void _updateControllers() { final interstitialConfig = widget.remoteConfig.adConfig.interstitialAdConfiguration; @@ -128,11 +94,18 @@ class _InterstitialAdSettingsFormState extends State } } + @override + void didUpdateWidget(covariant InterstitialAdSettingsForm oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.remoteConfig.adConfig.interstitialAdConfiguration != + oldWidget.remoteConfig.adConfig.interstitialAdConfiguration) { + _updateControllers(); + } + } + @override void dispose() { - _tabController - ..removeListener(_onTabChanged) - ..dispose(); + _tabController.dispose(); for (final controller in _transitionsBeforeShowingInterstitialAdsControllers.values) { controller.dispose(); @@ -220,90 +193,91 @@ class _InterstitialAdSettingsFormState extends State ); } + /// Builds role-specific configuration fields for interstitial ad frequency. + /// + /// This widget displays an input field for `transitionsBeforeShowingInterstitialAds` + /// for a given [AppUserRole]. Widget _buildInterstitialRoleSpecificFields( BuildContext context, AppLocalizations l10n, AppUserRole role, InterstitialAdConfiguration config, ) { - // Premium users do not see ads, so their settings are disabled. - final isEnabled = role != AppUserRole.premiumUser; + final roleConfig = config.visibleTo[role]; + // Removed isEnabled check as premium users can now be manually configured. return Column( children: [ - AppConfigIntField( - label: l10n.transitionsBeforeInterstitialAdsLabel, - description: l10n.transitionsBeforeInterstitialAdsDescription, - value: _getTransitionsBeforeInterstitial(config, role), + SwitchListTile( + title: Text(l10n.visibleToRoleLabel(role.l10n(context))), + value: roleConfig != null, onChanged: (value) { + final newVisibleTo = + Map.from( + config.visibleTo, + ); + if (value) { + // Default value when enabling for a role + newVisibleTo[role] = const InterstitialAdFrequencyConfig( + transitionsBeforeShowingInterstitialAds: 5, + ); + } else { + newVisibleTo.remove(role); + } widget.onConfigChanged( widget.remoteConfig.copyWith( adConfig: widget.remoteConfig.adConfig.copyWith( - interstitialAdConfiguration: - _updateTransitionsBeforeInterstitial( - config, - value, - role, - ), + interstitialAdConfiguration: config.copyWith( + visibleTo: newVisibleTo, + ), ), ), ); }, - controller: _transitionsBeforeShowingInterstitialAdsControllers[role], - enabled: isEnabled, ), + if (roleConfig != null) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.lg, + vertical: AppSpacing.sm, + ), + child: AppConfigIntField( + label: l10n.transitionsBeforeInterstitialAdsLabel, + description: l10n.transitionsBeforeInterstitialAdsDescription, + value: roleConfig.transitionsBeforeShowingInterstitialAds, + onChanged: (value) { + final newRoleConfig = roleConfig.copyWith( + transitionsBeforeShowingInterstitialAds: value, + ); + final newVisibleTo = + Map.from( + config.visibleTo, + )..[role] = newRoleConfig; + widget.onConfigChanged( + widget.remoteConfig.copyWith( + adConfig: widget.remoteConfig.adConfig.copyWith( + interstitialAdConfiguration: config.copyWith( + visibleTo: newVisibleTo, + ), + ), + ), + ); + }, + controller: + _transitionsBeforeShowingInterstitialAdsControllers[role], + // Removed enabled: isEnabled + ), + ), ], ); } + /// Retrieves the number of transitions before showing an interstitial ad + /// for a specific role from the configuration. int _getTransitionsBeforeInterstitial( InterstitialAdConfiguration config, AppUserRole role, ) { - switch (role) { - case AppUserRole.guestUser: - return config - .feedInterstitialAdFrequencyConfig - .guestTransitionsBeforeShowingInterstitialAds; - case AppUserRole.standardUser: - return config - .feedInterstitialAdFrequencyConfig - .standardUserTransitionsBeforeShowingInterstitialAds; - case AppUserRole.premiumUser: - return config - .feedInterstitialAdFrequencyConfig - .premiumUserTransitionsBeforeShowingInterstitialAds; - } - } - - InterstitialAdConfiguration _updateTransitionsBeforeInterstitial( - InterstitialAdConfiguration config, - int value, - AppUserRole role, - ) { - final currentFrequencyConfig = config.feedInterstitialAdFrequencyConfig; - - InterstitialAdFrequencyConfig newFrequencyConfig; - - switch (role) { - case AppUserRole.guestUser: - newFrequencyConfig = currentFrequencyConfig.copyWith( - guestTransitionsBeforeShowingInterstitialAds: value, - ); - case AppUserRole.standardUser: - newFrequencyConfig = currentFrequencyConfig.copyWith( - standardUserTransitionsBeforeShowingInterstitialAds: value, - ); - case AppUserRole.premiumUser: - // Premium users should not see ads, so their frequency is always 0. - // The UI field is disabled, but this is a safeguard. - newFrequencyConfig = currentFrequencyConfig.copyWith( - premiumUserTransitionsBeforeShowingInterstitialAds: 0, - ); - } - - return config.copyWith( - feedInterstitialAdFrequencyConfig: newFrequencyConfig, - ); + return config.visibleTo[role]?.transitionsBeforeShowingInterstitialAds ?? 0; } } diff --git a/lib/content_management/view/create_headline_page.dart b/lib/content_management/view/create_headline_page.dart index abf2bbb0..eaf21cfa 100644 --- a/lib/content_management/view/create_headline_page.dart +++ b/lib/content_management/view/create_headline_page.dart @@ -122,7 +122,7 @@ class _CreateHeadlineViewState extends State<_createheadlineview> { ); } } - : null, // Disable button if form is not valid + : null, ); }, ), diff --git a/lib/content_management/view/create_source_page.dart b/lib/content_management/view/create_source_page.dart index 31c71279..c6626426 100644 --- a/lib/content_management/view/create_source_page.dart +++ b/lib/content_management/view/create_source_page.dart @@ -119,7 +119,7 @@ class _CreateSourceViewState extends State<_createsourceview> { ); } } - : null, // Disable button if form is not valid + : null, ); }, ), diff --git a/lib/content_management/view/create_topic_page.dart b/lib/content_management/view/create_topic_page.dart index 6b73f013..0d4faf8b 100644 --- a/lib/content_management/view/create_topic_page.dart +++ b/lib/content_management/view/create_topic_page.dart @@ -119,7 +119,7 @@ class _CreateTopicViewState extends State<_createtopicview> { ); } } - : null, // Disable button if form is not valid + : null, ); }, ), diff --git a/lib/content_management/view/edit_headline_page.dart b/lib/content_management/view/edit_headline_page.dart index 150b7689..9812452c 100644 --- a/lib/content_management/view/edit_headline_page.dart +++ b/lib/content_management/view/edit_headline_page.dart @@ -112,7 +112,7 @@ class _EditHeadlineViewState extends State<_editheadlineview> { } } } - : null, // Disable button if form is not valid + : null, ); }, ), diff --git a/lib/content_management/view/edit_source_page.dart b/lib/content_management/view/edit_source_page.dart index 55a026b2..39454e8c 100644 --- a/lib/content_management/view/edit_source_page.dart +++ b/lib/content_management/view/edit_source_page.dart @@ -106,7 +106,7 @@ class _EditSourceViewState extends State<_editsourceview> { } } } - : null, // Disable button if form is not valid + : null, ); }, ), diff --git a/lib/content_management/view/edit_topic_page.dart b/lib/content_management/view/edit_topic_page.dart index 77f82f30..26b71a1e 100644 --- a/lib/content_management/view/edit_topic_page.dart +++ b/lib/content_management/view/edit_topic_page.dart @@ -106,7 +106,7 @@ class _EditTopicViewState extends State<_edittopicview> { } } } - : null, // Disable button if form is not valid + : null, ); }, ), diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e4db452b..84a348ba 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2587,6 +2587,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Publish Source'** String get publishSource; + + /// Label for a checkbox to enable/disable in-article ads for a specific user role. + /// + /// In en, this message translates to: + /// **'Enable In-Article Ads for {role}'** + String enableInArticleAdsForRoleLabel(String role); } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index a3ec1304..b49f7465 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -1382,4 +1382,9 @@ class AppLocalizationsAr extends AppLocalizations { @override String get publishSource => 'نشر المصدر'; + + @override + String enableInArticleAdsForRoleLabel(String role) { + return 'تمكين الإعلانات داخل المقال لـ $role'; + } } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 9ea4285d..5d6cfeea 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1387,4 +1387,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get publishSource => 'Publish Source'; + + @override + String enableInArticleAdsForRoleLabel(String role) { + return 'Enable In-Article Ads for $role'; + } } diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index d3d35ae5..ec5d8ae0 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -1745,5 +1745,15 @@ "publishSource": "نشر المصدر", "@publishSource": { "description": "تلميح زر نشر المصدر." + }, + "enableInArticleAdsForRoleLabel": "تمكين الإعلانات داخل المقال لـ {role}", + "@enableInArticleAdsForRoleLabel": { + "description": "Label for a checkbox to enable/disable in-article ads for a specific user role.", + "placeholders": { + "role": { + "type": "String", + "example": "مستخدم ضيف" + } + } } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index f775b62b..aed3f985 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1741,5 +1741,15 @@ "publishSource": "Publish Source", "@publishSource": { "description": "Tooltip for the publish source button." + }, + "enableInArticleAdsForRoleLabel": "Enable In-Article Ads for {role}", + "@enableInArticleAdsForRoleLabel": { + "description": "Label for a checkbox to enable/disable in-article ads for a specific user role.", + "placeholders": { + "role": { + "type": "String", + "example": "Guest User" + } + } } } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 3987dfff..297df396 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -90,7 +90,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: b6a3fb4ad862901a56b424fd92a9702f30513404 + resolved-ref: d7d9afa3e0cf8fb211fdea3342db8831813abf7d url: "https://github.com/flutter-news-app-full-source-code/core.git" source: git version: "0.0.0"

AltStyle によって変換されたページ (->オリジナル) /