Skip to content

Commit d709291

Browse files
committed
feat(ads): implement AdNavigatorObserver for interstitial ads
- Add AdNavigatorObserver class to listen to route changes and trigger interstitial ad display - Integrate with AppBloc for remote config and user ad frequency settings - Implement logic to show interstitial ads based on navigation events - Include logging for ad-related actions
1 parent 06c8f36 commit d709291

File tree

1 file changed

+132
-0
lines changed

1 file changed

+132
-0
lines changed

lib/ads/ad_navigator_observer.dart

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import 'dart:async';
2+
3+
import 'package:core/core.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:flutter_bloc/flutter_bloc.dart';
6+
import 'package:flutter_news_app_mobile_client_full_source_code/ads/ad_service.dart';
7+
import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/ad_theme_style.dart';
8+
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart';
9+
import 'package:logging/logging.dart';
10+
11+
/// {@template ad_navigator_observer}
12+
/// A [NavigatorObserver] that listens to route changes and triggers
13+
/// interstitial ad display based on [RemoteConfig] settings.
14+
///
15+
/// This observer is responsible for:
16+
/// 1. Tracking page transitions to determine when an interstitial ad should be shown.
17+
/// 2. Requesting an interstitial ad from the [AdService] when the criteria are met.
18+
/// 3. Showing the interstitial ad to the user.
19+
///
20+
/// It interacts with the [AppBloc] to get the current [RemoteConfig] and
21+
/// user's ad frequency settings, and to dispatch events for page transitions.
22+
/// {@endtemplate}
23+
class AdNavigatorObserver extends NavigatorObserver {
24+
/// {@macro ad_navigator_observer}
25+
AdNavigatorObserver({
26+
required this.appBloc,
27+
required this.adService,
28+
Logger? logger,
29+
}) : _logger = logger ?? Logger('AdNavigatorObserver');
30+
31+
final AppBloc appBloc;
32+
final AdService adService;
33+
final Logger _logger;
34+
35+
/// Tracks the number of page transitions since the last interstitial ad was shown.
36+
/// This is managed by the AppBloc.
37+
38+
@override
39+
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
40+
super.didPush(route, previousRoute);
41+
_logger.info('Route pushed: ${route.settings.name}');
42+
_handlePageTransition();
43+
}
44+
45+
@override
46+
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
47+
super.didPop(route, previousRoute);
48+
_logger.info('Route popped: ${route.settings.name}');
49+
_handlePageTransition();
50+
}
51+
52+
/// Handles a page transition event.
53+
///
54+
/// Dispatches an [AppPageTransitioned] event to the [AppBloc] to update
55+
/// the transition count and potentially trigger an interstitial ad.
56+
void _handlePageTransition() {
57+
// Only consider actual page routes, not overlays or dialogs.
58+
// This check is a simplification; a more robust solution might inspect
59+
// route types or use GoRouter's specific navigation events.
60+
if (navigator?.currentRoutes.last.settings.name != null) {
61+
appBloc.add(const AppPageTransitioned());
62+
}
63+
}
64+
65+
/// Requests and shows an interstitial ad if conditions are met.
66+
///
67+
/// This method is called by the [AppBloc] when it determines an ad is due.
68+
Future<void> showInterstitialAd() async {
69+
final remoteConfig = appBloc.state.remoteConfig;
70+
final user = appBloc.state.user;
71+
72+
if (remoteConfig == null || !remoteConfig.adConfig.enabled) {
73+
_logger.info('Interstitial ads disabled or remote config not available.');
74+
return;
75+
}
76+
77+
final adConfig = remoteConfig.adConfig;
78+
final interstitialConfig = adConfig.interstitialAdConfiguration;
79+
80+
if (!interstitialConfig.enabled) {
81+
_logger.info('Interstitial ads are specifically disabled in config.');
82+
return;
83+
}
84+
85+
// Create AdThemeStyle from current theme for ad loading.
86+
// This requires a BuildContext, which is not directly available here.
87+
// The AppBloc will need to provide the AdThemeStyle or the AdService
88+
// will need to be able to create it. For now, we'll assume a default
89+
// or that the AdService can handle it.
90+
//
91+
// TODO(fulleni): Revisit how AdThemeStyle is passed for interstitial ads.
92+
// For now, a placeholder AdThemeStyle is used.
93+
final adThemeStyle = AdThemeStyle.fromTheme(ThemeData());
94+
95+
_logger.info('Attempting to load interstitial ad...');
96+
final interstitialAd = await adService.getInterstitialAd(
97+
adConfig: adConfig,
98+
adThemeStyle: adThemeStyle,
99+
);
100+
101+
if (interstitialAd != null) {
102+
_logger.info('Interstitial ad loaded. Showing...');
103+
// Show the AdMob interstitial ad.
104+
if (interstitialAd.provider == AdPlatformType.admob &&
105+
interstitialAd.adObject is admob.InterstitialAd) {
106+
final admobInterstitialAd =
107+
interstitialAd.adObject as admob.InterstitialAd;
108+
admobInterstitialAd.fullScreenContentCallback =
109+
admob.FullScreenContentCallback(
110+
onAdDismissedFullScreenContent: (ad) {
111+
_logger.info('Interstitial Ad dismissed.');
112+
ad.dispose();
113+
},
114+
onAdFailedToShowFullScreenContent: (ad, error) {
115+
_logger.severe('Interstitial Ad failed to show: $error');
116+
ad.dispose();
117+
},
118+
onAdShowedFullScreenContent: (ad) {
119+
_logger.info('Interstitial Ad showed.');
120+
},
121+
);
122+
await admobInterstitialAd.show();
123+
} else if (interstitialAd.provider == AdPlatformType.local &&
124+
interstitialAd.adObject is LocalInterstitialAd) {
125+
// TODO(fulleni): Implement showing local interstitial ad (e.g., via a dialog).
126+
_logger.info('Showing local interstitial ad (placeholder).');
127+
}
128+
} else {
129+
_logger.info('No interstitial ad loaded.');
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)