@@ -6,8 +6,15 @@ import 'package:flutter_news_app_mobile_client_full_source_code/ads/ad_service.d
66import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/ad_theme_style.dart' ;
77import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/interstitial_ad.dart' ;
88import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart' ;
9- import 'package:logging/logging.dart' ;
109import 'package:google_mobile_ads/google_mobile_ads.dart' as admob;
10+ import 'package:logging/logging.dart' ;
11+
12+ /// A function that provides the current [AppState] .
13+ ///
14+ /// This is used for dependency injection to decouple the [AdNavigatorObserver]
15+ /// from a direct dependency on the [AppBloc] instance, making it more
16+ /// testable and reusable.
17+ typedef AppStateProvider = AppState Function ();
1118
1219/// {@template ad_navigator_observer}
1320/// A [NavigatorObserver] that listens to route changes and triggers
@@ -18,54 +25,112 @@ import 'package:google_mobile_ads/google_mobile_ads.dart' as admob;
1825/// 2. Requesting an interstitial ad from the [AdService] when the criteria are met.
1926/// 3. Showing the interstitial ad to the user.
2027///
21- /// It interacts with the [AppBloc] to get the current [RemoteConfig] and
22- /// user's ad frequency settings, and to dispatch events for page transitions .
28+ /// It retrieves the current [AppState] via the [appStateProvider] to get the
29+ /// latest [RemoteConfig] and user's ad frequency settings.
2330/// {@endtemplate}
2431class AdNavigatorObserver extends NavigatorObserver {
2532 /// {@macro ad_navigator_observer}
2633 AdNavigatorObserver ({
27- required this .appBloc ,
34+ required this .appStateProvider ,
2835 required this .adService,
2936 required AdThemeStyle adThemeStyle,
3037 Logger ? logger,
31- }) : _logger = logger ?? Logger ('AdNavigatorObserver' ),
32- _adThemeStyle = adThemeStyle;
38+ }) : _logger = logger ?? Logger ('AdNavigatorObserver' ),
39+ _adThemeStyle = adThemeStyle;
40+
41+ /// A function that provides the current [AppState] .
42+ final AppStateProvider appStateProvider;
3343
34- final AppBloc appBloc;
44+ /// The service responsible for fetching and loading ads.
3545 final AdService adService;
46+
3647 final Logger _logger;
3748 final AdThemeStyle _adThemeStyle;
3849
50+ /// Tracks the number of page transitions since the last interstitial ad.
51+ int _pageTransitionCount = 0 ;
52+
3953 @override
4054 void didPush (Route <dynamic > route, Route <dynamic >? previousRoute) {
4155 super .didPush (route, previousRoute);
4256 _logger.info ('Route pushed: ${route .settings .name }' );
43- _handlePageTransition (route);
57+ if (route is PageRoute && route.settings.name != null ) {
58+ _handlePageTransition ();
59+ }
4460 }
4561
4662 @override
4763 void didPop (Route <dynamic > route, Route <dynamic >? previousRoute) {
4864 super .didPop (route, previousRoute);
4965 _logger.info ('Route popped: ${route .settings .name }' );
50- _handlePageTransition (route);
66+ if (route is PageRoute && route.settings.name != null ) {
67+ _handlePageTransition ();
68+ }
5169 }
5270
53- /// Handles a page transition event.
54- ///
55- /// Dispatches an [AppPageTransitioned] event to the [AppBloc] to update
56- /// the transition count and potentially trigger an interstitial ad.
57- void _handlePageTransition (Route <dynamic > route) {
58- if (route is PageRoute && route.settings.name != null ) {
59- appBloc.add (const AppPageTransitioned ());
71+ /// Handles a page transition event, checks ad frequency, and shows an ad if needed.
72+ void _handlePageTransition () {
73+ _pageTransitionCount++ ;
74+ _logger.info ('Page transitioned. Current count: $_pageTransitionCount ' );
75+
76+ final appState = appStateProvider ();
77+ final remoteConfig = appState.remoteConfig;
78+ final user = appState.user;
79+
80+ // Only proceed if remote config is available, ads are globally enabled,
81+ // and interstitial ads are enabled in the config.
82+ if (remoteConfig == null ||
83+ ! remoteConfig.adConfig.enabled ||
84+ ! remoteConfig.adConfig.interstitialAdConfiguration.enabled) {
85+ _logger.info ('Interstitial ads are not enabled or config not ready.' );
86+ return ;
87+ }
88+
89+ final interstitialConfig =
90+ remoteConfig.adConfig.interstitialAdConfiguration;
91+ final frequencyConfig =
92+ interstitialConfig.feedInterstitialAdFrequencyConfig;
93+
94+ // Determine the required transitions based on user role.
95+ final int requiredTransitions;
96+ switch (user? .appRole) {
97+ case AppUserRole .guestUser:
98+ requiredTransitions =
99+ frequencyConfig.guestTransitionsBeforeShowingInterstitialAds;
100+ break ;
101+ case AppUserRole .standardUser:
102+ requiredTransitions =
103+ frequencyConfig.standardUserTransitionsBeforeShowingInterstitialAds;
104+ break ;
105+ case AppUserRole .premiumUser:
106+ requiredTransitions =
107+ frequencyConfig.premiumUserTransitionsBeforeShowingInterstitialAds;
108+ break ;
109+ case null :
110+ // If user is null, default to guest user settings.
111+ requiredTransitions =
112+ frequencyConfig.guestTransitionsBeforeShowingInterstitialAds;
113+ break ;
114+ }
115+
116+ _logger.info (
117+ 'Required transitions for user role ${user ?.appRole }: $requiredTransitions ' ,
118+ );
119+
120+ // Check if it's time to show an interstitial ad.
121+ if (requiredTransitions > 0 &&
122+ _pageTransitionCount >= requiredTransitions) {
123+ _logger.info ('Interstitial ad due. Requesting ad.' );
124+ _showInterstitialAd ();
125+ _pageTransitionCount = 0 ; // Reset count after showing
60126 }
61127 }
62128
63129 /// Requests and shows an interstitial ad if conditions are met.
64- ///
65- /// This method is called by the [AppBloc] when it determines an ad is due.
66- Future <void > showInterstitialAd () async {
67- final remoteConfig = appBloc.state.remoteConfig;
130+ Future <void > _showInterstitialAd () async {
131+ final remoteConfig = appStateProvider ().remoteConfig;
68132
133+ // This is a secondary check. The primary check is in _handlePageTransition.
69134 if (remoteConfig == null || ! remoteConfig.adConfig.enabled) {
70135 _logger.info ('Interstitial ads disabled or remote config not available.' );
71136 return ;
@@ -91,21 +156,20 @@ class AdNavigatorObserver extends NavigatorObserver {
91156 if (interstitialAd.provider == AdPlatformType .admob &&
92157 interstitialAd.adObject is admob.InterstitialAd ) {
93158 final admobInterstitialAd =
94- interstitialAd.adObject as admob.InterstitialAd ;
95- admobInterstitialAd.fullScreenContentCallback =
96- admob.FullScreenContentCallback (
97- onAdDismissedFullScreenContent: (ad) {
98- _logger.info ('Interstitial Ad dismissed.' );
99- ad.dispose ();
100- },
101- onAdFailedToShowFullScreenContent: (ad, error) {
102- _logger.severe ('Interstitial Ad failed to show: $error ' );
103- ad.dispose ();
104- },
105- onAdShowedFullScreenContent: (ad) {
106- _logger.info ('Interstitial Ad showed.' );
107- },
108- );
159+ interstitialAd.adObject as admob.InterstitialAd
160+ ..fullScreenContentCallback = admob.FullScreenContentCallback (
161+ onAdDismissedFullScreenContent: (ad) {
162+ _logger.info ('Interstitial Ad dismissed.' );
163+ ad.dispose ();
164+ },
165+ onAdFailedToShowFullScreenContent: (ad, error) {
166+ _logger.severe ('Interstitial Ad failed to show: $error ' );
167+ ad.dispose ();
168+ },
169+ onAdShowedFullScreenContent: (ad) {
170+ _logger.info ('Interstitial Ad showed.' );
171+ },
172+ );
109173 await admobInterstitialAd.show ();
110174 } else if (interstitialAd.provider == AdPlatformType .local &&
111175 interstitialAd.adObject is LocalInterstitialAd ) {
0 commit comments