From 965c0efe90ca27907784a25caf73fe4c37def510 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 13:22:21 +0100 Subject: [PATCH 01/13] chore(deps): update core.git to d7d9afa3 - Update resolved-ref in pubspec.lock from b6a3fb4 to d7d9afa3 --- pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From eabfc06606acf3f423bfb8579fb3dd29c5eba102 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 13:22:33 +0100 Subject: [PATCH 02/13] refactor(ad_config_form): simplify AdConfigForm widget - Remove TabBar and role-based conditional rendering - Update widget documentation - Simplify state management by removing controllers and related methods --- .../widgets/ad_config_form.dart | 130 +----------------- 1 file changed, 3 insertions(+), 127 deletions(-) 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; - } - } } From 904c0bb492913cb34e6a245683f9718f218b26cd Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 13:22:44 +0100 Subject: [PATCH 03/13] feat(app_configuration): implement role-based in-article ad slot visibility - Add TabBar for selecting user roles - Implement role-specific configuration fields for in-article ad slots - Use CheckboxListTile to enable/disable ad slots per role - Update state management with TabController - Refactor UI layout for better readability --- .../widgets/article_ad_settings_form.dart | 118 ++++++++++++++---- 1 file changed, 96 insertions(+), 22 deletions(-) diff --git a/lib/app_configuration/widgets/article_ad_settings_form.dart b/lib/app_configuration/widgets/article_ad_settings_form.dart index 828f6be9..a6b1794a 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,56 @@ 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]; + final isEnabled = role != AppUserRole.premiumUser; + + return Column( + children: [ + // Checkbox for each InArticleAdSlotType + for (final slotType in InArticleAdSlotType.values) + CheckboxListTile( + title: Text(slotType.l10n(context)), + value: (roleSlots != null && roleSlots[slotType] == true) && + isEnabled, + onChanged: isEnabled + ? (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, + ), + ), + ), + ); + } + : null, + ), + ], + ); + } } From f9f9cd993794779c7a77474b9574c85ebad68130 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 13:22:56 +0100 Subject: [PATCH 04/13] refactor(app_configuration): update feed ad settings form logic - Replace separate frequency and placement interval fields with a single enabled toggle for each role - Restructure ad configuration model to use a map for role-specific settings - Update controller initialization and value updating logic to reflect new model - Modify tab change listener to enforce business rules for premium users - Update ad frequency and placement interval retrieval methods - Remove unnecessary switch statements and simplify configuration updates --- .../widgets/feed_ad_settings_form.dart | 343 +++++++++--------- 1 file changed, 169 insertions(+), 174 deletions(-) diff --git a/lib/app_configuration/widgets/feed_ad_settings_form.dart b/lib/app_configuration/widgets/feed_ad_settings_form.dart index 211dd5d9..d60ab029 100644 --- a/lib/app_configuration/widgets/feed_ad_settings_form.dart +++ b/lib/app_configuration/widgets/feed_ad_settings_form.dart @@ -31,9 +31,15 @@ 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; + _adPlacementIntervalControllers; @override void initState() { @@ -46,72 +52,31 @@ class _FeedAdSettingsFormState extends State _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(); - } - } - + /// Initializes text editing controllers for each user role based on current + /// remote config values. void _initializeControllers() { final feedAdConfig = widget.remoteConfig.adConfig.feedAdConfiguration; _adFrequencyControllers = { for (final role in AppUserRole.values) - role: - TextEditingController( - text: _getAdFrequency(feedAdConfig, role).toString(), - ) - ..selection = TextSelection.collapsed( - offset: _getAdFrequency(feedAdConfig, role).toString().length, - ), + role: TextEditingController( + text: _getAdFrequency(feedAdConfig, role).toString(), + )..selection = TextSelection.collapsed( + offset: _getAdFrequency(feedAdConfig, role).toString().length, + ), }; _adPlacementIntervalControllers = { for (final role in AppUserRole.values) - role: - TextEditingController( - text: _getAdPlacementInterval(feedAdConfig, role).toString(), - ) - ..selection = TextSelection.collapsed( - offset: _getAdPlacementInterval( - feedAdConfig, - role, - ).toString().length, - ), + role: TextEditingController( + text: _getAdPlacementInterval(feedAdConfig, role).toString(), + )..selection = TextSelection.collapsed( + offset: + _getAdPlacementInterval(feedAdConfig, role).toString().length, + ), }; } + /// 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) { @@ -123,21 +88,65 @@ class _FeedAdSettingsFormState extends State ); } - final newPlacementIntervalValue = _getAdPlacementInterval( - feedAdConfig, - role, - ).toString(); + final newPlacementIntervalValue = + _getAdPlacementInterval(feedAdConfig, role).toString(); if (_adPlacementIntervalControllers[role]?.text != newPlacementIntervalValue) { _adPlacementIntervalControllers[role]?.text = newPlacementIntervalValue; _adPlacementIntervalControllers[role]?.selection = TextSelection.collapsed( - offset: newPlacementIntervalValue.length, - ); + offset: newPlacementIntervalValue.length, + ); + } + } + } + + /// Listener for tab changes to enforce business rules, specifically for + /// premium users who should not see ads. + 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. + final premiumRoleConfig = feedAdConfig.visibleTo[AppUserRole.premiumUser]; + if (premiumRoleConfig != null && + (premiumRoleConfig.adFrequency != 0 || + premiumRoleConfig.adPlacementInterval != 0)) { + final updatedVisibleTo = + Map.from(feedAdConfig.visibleTo) + ..[AppUserRole.premiumUser] = const FeedAdFrequencyConfig( + adFrequency: 0, + adPlacementInterval: 0, + ); + + final updatedFeedAdConfig = + feedAdConfig.copyWith(visibleTo: updatedVisibleTo); + + 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(); + } + } + @override void dispose() { _tabController @@ -245,7 +254,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,7 +269,6 @@ class _FeedAdSettingsFormState extends State ), ), const SizedBox(height: AppSpacing.lg), - // TabBarView to display role-specific fields SizedBox( height: 250, child: TabBarView( @@ -284,134 +291,122 @@ 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]. Premium users have these fields disabled + /// as they should not see ads. Widget _buildRoleSpecificFields( BuildContext context, AppLocalizations l10n, AppUserRole role, FeedAdConfiguration config, ) { - // Premium users do not see ads, so their settings are disabled. + final roleConfig = config.visibleTo[role]; final isEnabled = role != AppUserRole.premiumUser; return Column( children: [ - AppConfigIntField( - label: l10n.adFrequencyLabel, - description: l10n.adFrequencyDescription, - value: _getAdFrequency(config, role), - onChanged: (value) { - widget.onConfigChanged( - widget.remoteConfig.copyWith( - adConfig: widget.remoteConfig.adConfig.copyWith( - feedAdConfiguration: _updateAdFrequency(config, value, role), - ), - ), - ); - }, - controller: _adFrequencyControllers[role], - enabled: isEnabled, + CheckboxListTile( + title: Text(l10n.visibleToRoleLabel(role.l10n(context))), + value: roleConfig != null && isEnabled, + onChanged: isEnabled + ? (value) { + final newVisibleTo = + Map.from( + config.visibleTo, + ); + if (value ?? false) { + // 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: config.copyWith( + visibleTo: newVisibleTo, + ), + ), + ), + ); + } + : null, ), - AppConfigIntField( - label: l10n.adPlacementIntervalLabel, - description: l10n.adPlacementIntervalDescription, - value: _getAdPlacementInterval(config, role), - onChanged: (value) { - widget.onConfigChanged( - widget.remoteConfig.copyWith( - adConfig: widget.remoteConfig.adConfig.copyWith( - feedAdConfiguration: _updateAdPlacementInterval( - config, - value, - role, - ), + 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], + enabled: isEnabled, ), - ), - ); - }, - controller: _adPlacementIntervalControllers[role], - enabled: isEnabled, - ), + 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], + enabled: isEnabled, + ), + ], + ), + ), ], ); } + /// 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; } } From f0893b0625c7b0a93a7ebaf8da06db5b8aaa77a5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 13:23:06 +0100 Subject: [PATCH 05/13] refactor(ad): improve interstitial ad config management - Replace single config object with map for better role-based frequency control - Add checkbox to toggle ad visibility for each user role - Update controllers initialization and updates logic - Simplify transitions retrieval and update methods - Enhance comments and code structure for better readability --- .../interstitial_ad_settings_form.dart | 248 +++++++++--------- 1 file changed, 129 insertions(+), 119 deletions(-) diff --git a/lib/app_configuration/widgets/interstitial_ad_settings_form.dart b/lib/app_configuration/widgets/interstitial_ad_settings_form.dart index 24c57acc..2c784f52 100644 --- a/lib/app_configuration/widgets/interstitial_ad_settings_form.dart +++ b/lib/app_configuration/widgets/interstitial_ad_settings_form.dart @@ -31,8 +31,11 @@ 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; + _transitionsBeforeShowingInterstitialAdsControllers; @override void initState() { @@ -45,6 +48,51 @@ class _InterstitialAdSettingsFormState extends State _tabController.addListener(_onTabChanged); } + /// Initializes text editing controllers for each user role based on current + /// remote config values. + void _initializeControllers() { + final interstitialConfig = + widget.remoteConfig.adConfig.interstitialAdConfiguration; + _transitionsBeforeShowingInterstitialAdsControllers = { + for (final role in AppUserRole.values) + role: TextEditingController( + text: _getTransitionsBeforeInterstitial( + interstitialConfig, + role, + ).toString(), + )..selection = TextSelection.collapsed( + offset: _getTransitionsBeforeInterstitial( + interstitialConfig, + role, + ).toString().length, + ), + }; + } + + /// 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; + for (final role in AppUserRole.values) { + final newInterstitialValue = _getTransitionsBeforeInterstitial( + interstitialConfig, + role, + ).toString(); + if (_transitionsBeforeShowingInterstitialAdsControllers[role]?.text != + newInterstitialValue) { + _transitionsBeforeShowingInterstitialAdsControllers[role]?.text = + newInterstitialValue; + _transitionsBeforeShowingInterstitialAdsControllers[role]?.selection = + TextSelection.collapsed( + offset: newInterstitialValue.length, + ); + } + } + } + + /// Listener for tab changes to enforce business rules, specifically for + /// premium users who should not see ads. void _onTabChanged() { if (_tabController.indexIsChanging) return; @@ -55,18 +103,19 @@ class _InterstitialAdSettingsFormState extends State // 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, - ); + final premiumRoleConfig = + interstitialAdConfig.visibleTo[AppUserRole.premiumUser]; + if (premiumRoleConfig != null && + premiumRoleConfig.transitionsBeforeShowingInterstitialAds != 0) { + final updatedVisibleTo = Map.from( + interstitialAdConfig.visibleTo, + )..[AppUserRole.premiumUser] = const InterstitialAdFrequencyConfig( + transitionsBeforeShowingInterstitialAds: 0, + ); + + final updatedInterstitialAdConfig = + interstitialAdConfig.copyWith(visibleTo: updatedVisibleTo); + widget.onConfigChanged( widget.remoteConfig.copyWith( adConfig: adConfig.copyWith( @@ -87,47 +136,6 @@ class _InterstitialAdSettingsFormState extends State } } - void _initializeControllers() { - final interstitialConfig = - widget.remoteConfig.adConfig.interstitialAdConfiguration; - _transitionsBeforeShowingInterstitialAdsControllers = { - for (final role in AppUserRole.values) - role: - TextEditingController( - text: _getTransitionsBeforeInterstitial( - interstitialConfig, - role, - ).toString(), - ) - ..selection = TextSelection.collapsed( - offset: _getTransitionsBeforeInterstitial( - interstitialConfig, - role, - ).toString().length, - ), - }; - } - - void _updateControllers() { - final interstitialConfig = - widget.remoteConfig.adConfig.interstitialAdConfiguration; - for (final role in AppUserRole.values) { - final newInterstitialValue = _getTransitionsBeforeInterstitial( - interstitialConfig, - role, - ).toString(); - if (_transitionsBeforeShowingInterstitialAdsControllers[role]?.text != - newInterstitialValue) { - _transitionsBeforeShowingInterstitialAdsControllers[role]?.text = - newInterstitialValue; - _transitionsBeforeShowingInterstitialAdsControllers[role]?.selection = - TextSelection.collapsed( - offset: newInterstitialValue.length, - ); - } - } - } - @override void dispose() { _tabController @@ -220,90 +228,92 @@ 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]. Premium users have this field disabled + /// as they should not see ads. Widget _buildInterstitialRoleSpecificFields( BuildContext context, AppLocalizations l10n, AppUserRole role, InterstitialAdConfiguration config, ) { - // Premium users do not see ads, so their settings are disabled. + final roleConfig = config.visibleTo[role]; final isEnabled = role != AppUserRole.premiumUser; return Column( children: [ - AppConfigIntField( - label: l10n.transitionsBeforeInterstitialAdsLabel, - description: l10n.transitionsBeforeInterstitialAdsDescription, - value: _getTransitionsBeforeInterstitial(config, role), - onChanged: (value) { - widget.onConfigChanged( - widget.remoteConfig.copyWith( - adConfig: widget.remoteConfig.adConfig.copyWith( - interstitialAdConfiguration: - _updateTransitionsBeforeInterstitial( - config, - value, - role, + CheckboxListTile( + title: Text(l10n.visibleToRoleLabel(role.l10n(context))), + value: roleConfig != null && isEnabled, + onChanged: isEnabled + ? (value) { + final newVisibleTo = + Map.from( + config.visibleTo, + ); + if (value ?? false) { + // 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: config.copyWith( + visibleTo: newVisibleTo, + ), ), - ), - ), - ); - }, - controller: _transitionsBeforeShowingInterstitialAdsControllers[role], - enabled: isEnabled, + ), + ); + } + : null, ), + 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], + 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; } } From 0ecbf7359a4f5c9ccd7cca6bc2954d75bbd56fa7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 16:22:44 +0100 Subject: [PATCH 06/13] feat(localization): add translations for in-article ads settings - Add Arabic and English translations for new checkbox label related to in-article ads - Introduce placeholder for user role in both Arabic and English versions --- lib/l10n/app_localizations.dart | 6 ++++++ lib/l10n/app_localizations_ar.dart | 5 +++++ lib/l10n/app_localizations_en.dart | 5 +++++ lib/l10n/arb/app_ar.arb | 10 ++++++++++ lib/l10n/arb/app_en.arb | 10 ++++++++++ 5 files changed, 36 insertions(+) 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 From 1f8048be59f608ead35a10f2eeb88f8a7910fa10 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 16:22:53 +0100 Subject: [PATCH 07/13] refactor(app_configuration): improve in-article ad settings UI consistency - Replace CheckboxListTile with SwitchListTile for better alignment with existing design - Restructure the widget layout to improve usability - Enhance the logic for enabling/disabling in-article ads for different user roles --- .../widgets/article_ad_settings_form.dart | 96 +++++++++++++------ 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/lib/app_configuration/widgets/article_ad_settings_form.dart b/lib/app_configuration/widgets/article_ad_settings_form.dart index a6b1794a..ab1e2e84 100644 --- a/lib/app_configuration/widgets/article_ad_settings_form.dart +++ b/lib/app_configuration/widgets/article_ad_settings_form.dart @@ -202,38 +202,78 @@ class _ArticleAdSettingsFormState extends State return Column( children: [ - // Checkbox for each InArticleAdSlotType - for (final slotType in InArticleAdSlotType.values) - CheckboxListTile( - title: Text(slotType.l10n(context)), - value: (roleSlots != null && roleSlots[slotType] == true) && - isEnabled, - onChanged: isEnabled - ? (value) { - final newRoleSlots = - Map.from(roleSlots ?? {}); - if (value ?? false) { - newRoleSlots[slotType] = true; - } else { - newRoleSlots.remove(slotType); - } + SwitchListTile( + // Changed from CheckboxListTile to SwitchListTile for consistency + title: Text(l10n.enableInArticleAdsForRoleLabel(role.l10n(context))), + value: roleSlots != null && isEnabled, + onChanged: isEnabled + ? (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, + ), + ), + ), + ); + } + : null, + ), + 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) + SwitchListTile( + title: Text(slotType.l10n(context)), + value: roleSlots[slotType] == true, + onChanged: (value) { + final newRoleSlots = + Map.from(roleSlots); + if (value) { + newRoleSlots[slotType] = true; + } else { + newRoleSlots.remove(slotType); + } - final newVisibleTo = - Map>.from( - config.visibleTo, - )..[role] = newRoleSlots; + final newVisibleTo = + Map>.from( + config.visibleTo, + )..[role] = newRoleSlots; - widget.onConfigChanged( - widget.remoteConfig.copyWith( - adConfig: widget.remoteConfig.adConfig.copyWith( - articleAdConfiguration: config.copyWith( - visibleTo: newVisibleTo, + widget.onConfigChanged( + widget.remoteConfig.copyWith( + adConfig: widget.remoteConfig.adConfig.copyWith( + articleAdConfiguration: config.copyWith( + visibleTo: newVisibleTo, + ), ), ), - ), - ); - } - : null, + ); + }, + ), + ], + ), ), ], ); From ffc0c65279bd94c776b8f740c7550045bbb2cf5d Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 16:23:05 +0100 Subject: [PATCH 08/13] refactor(app_configuration): improve ad settings UI and functionality - Increase TabBarView height for better spacing - Replace CheckboxListTile with SwitchListTile for consistency - Update role visibility toggle label and logic --- lib/app_configuration/widgets/feed_ad_settings_form.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/app_configuration/widgets/feed_ad_settings_form.dart b/lib/app_configuration/widgets/feed_ad_settings_form.dart index d60ab029..dbbe99cb 100644 --- a/lib/app_configuration/widgets/feed_ad_settings_form.dart +++ b/lib/app_configuration/widgets/feed_ad_settings_form.dart @@ -270,7 +270,7 @@ class _FeedAdSettingsFormState extends State ), const SizedBox(height: AppSpacing.lg), SizedBox( - height: 250, + height: 350, // Increased height for better spacing child: TabBarView( controller: _tabController, children: AppUserRole.values @@ -307,8 +307,9 @@ class _FeedAdSettingsFormState extends State return Column( children: [ - CheckboxListTile( - title: Text(l10n.visibleToRoleLabel(role.l10n(context))), + SwitchListTile( + // Changed from CheckboxListTile to SwitchListTile for consistency + title: Text(l10n.enableInArticleAdsForRoleLabel(role.l10n(context))), value: roleConfig != null && isEnabled, onChanged: isEnabled ? (value) { @@ -316,7 +317,7 @@ class _FeedAdSettingsFormState extends State Map.from( config.visibleTo, ); - if (value ?? false) { + if (value) { // Default values when enabling for a role newVisibleTo[role] = const FeedAdFrequencyConfig( adFrequency: 5, From 7413d2591ef6575ef8f4a0b27c372b23cfe16485 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 16:23:16 +0100 Subject: [PATCH 09/13] refactor(ad): improve UI consistency and code formatting in interstitial ad settings - Replace CheckboxListTile with SwitchListTile for better consistency with other screens - Update checkbox labels to follow a more consistent pattern - Adjust code formatting and indentation for improved readability --- .../interstitial_ad_settings_form.dart | 70 +++++++++++-------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/lib/app_configuration/widgets/interstitial_ad_settings_form.dart b/lib/app_configuration/widgets/interstitial_ad_settings_form.dart index 2c784f52..ecfcc0d9 100644 --- a/lib/app_configuration/widgets/interstitial_ad_settings_form.dart +++ b/lib/app_configuration/widgets/interstitial_ad_settings_form.dart @@ -35,7 +35,7 @@ class _InterstitialAdSettingsFormState extends State /// 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; + _transitionsBeforeShowingInterstitialAdsControllers; @override void initState() { @@ -55,17 +55,19 @@ class _InterstitialAdSettingsFormState extends State widget.remoteConfig.adConfig.interstitialAdConfiguration; _transitionsBeforeShowingInterstitialAdsControllers = { for (final role in AppUserRole.values) - role: TextEditingController( - text: _getTransitionsBeforeInterstitial( - interstitialConfig, - role, - ).toString(), - )..selection = TextSelection.collapsed( - offset: _getTransitionsBeforeInterstitial( - interstitialConfig, - role, - ).toString().length, - ), + role: + TextEditingController( + text: _getTransitionsBeforeInterstitial( + interstitialConfig, + role, + ).toString(), + ) + ..selection = TextSelection.collapsed( + offset: _getTransitionsBeforeInterstitial( + interstitialConfig, + role, + ).toString().length, + ), }; } @@ -85,8 +87,8 @@ class _InterstitialAdSettingsFormState extends State newInterstitialValue; _transitionsBeforeShowingInterstitialAdsControllers[role]?.selection = TextSelection.collapsed( - offset: newInterstitialValue.length, - ); + offset: newInterstitialValue.length, + ); } } } @@ -107,14 +109,17 @@ class _InterstitialAdSettingsFormState extends State interstitialAdConfig.visibleTo[AppUserRole.premiumUser]; if (premiumRoleConfig != null && premiumRoleConfig.transitionsBeforeShowingInterstitialAds != 0) { - final updatedVisibleTo = Map.from( - interstitialAdConfig.visibleTo, - )..[AppUserRole.premiumUser] = const InterstitialAdFrequencyConfig( - transitionsBeforeShowingInterstitialAds: 0, - ); + final updatedVisibleTo = + Map.from( + interstitialAdConfig.visibleTo, + ) + ..[AppUserRole.premiumUser] = const InterstitialAdFrequencyConfig( + transitionsBeforeShowingInterstitialAds: 0, + ); - final updatedInterstitialAdConfig = - interstitialAdConfig.copyWith(visibleTo: updatedVisibleTo); + final updatedInterstitialAdConfig = interstitialAdConfig.copyWith( + visibleTo: updatedVisibleTo, + ); widget.onConfigChanged( widget.remoteConfig.copyWith( @@ -244,16 +249,17 @@ class _InterstitialAdSettingsFormState extends State return Column( children: [ - CheckboxListTile( - title: Text(l10n.visibleToRoleLabel(role.l10n(context))), + SwitchListTile( + // Changed from CheckboxListTile to SwitchListTile for consistency + title: Text(l10n.enableInArticleAdsForRoleLabel(role.l10n(context))), value: roleConfig != null && isEnabled, onChanged: isEnabled ? (value) { final newVisibleTo = Map.from( - config.visibleTo, - ); - if (value ?? false) { + config.visibleTo, + ); + if (value) { // Default value when enabling for a role newVisibleTo[role] = const InterstitialAdFrequencyConfig( transitionsBeforeShowingInterstitialAds: 5, @@ -284,12 +290,13 @@ class _InterstitialAdSettingsFormState extends State description: l10n.transitionsBeforeInterstitialAdsDescription, value: roleConfig.transitionsBeforeShowingInterstitialAds, onChanged: (value) { - final newRoleConfig = - roleConfig.copyWith(transitionsBeforeShowingInterstitialAds: value); + final newRoleConfig = roleConfig.copyWith( + transitionsBeforeShowingInterstitialAds: value, + ); final newVisibleTo = Map.from( - config.visibleTo, - )..[role] = newRoleConfig; + config.visibleTo, + )..[role] = newRoleConfig; widget.onConfigChanged( widget.remoteConfig.copyWith( adConfig: widget.remoteConfig.adConfig.copyWith( @@ -300,7 +307,8 @@ class _InterstitialAdSettingsFormState extends State ), ); }, - controller: _transitionsBeforeShowingInterstitialAdsControllers[role], + controller: + _transitionsBeforeShowingInterstitialAdsControllers[role], enabled: isEnabled, ), ), From d32724bc4da6083cf0012d62de22ef1262701b7c Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 16:39:41 +0100 Subject: [PATCH 10/13] refactor(ad): revert in-article ad settings UI changes - Revert SwitchListTile back to CheckboxListTile for in-article ad slot types - Remove premiuim user role special handling - Simplify role slot toggle logic --- .../widgets/article_ad_settings_form.dart | 69 +++++++++---------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/lib/app_configuration/widgets/article_ad_settings_form.dart b/lib/app_configuration/widgets/article_ad_settings_form.dart index ab1e2e84..30af7397 100644 --- a/lib/app_configuration/widgets/article_ad_settings_form.dart +++ b/lib/app_configuration/widgets/article_ad_settings_form.dart @@ -198,41 +198,37 @@ class _ArticleAdSettingsFormState extends State ArticleAdConfiguration config, ) { final roleSlots = config.visibleTo[role]; - final isEnabled = role != AppUserRole.premiumUser; return Column( children: [ SwitchListTile( - // Changed from CheckboxListTile to SwitchListTile for consistency title: Text(l10n.enableInArticleAdsForRoleLabel(role.l10n(context))), - value: roleSlots != null && isEnabled, - onChanged: isEnabled - ? (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); - } + value: roleSlots != null, // Value is true if roleSlots exists + 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, - ), - ), - ), - ); - } - : null, + widget.onConfigChanged( + widget.remoteConfig.copyWith( + adConfig: widget.remoteConfig.adConfig.copyWith( + articleAdConfiguration: config.copyWith( + visibleTo: newVisibleTo, + ), + ), + ), + ); + }, ), if (roleSlots != null) Padding( @@ -244,13 +240,14 @@ class _ArticleAdSettingsFormState extends State children: [ // SwitchListTile for each InArticleAdSlotType for (final slotType in InArticleAdSlotType.values) - SwitchListTile( + CheckboxListTile( title: Text(slotType.l10n(context)), - value: roleSlots[slotType] == true, + value: roleSlots[slotType] ?? false, onChanged: (value) { - final newRoleSlots = - Map.from(roleSlots); - if (value) { + final newRoleSlots = Map.from( + roleSlots, + ); + if (value ?? false) { newRoleSlots[slotType] = true; } else { newRoleSlots.remove(slotType); @@ -258,8 +255,8 @@ class _ArticleAdSettingsFormState extends State final newVisibleTo = Map>.from( - config.visibleTo, - )..[role] = newRoleSlots; + config.visibleTo, + )..[role] = newRoleSlots; widget.onConfigChanged( widget.remoteConfig.copyWith( From 36aa7b6d9d2d0a79c7aa707f5b8600d0301f4072 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 16:39:50 +0100 Subject: [PATCH 11/13] feat(ad-settings): remove automatic ad disabling for premium users - Remove _tabController listener for enforcing premium user ad rules - Remove 'isEnabled' check in role-specific fields - Update widget descriptions and switch labels - Remove unnecessary comments --- .../widgets/feed_ad_settings_form.dart | 105 ++++++------------ 1 file changed, 31 insertions(+), 74 deletions(-) diff --git a/lib/app_configuration/widgets/feed_ad_settings_form.dart b/lib/app_configuration/widgets/feed_ad_settings_form.dart index dbbe99cb..21f11aaf 100644 --- a/lib/app_configuration/widgets/feed_ad_settings_form.dart +++ b/lib/app_configuration/widgets/feed_ad_settings_form.dart @@ -49,7 +49,8 @@ class _FeedAdSettingsFormState extends State vsync: this, ); _initializeControllers(); - _tabController.addListener(_onTabChanged); + // 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 @@ -101,42 +102,6 @@ class _FeedAdSettingsFormState extends State } } - /// Listener for tab changes to enforce business rules, specifically for - /// premium users who should not see ads. - 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. - final premiumRoleConfig = feedAdConfig.visibleTo[AppUserRole.premiumUser]; - if (premiumRoleConfig != null && - (premiumRoleConfig.adFrequency != 0 || - premiumRoleConfig.adPlacementInterval != 0)) { - final updatedVisibleTo = - Map.from(feedAdConfig.visibleTo) - ..[AppUserRole.premiumUser] = const FeedAdFrequencyConfig( - adFrequency: 0, - adPlacementInterval: 0, - ); - - final updatedFeedAdConfig = - feedAdConfig.copyWith(visibleTo: updatedVisibleTo); - - widget.onConfigChanged( - widget.remoteConfig.copyWith( - adConfig: adConfig.copyWith( - feedAdConfiguration: updatedFeedAdConfig, - ), - ), - ); - } - } - } @override void didUpdateWidget(covariant FeedAdSettingsForm oldWidget) { @@ -149,9 +114,7 @@ class _FeedAdSettingsFormState extends State @override void dispose() { - _tabController - ..removeListener(_onTabChanged) - ..dispose(); + _tabController.dispose(); for (final controller in _adFrequencyControllers.values) { controller.dispose(); } @@ -294,8 +257,7 @@ 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]. Premium users have these fields disabled - /// as they should not see ads. + /// for a given [AppUserRole]. Widget _buildRoleSpecificFields( BuildContext context, AppLocalizations l10n, @@ -303,40 +265,37 @@ class _FeedAdSettingsFormState extends State FeedAdConfiguration config, ) { final roleConfig = config.visibleTo[role]; - final isEnabled = role != AppUserRole.premiumUser; + // Removed isEnabled check as premium users can now be manually configured. return Column( children: [ SwitchListTile( - // Changed from CheckboxListTile to SwitchListTile for consistency - title: Text(l10n.enableInArticleAdsForRoleLabel(role.l10n(context))), - value: roleConfig != null && isEnabled, - onChanged: isEnabled - ? (value) { - final newVisibleTo = - Map.from( - config.visibleTo, - ); - 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: config.copyWith( - visibleTo: newVisibleTo, - ), - ), - ), - ); - } - : null, + title: Text(l10n.visibleToRoleLabel(role.l10n(context))), + value: roleConfig != null, // Value is true if roleConfig exists + onChanged: (value) { + final newVisibleTo = + Map.from( + config.visibleTo, + ); + 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: config.copyWith( + visibleTo: newVisibleTo, + ), + ), + ), + ); + }, ), if (roleConfig != null) Padding( @@ -368,7 +327,6 @@ class _FeedAdSettingsFormState extends State ); }, controller: _adFrequencyControllers[role], - enabled: isEnabled, ), AppConfigIntField( label: l10n.adPlacementIntervalLabel, @@ -392,7 +350,6 @@ class _FeedAdSettingsFormState extends State ); }, controller: _adPlacementIntervalControllers[role], - enabled: isEnabled, ), ], ), From b0514b11bdbb25262568aeccb0387ae1fae7324e Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 16:39:59 +0100 Subject: [PATCH 12/13] feat(ad-config): remove auto-disable ads for premium users - Remove _tabController.addListener(_onTabChanged) and related logic - Remove automatic setting of transitions to 0 for premium users - Remove isEnabled check from _buildInterstitialRoleSpecificFields - Update switch label to "Visible to [Role]" - Remove.enabled parameter from TextFormField --- .../interstitial_ad_settings_form.dart | 138 ++++++------------ 1 file changed, 47 insertions(+), 91 deletions(-) diff --git a/lib/app_configuration/widgets/interstitial_ad_settings_form.dart b/lib/app_configuration/widgets/interstitial_ad_settings_form.dart index ecfcc0d9..e20cc3b6 100644 --- a/lib/app_configuration/widgets/interstitial_ad_settings_form.dart +++ b/lib/app_configuration/widgets/interstitial_ad_settings_form.dart @@ -35,7 +35,7 @@ class _InterstitialAdSettingsFormState extends State /// 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; + _transitionsBeforeShowingInterstitialAdsControllers; @override void initState() { @@ -45,7 +45,8 @@ class _InterstitialAdSettingsFormState extends State vsync: this, ); _initializeControllers(); - _tabController.addListener(_onTabChanged); + // 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 @@ -55,19 +56,17 @@ class _InterstitialAdSettingsFormState extends State widget.remoteConfig.adConfig.interstitialAdConfiguration; _transitionsBeforeShowingInterstitialAdsControllers = { for (final role in AppUserRole.values) - role: - TextEditingController( - text: _getTransitionsBeforeInterstitial( - interstitialConfig, - role, - ).toString(), - ) - ..selection = TextSelection.collapsed( - offset: _getTransitionsBeforeInterstitial( - interstitialConfig, - role, - ).toString().length, - ), + role: TextEditingController( + text: _getTransitionsBeforeInterstitial( + interstitialConfig, + role, + ).toString(), + )..selection = TextSelection.collapsed( + offset: _getTransitionsBeforeInterstitial( + interstitialConfig, + role, + ).toString().length, + ), }; } @@ -87,51 +86,13 @@ class _InterstitialAdSettingsFormState extends State newInterstitialValue; _transitionsBeforeShowingInterstitialAdsControllers[role]?.selection = TextSelection.collapsed( - offset: newInterstitialValue.length, - ); - } - } - } - - /// Listener for tab changes to enforce business rules, specifically for - /// premium users who should not see ads. - 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. - final premiumRoleConfig = - interstitialAdConfig.visibleTo[AppUserRole.premiumUser]; - if (premiumRoleConfig != null && - premiumRoleConfig.transitionsBeforeShowingInterstitialAds != 0) { - final updatedVisibleTo = - Map.from( - interstitialAdConfig.visibleTo, - ) - ..[AppUserRole.premiumUser] = const InterstitialAdFrequencyConfig( - transitionsBeforeShowingInterstitialAds: 0, - ); - - final updatedInterstitialAdConfig = interstitialAdConfig.copyWith( - visibleTo: updatedVisibleTo, - ); - - widget.onConfigChanged( - widget.remoteConfig.copyWith( - adConfig: adConfig.copyWith( - interstitialAdConfiguration: updatedInterstitialAdConfig, - ), - ), + offset: newInterstitialValue.length, ); } } } + @override void didUpdateWidget(covariant InterstitialAdSettingsForm oldWidget) { super.didUpdateWidget(oldWidget); @@ -144,8 +105,7 @@ class _InterstitialAdSettingsFormState extends State @override void dispose() { _tabController - ..removeListener(_onTabChanged) - ..dispose(); + .dispose(); for (final controller in _transitionsBeforeShowingInterstitialAdsControllers.values) { controller.dispose(); @@ -236,8 +196,7 @@ 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]. Premium users have this field disabled - /// as they should not see ads. + /// for a given [AppUserRole]. Widget _buildInterstitialRoleSpecificFields( BuildContext context, AppLocalizations l10n, @@ -245,39 +204,36 @@ class _InterstitialAdSettingsFormState extends State InterstitialAdConfiguration config, ) { final roleConfig = config.visibleTo[role]; - final isEnabled = role != AppUserRole.premiumUser; + // Removed isEnabled check as premium users can now be manually configured. return Column( children: [ SwitchListTile( - // Changed from CheckboxListTile to SwitchListTile for consistency - title: Text(l10n.enableInArticleAdsForRoleLabel(role.l10n(context))), - value: roleConfig != null && isEnabled, - onChanged: isEnabled - ? (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: config.copyWith( - visibleTo: newVisibleTo, - ), - ), - ), - ); - } - : null, + title: Text(l10n.visibleToRoleLabel(role.l10n(context))), + value: roleConfig != null, // Value is true if roleConfig exists + 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: config.copyWith( + visibleTo: newVisibleTo, + ), + ), + ), + ); + }, ), if (roleConfig != null) Padding( @@ -295,8 +251,8 @@ class _InterstitialAdSettingsFormState extends State ); final newVisibleTo = Map.from( - config.visibleTo, - )..[role] = newRoleConfig; + config.visibleTo, + )..[role] = newRoleConfig; widget.onConfigChanged( widget.remoteConfig.copyWith( adConfig: widget.remoteConfig.adConfig.copyWith( @@ -309,7 +265,7 @@ class _InterstitialAdSettingsFormState extends State }, controller: _transitionsBeforeShowingInterstitialAdsControllers[role], - enabled: isEnabled, + // Removed enabled: isEnabled ), ), ], From fdfe26f272f4a740ac469c97b97f5630cc639fac Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 23 Sep 2025 16:41:24 +0100 Subject: [PATCH 13/13] style: format --- .../widgets/article_ad_settings_form.dart | 2 +- .../widgets/feed_ad_settings_form.dart | 66 +++++++++++-------- .../interstitial_ad_settings_form.dart | 44 ++++++------- .../view/create_headline_page.dart | 2 +- .../view/create_source_page.dart | 2 +- .../view/create_topic_page.dart | 2 +- .../view/edit_headline_page.dart | 2 +- .../view/edit_source_page.dart | 2 +- .../view/edit_topic_page.dart | 2 +- 9 files changed, 66 insertions(+), 58 deletions(-) diff --git a/lib/app_configuration/widgets/article_ad_settings_form.dart b/lib/app_configuration/widgets/article_ad_settings_form.dart index 30af7397..274aae00 100644 --- a/lib/app_configuration/widgets/article_ad_settings_form.dart +++ b/lib/app_configuration/widgets/article_ad_settings_form.dart @@ -203,7 +203,7 @@ class _ArticleAdSettingsFormState extends State children: [ SwitchListTile( title: Text(l10n.enableInArticleAdsForRoleLabel(role.l10n(context))), - value: roleSlots != null, // Value is true if roleSlots exists + value: roleSlots != null, onChanged: (value) { final newVisibleTo = Map>.from( diff --git a/lib/app_configuration/widgets/feed_ad_settings_form.dart b/lib/app_configuration/widgets/feed_ad_settings_form.dart index 21f11aaf..202a1013 100644 --- a/lib/app_configuration/widgets/feed_ad_settings_form.dart +++ b/lib/app_configuration/widgets/feed_ad_settings_form.dart @@ -39,7 +39,7 @@ class _FeedAdSettingsFormState extends State /// 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; + _adPlacementIntervalControllers; @override void initState() { @@ -59,20 +59,26 @@ class _FeedAdSettingsFormState extends State final feedAdConfig = widget.remoteConfig.adConfig.feedAdConfiguration; _adFrequencyControllers = { for (final role in AppUserRole.values) - role: TextEditingController( - text: _getAdFrequency(feedAdConfig, role).toString(), - )..selection = TextSelection.collapsed( - offset: _getAdFrequency(feedAdConfig, role).toString().length, - ), + role: + TextEditingController( + text: _getAdFrequency(feedAdConfig, role).toString(), + ) + ..selection = TextSelection.collapsed( + offset: _getAdFrequency(feedAdConfig, role).toString().length, + ), }; _adPlacementIntervalControllers = { for (final role in AppUserRole.values) - role: TextEditingController( - text: _getAdPlacementInterval(feedAdConfig, role).toString(), - )..selection = TextSelection.collapsed( - offset: - _getAdPlacementInterval(feedAdConfig, role).toString().length, - ), + role: + TextEditingController( + text: _getAdPlacementInterval(feedAdConfig, role).toString(), + ) + ..selection = TextSelection.collapsed( + offset: _getAdPlacementInterval( + feedAdConfig, + role, + ).toString().length, + ), }; } @@ -89,20 +95,21 @@ class _FeedAdSettingsFormState extends State ); } - final newPlacementIntervalValue = - _getAdPlacementInterval(feedAdConfig, role).toString(); + final newPlacementIntervalValue = _getAdPlacementInterval( + feedAdConfig, + role, + ).toString(); if (_adPlacementIntervalControllers[role]?.text != newPlacementIntervalValue) { _adPlacementIntervalControllers[role]?.text = newPlacementIntervalValue; _adPlacementIntervalControllers[role]?.selection = TextSelection.collapsed( - offset: newPlacementIntervalValue.length, - ); + offset: newPlacementIntervalValue.length, + ); } } } - @override void didUpdateWidget(covariant FeedAdSettingsForm oldWidget) { super.didUpdateWidget(oldWidget); @@ -233,7 +240,7 @@ class _FeedAdSettingsFormState extends State ), const SizedBox(height: AppSpacing.lg), SizedBox( - height: 350, // Increased height for better spacing + height: 350, child: TabBarView( controller: _tabController, children: AppUserRole.values @@ -271,10 +278,9 @@ class _FeedAdSettingsFormState extends State children: [ SwitchListTile( title: Text(l10n.visibleToRoleLabel(role.l10n(context))), - value: roleConfig != null, // Value is true if roleConfig exists + value: roleConfig != null, onChanged: (value) { - final newVisibleTo = - Map.from( + final newVisibleTo = Map.from( config.visibleTo, ); if (value) { @@ -310,12 +316,13 @@ class _FeedAdSettingsFormState extends State description: l10n.adFrequencyDescription, value: roleConfig.adFrequency, onChanged: (value) { - final newRoleConfig = - roleConfig.copyWith(adFrequency: value); + final newRoleConfig = roleConfig.copyWith( + adFrequency: value, + ); final newVisibleTo = Map.from( - config.visibleTo, - )..[role] = newRoleConfig; + config.visibleTo, + )..[role] = newRoleConfig; widget.onConfigChanged( widget.remoteConfig.copyWith( adConfig: widget.remoteConfig.adConfig.copyWith( @@ -333,12 +340,13 @@ class _FeedAdSettingsFormState extends State description: l10n.adPlacementIntervalDescription, value: roleConfig.adPlacementInterval, onChanged: (value) { - final newRoleConfig = - roleConfig.copyWith(adPlacementInterval: value); + final newRoleConfig = roleConfig.copyWith( + adPlacementInterval: value, + ); final newVisibleTo = Map.from( - config.visibleTo, - )..[role] = newRoleConfig; + config.visibleTo, + )..[role] = newRoleConfig; widget.onConfigChanged( widget.remoteConfig.copyWith( adConfig: widget.remoteConfig.adConfig.copyWith( diff --git a/lib/app_configuration/widgets/interstitial_ad_settings_form.dart b/lib/app_configuration/widgets/interstitial_ad_settings_form.dart index e20cc3b6..a9f3bc2c 100644 --- a/lib/app_configuration/widgets/interstitial_ad_settings_form.dart +++ b/lib/app_configuration/widgets/interstitial_ad_settings_form.dart @@ -35,7 +35,7 @@ class _InterstitialAdSettingsFormState extends State /// 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; + _transitionsBeforeShowingInterstitialAdsControllers; @override void initState() { @@ -56,17 +56,19 @@ class _InterstitialAdSettingsFormState extends State widget.remoteConfig.adConfig.interstitialAdConfiguration; _transitionsBeforeShowingInterstitialAdsControllers = { for (final role in AppUserRole.values) - role: TextEditingController( - text: _getTransitionsBeforeInterstitial( - interstitialConfig, - role, - ).toString(), - )..selection = TextSelection.collapsed( - offset: _getTransitionsBeforeInterstitial( - interstitialConfig, - role, - ).toString().length, - ), + role: + TextEditingController( + text: _getTransitionsBeforeInterstitial( + interstitialConfig, + role, + ).toString(), + ) + ..selection = TextSelection.collapsed( + offset: _getTransitionsBeforeInterstitial( + interstitialConfig, + role, + ).toString().length, + ), }; } @@ -86,13 +88,12 @@ class _InterstitialAdSettingsFormState extends State newInterstitialValue; _transitionsBeforeShowingInterstitialAdsControllers[role]?.selection = TextSelection.collapsed( - offset: newInterstitialValue.length, - ); + offset: newInterstitialValue.length, + ); } } } - @override void didUpdateWidget(covariant InterstitialAdSettingsForm oldWidget) { super.didUpdateWidget(oldWidget); @@ -104,8 +105,7 @@ class _InterstitialAdSettingsFormState extends State @override void dispose() { - _tabController - .dispose(); + _tabController.dispose(); for (final controller in _transitionsBeforeShowingInterstitialAdsControllers.values) { controller.dispose(); @@ -210,12 +210,12 @@ class _InterstitialAdSettingsFormState extends State children: [ SwitchListTile( title: Text(l10n.visibleToRoleLabel(role.l10n(context))), - value: roleConfig != null, // Value is true if roleConfig exists + value: roleConfig != null, onChanged: (value) { final newVisibleTo = Map.from( - config.visibleTo, - ); + config.visibleTo, + ); if (value) { // Default value when enabling for a role newVisibleTo[role] = const InterstitialAdFrequencyConfig( @@ -251,8 +251,8 @@ class _InterstitialAdSettingsFormState extends State ); final newVisibleTo = Map.from( - config.visibleTo, - )..[role] = newRoleConfig; + config.visibleTo, + )..[role] = newRoleConfig; widget.onConfigChanged( widget.remoteConfig.copyWith( adConfig: widget.remoteConfig.adConfig.copyWith( 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, ); }, ),