Skip to content

Commit 9b90964

Browse files
authored
Merge branch 'main' into refactor/filter-active-content-status
Signed-off-by: fulleni <fulleni@hotmail.com>
2 parents bd25151 + f6636d1 commit 9b90964

11 files changed

+195
-153
lines changed

lib/ads/ad_service.dart

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,12 @@ class AdService {
9595
/// - [adThemeStyle]: UI-agnostic theme properties for ad styling.
9696
/// - [headlineImageStyle]: The user's preference for feed layout,
9797
/// which can be used to request an appropriately sized ad.
98+
/// - [userRole]: The current role of the user, used to determine ad visibility.
9899
Future<InlineAd?> getFeedAd({
99100
required AdConfig adConfig,
100101
required AdType adType,
101102
required AdThemeStyle adThemeStyle,
103+
required AppUserRole userRole,
102104
HeadlineImageStyle? headlineImageStyle,
103105
}) async {
104106
_logger.info('AdService: getFeedAd called for adType: $adType');
@@ -108,6 +110,7 @@ class AdService {
108110
adThemeStyle: adThemeStyle,
109111
feedAd: true,
110112
headlineImageStyle: headlineImageStyle,
113+
userRole: userRole,
111114
);
112115
}
113116

@@ -116,25 +119,37 @@ class AdService {
116119
/// This method delegates the ad loading to the appropriate [AdProvider]
117120
/// based on the [adConfig]'s `primaryAdPlatform`. It is specifically
118121
/// designed for interstitial ads that are displayed as full-screen overlays,
119-
/// typically triggered on route changes.
122+
/// typically on route changes.
120123
///
121124
/// Returns an [InterstitialAd] if an interstitial ad is available, otherwise `null`.
122125
///
123126
/// - [adConfig]: The remote configuration for ad display rules.
124127
/// - [adThemeStyle]: UI-agnostic theme properties for ad styling.
128+
/// - [userRole]: The current role of the user, used to determine ad visibility.
125129
Future<InterstitialAd?> getInterstitialAd({
126130
required AdConfig adConfig,
127131
required AdThemeStyle adThemeStyle,
132+
required AppUserRole userRole,
128133
}) async {
129134
_logger.info('AdService: getInterstitialAd called.');
130135
if (!adConfig.enabled) {
131136
_logger.info('AdService: Ads are globally disabled in RemoteConfig.');
132137
return null;
133138
}
134139

135-
// Check if interstitial ads are enabled in the remote config.
136-
if (!adConfig.interstitialAdConfiguration.enabled) {
137-
_logger.info('AdService: Interstitial ads are disabled in RemoteConfig.');
140+
// Check if interstitial ads are enabled for the current user role.
141+
final interstitialConfig = adConfig.interstitialAdConfiguration;
142+
// Check if the interstitial ads are globally enabled AND if the current
143+
// user role has a defined configuration in the visibleTo map.
144+
final isInterstitialEnabledForRole =
145+
interstitialConfig.enabled &&
146+
interstitialConfig.visibleTo.containsKey(userRole);
147+
148+
if (!isInterstitialEnabledForRole) {
149+
_logger.info(
150+
'AdService: Interstitial ads are disabled for user role $userRole '
151+
'or globally in RemoteConfig.',
152+
);
138153
return null;
139154
}
140155

@@ -216,9 +231,13 @@ class AdService {
216231
///
217232
/// - [adConfig]: The remote configuration for ad display rules.
218233
/// - [adThemeStyle]: UI-agnostic theme properties for ad styling.
234+
/// - [userRole]: The current role of the user, used to determine ad visibility.
235+
/// - [slotType]: The specific in-article ad slot type.
219236
Future<InlineAd?> getInArticleAd({
220237
required AdConfig adConfig,
221238
required AdThemeStyle adThemeStyle,
239+
required AppUserRole userRole,
240+
required InArticleAdSlotType slotType,
222241
}) async {
223242
_logger.info('AdService: getInArticleAd called.');
224243
return _loadInlineAd(
@@ -227,6 +246,8 @@ class AdService {
227246
adThemeStyle: adThemeStyle,
228247
feedAd: false,
229248
bannerAdShape: adConfig.articleAdConfiguration.bannerAdShape,
249+
userRole: userRole,
250+
slotType: slotType,
230251
);
231252
}
232253

@@ -243,30 +264,57 @@ class AdService {
243264
/// - [headlineImageStyle]: The user's preference for feed layout,
244265
/// which can be used to request an appropriately sized ad.
245266
/// - [bannerAdShape]: The preferred shape for banner ads, used for in-article banners.
267+
/// - [userRole]: The current role of the user, used to determine ad visibility.
268+
/// - [slotType]: The specific in-article ad slot type, used for in-article ads.
246269
///
247270
/// Returns an [InlineAd] if an ad is successfully loaded, otherwise `null`.
248271
Future<InlineAd?> _loadInlineAd({
249272
required AdConfig adConfig,
250273
required AdType adType,
251274
required AdThemeStyle adThemeStyle,
252275
required bool feedAd,
276+
required AppUserRole userRole,
253277
HeadlineImageStyle? headlineImageStyle,
254278
BannerAdShape? bannerAdShape,
279+
InArticleAdSlotType? slotType,
255280
}) async {
256281
_logger.info(
257282
'AdService: _loadInlineAd called for adType: $adType, feedAd: $feedAd',
258283
);
259-
// Check if ads are globally enabled and specifically for the context (feed or article).
284+
// Check if ads are globally enabled.
260285
if (!adConfig.enabled) {
261286
_logger.info('AdService: Ads are globally disabled in RemoteConfig.');
262287
return null;
263288
}
264-
if (feedAd && !adConfig.feedAdConfiguration.enabled) {
265-
_logger.info('AdService: Feed ads are disabled in RemoteConfig.');
266-
return null;
289+
290+
// Check if ads are enabled for the specific context and user role.
291+
var isContextEnabled = false;
292+
if (feedAd) {
293+
final feedAdConfig = adConfig.feedAdConfiguration;
294+
// Check if feed ads are globally enabled AND if the current user role
295+
// has a defined configuration in the visibleTo map.
296+
isContextEnabled =
297+
feedAdConfig.enabled && feedAdConfig.visibleTo.containsKey(userRole);
298+
} else {
299+
// For in-article ads, check global article ad enablement and then
300+
// specific slot enablement for the user role.
301+
final articleAdConfig = adConfig.articleAdConfiguration;
302+
final isArticleAdEnabledForRole = articleAdConfig.visibleTo.containsKey(
303+
userRole,
304+
);
305+
final isSlotEnabledForRole =
306+
articleAdConfig.visibleTo[userRole]?[slotType] ?? false;
307+
isContextEnabled =
308+
articleAdConfig.enabled &&
309+
isArticleAdEnabledForRole &&
310+
isSlotEnabledForRole;
267311
}
268-
if (!feedAd && !adConfig.articleAdConfiguration.enabled) {
269-
_logger.info('AdService: In-article ads are disabled in RemoteConfig.');
312+
313+
if (!isContextEnabled) {
314+
_logger.info(
315+
'AdService: Ads are disabled for current context (feedAd: $feedAd, '
316+
'slotType: $slotType) and user role $userRole in RemoteConfig.',
317+
);
270318
return null;
271319
}
272320

lib/ads/inline_ad_cache_service.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,11 @@ class InlineAdCacheService {
127127
'Clearing all cached inline ads and disposing their resources.',
128128
);
129129
for (final ad in _cache.values.whereType<InlineAd>()) {
130+
130131
// Delegate disposal to AdService
131132
_adService.disposeAd(ad);
132133
}
134+
133135
// Ensure cache is empty after disposal attempts.
134136
_cache.clear();
135137
_logger.info('All cached inline ads cleared.');

lib/ads/interstitial_ad_manager.dart

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ class InterstitialAdManager {
124124
final ad = await _adService.getInterstitialAd(
125125
adConfig: adConfig,
126126
adThemeStyle: adThemeStyle,
127+
userRole: _userRole ?? AppUserRole.guestUser,
127128
);
128129

129130
if (ad != null) {
@@ -165,8 +166,12 @@ class InterstitialAdManager {
165166
}
166167

167168
final frequencyConfig =
168-
adConfig.interstitialAdConfiguration.feedInterstitialAdFrequencyConfig;
169-
final requiredTransitions = _getRequiredTransitions(frequencyConfig);
169+
adConfig.interstitialAdConfiguration.visibleTo[_userRole];
170+
171+
// If no frequency config is found for the user role, or if it's explicitly
172+
// disabled (transitionsBeforeShowingInterstitialAds == 0), then no ad should be shown.
173+
final requiredTransitions =
174+
frequencyConfig?.transitionsBeforeShowingInterstitialAds ?? 0;
170175

171176
if (requiredTransitions > 0 && _transitionCount >= requiredTransitions) {
172177
_logger.info('Transition count meets threshold. Attempting to show ad.');
@@ -288,18 +293,4 @@ class InterstitialAdManager {
288293
builder: (_) => const DemoInterstitialAdDialog(),
289294
);
290295
}
291-
292-
/// Determines the required number of transitions based on the user's role.
293-
int _getRequiredTransitions(InterstitialAdFrequencyConfig config) {
294-
switch (_userRole) {
295-
case AppUserRole.guestUser:
296-
return config.guestTransitionsBeforeShowingInterstitialAds;
297-
case AppUserRole.standardUser:
298-
return config.standardUserTransitionsBeforeShowingInterstitialAds;
299-
case AppUserRole.premiumUser:
300-
return config.premiumUserTransitionsBeforeShowingInterstitialAds;
301-
case null:
302-
return config.guestTransitionsBeforeShowingInterstitialAds;
303-
}
304-
}
305296
}

lib/ads/widgets/demo_banner_ad_widget.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class DemoBannerAdWidget extends StatelessWidget {
3838
};
3939
} else {
4040
adHeight = headlineImageStyle == HeadlineImageStyle.largeThumbnail
41-
? 250 // Height for mediumRectangle banner
41+
? 250
4242
: 50;
4343
}
4444

lib/ads/widgets/demo_native_ad_widget.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class DemoNativeAdWidget extends StatelessWidget {
2323

2424
// Determine the height based on the headlineImageStyle, mimicking real ad widgets.
2525
final adHeight = headlineImageStyle == HeadlineImageStyle.largeThumbnail
26-
? 250 // Height for medium native ad template
26+
? 250
2727
: 120;
2828

2929
return Card(

lib/ads/widgets/feed_ad_loader_widget.dart

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,10 @@ class _FeedAdLoaderWidgetState extends State<FeedAdLoaderWidget> {
205205
return;
206206
}
207207

208-
// Get the current HeadlineImageStyle from AppBloc
209-
final headlineImageStyle = context
210-
.read<AppBloc>()
211-
.state
212-
.headlineImageStyle;
208+
// Get the current HeadlineImageStyle and user role from AppBloc
209+
final appBlocState = context.read<AppBloc>().state;
210+
final headlineImageStyle = appBlocState.headlineImageStyle;
211+
final userRole = appBlocState.user?.appRole ?? AppUserRole.guestUser;
213212

214213
// Call AdService.getFeedAd with the full AdConfig and adType from the placeholder.
215214
final loadedAd = await _adService.getFeedAd(

lib/ads/widgets/in_article_ad_loader_widget.dart

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ import 'package:ui_kit/ui_kit.dart';
4141
class InArticleAdLoaderWidget extends StatefulWidget {
4242
/// {@macro in_article_ad_loader_widget}
4343
const InArticleAdLoaderWidget({
44-
required this.slotConfiguration,
44+
required this.slotType,
4545
required this.adThemeStyle,
4646
required this.adConfig,
4747
super.key,
4848
});
4949

50-
/// The configuration for this specific in-article ad slot.
51-
final InArticleAdSlotConfiguration slotConfiguration;
50+
/// The type of the in-article ad slot.
51+
final InArticleAdSlotType slotType;
5252

5353
/// The current theme style for ads, used during ad loading.
5454
final AdThemeStyle adThemeStyle;
@@ -82,18 +82,18 @@ class _InArticleAdLoaderWidgetState extends State<InArticleAdLoaderWidget> {
8282
@override
8383
void didUpdateWidget(covariant InArticleAdLoaderWidget oldWidget) {
8484
super.didUpdateWidget(oldWidget);
85-
// If the slotConfiguration changes, it means this widget is being reused
85+
// If the slotType changes, it means this widget is being reused
8686
// for a different ad slot. We need to cancel any ongoing load for the old
8787
// ad and initiate a new load for the new ad.
8888
// Also, if the adConfig changes, we should re-evaluate and potentially reload.
89-
if (widget.slotConfiguration != oldWidget.slotConfiguration ||
89+
if (widget.slotType != oldWidget.slotType ||
9090
widget.adConfig != oldWidget.adConfig) {
9191
_logger.info(
92-
'InArticleAdLoaderWidget updated for new slot configuration or AdConfig changed. Re-loading ad.',
92+
'InArticleAdLoaderWidget updated for new slot type: '
93+
'${widget.slotType.name} or AdConfig changed. Re-loading ad.',
9394
);
9495
// Dispose of the old ad's resources before loading a new one.
95-
final oldCacheKey =
96-
'in_article_ad_${oldWidget.slotConfiguration.slotType.name}';
96+
final oldCacheKey = 'in_article_ad_${oldWidget.slotType.name}';
9797
// Only dispose if it was actually cached (i.e., not an AdMob in-article ad).
9898
// The removeAndDisposeAd method handles the check internally.
9999
_adCacheService.removeAndDisposeAd(oldCacheKey);
@@ -116,7 +116,7 @@ class _InArticleAdLoaderWidgetState extends State<InArticleAdLoaderWidget> {
116116
@override
117117
void dispose() {
118118
// Dispose of the ad's resources when the widget is permanently removed.
119-
final cacheKey = 'in_article_ad_${widget.slotConfiguration.slotType.name}';
119+
final cacheKey = 'in_article_ad_${widget.slotType.name}';
120120
// Only dispose if it was actually cached (i.e., not an AdMob in-article ad).
121121
// The removeAndDisposeAd method handles the check internally.
122122
_adCacheService.removeAndDisposeAd(cacheKey);
@@ -148,7 +148,7 @@ class _InArticleAdLoaderWidgetState extends State<InArticleAdLoaderWidget> {
148148

149149
// In-article ads are typically unique to their slot, so we use the slotType
150150
// as part of the cache key to differentiate them.
151-
final cacheKey = 'in_article_ad_${widget.slotConfiguration.slotType.name}';
151+
final cacheKey = 'in_article_ad_${widget.slotType.name}';
152152
InlineAd? loadedAd;
153153

154154
// Determine if the primary ad platform is AdMob.
@@ -159,7 +159,7 @@ class _InArticleAdLoaderWidgetState extends State<InArticleAdLoaderWidget> {
159159
final cachedAd = _adCacheService.getAd(cacheKey);
160160
if (cachedAd != null) {
161161
_logger.info(
162-
'Using cached in-article ad for slot: ${widget.slotConfiguration.slotType.name}',
162+
'Using cached in-article ad for slot: ${widget.slotType.name}',
163163
);
164164
if (!mounted) return;
165165
setState(() {
@@ -174,23 +174,27 @@ class _InArticleAdLoaderWidgetState extends State<InArticleAdLoaderWidget> {
174174
} else {
175175
_logger.info(
176176
'AdMob is primary ad platform. Bypassing cache for in-article ad '
177-
'for slot: ${widget.slotConfiguration.slotType.name}.',
177+
'for slot: ${widget.slotType.name}.',
178178
);
179179
}
180180

181-
_logger.info(
182-
'Loading new in-article ad for slot: ${widget.slotConfiguration.slotType.name}',
183-
);
181+
_logger.info('Loading new in-article ad for slot: ${widget.slotType.name}');
184182
try {
183+
// Get the current user role from AppBloc
184+
final appBlocState = context.read<AppBloc>().state;
185+
final userRole = appBlocState.user?.appRole ?? AppUserRole.guestUser;
186+
185187
// Call AdService.getInArticleAd with the full AdConfig.
186188
loadedAd = await _adService.getInArticleAd(
187189
adConfig: widget.adConfig,
188190
adThemeStyle: widget.adThemeStyle,
191+
userRole: userRole,
192+
slotType: widget.slotType,
189193
);
190194

191195
if (loadedAd != null) {
192196
_logger.info(
193-
'New in-article ad loaded for slot: ${widget.slotConfiguration.slotType.name}',
197+
'New in-article ad loaded for slot: ${widget.slotType.name}',
194198
);
195199
// Only cache non-AdMob ads. AdMob ads are not cached to prevent reuse issues.
196200
if (!isAdMob) {
@@ -211,7 +215,7 @@ class _InArticleAdLoaderWidgetState extends State<InArticleAdLoaderWidget> {
211215
}
212216
} else {
213217
_logger.warning(
214-
'Failed to load in-article ad for slot: ${widget.slotConfiguration.slotType.name}. '
218+
'Failed to load in-article ad for slot: ${widget.slotType.name}. '
215219
'No ad returned.',
216220
);
217221
if (!mounted) return;
@@ -227,7 +231,7 @@ class _InArticleAdLoaderWidgetState extends State<InArticleAdLoaderWidget> {
227231
}
228232
} catch (e, s) {
229233
_logger.severe(
230-
'Error loading in-article ad for slot: ${widget.slotConfiguration.slotType.name}: $e',
234+
'Error loading in-article ad for slot: ${widget.slotType.name}: $e',
231235
e,
232236
s,
233237
);

0 commit comments

Comments
 (0)