Skip to content

Commit 1501cf1

Browse files
authored
Merge pull request #111 from flutter-news-app-full-source-code/no-op-ad-provider-for-demo-enviorement
No op ad provider for demo enviorement
2 parents d11418e + d730d6d commit 1501cf1

14 files changed

+333
-83
lines changed

lib/ads/ad_navigator_observer.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:flutter_news_app_mobile_client_full_source_code/ads/ad_service.d
66
import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/ad_theme_style.dart';
77
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/widgets.dart';
88
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart';
9+
import 'package:flutter_news_app_mobile_client_full_source_code/app/config/app_environment.dart';
910
import 'package:google_mobile_ads/google_mobile_ads.dart' as admob;
1011
import 'package:logging/logging.dart';
1112

@@ -124,8 +125,21 @@ class AdNavigatorObserver extends NavigatorObserver {
124125

125126
/// Requests and shows an interstitial ad if conditions are met.
126127
Future<void> _showInterstitialAd() async {
127-
final remoteConfig = appStateProvider().remoteConfig;
128+
final appState = appStateProvider();
129+
final appEnvironment = appState.environment;
130+
final remoteConfig = appState.remoteConfig;
131+
132+
// In demo environment, display a placeholder interstitial ad directly.
133+
if (appEnvironment == AppEnvironment.demo) {
134+
_logger.info('Demo environment: Showing placeholder interstitial ad.');
135+
await showDialog<void>(
136+
context: navigator!.context,
137+
builder: (context) => const DemoInterstitialAdDialog(),
138+
);
139+
return;
140+
}
128141

142+
// For other environments (development, production), proceed with real ad loading.
129143
// This is a secondary check. The primary check is in _handlePageTransition.
130144
if (remoteConfig == null || !remoteConfig.adConfig.enabled) {
131145
_logger.info('Interstitial ads disabled or remote config not available.');
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import 'package:core/core.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/app_localizations.dart';
4+
import 'package:ui_kit/ui_kit.dart';
5+
6+
/// {@template demo_banner_ad_widget}
7+
/// A widget that displays a placeholder for a banner ad in demo mode.
8+
///
9+
/// This widget mimics the visual dimensions of a real banner ad but
10+
/// contains only static text to indicate it's a demo.
11+
/// {@endtemplate}
12+
class DemoBannerAdWidget extends StatelessWidget {
13+
/// {@macro demo_banner_ad_widget}
14+
const DemoBannerAdWidget({this.headlineImageStyle, super.key});
15+
16+
/// The user's preference for feed layout, used to determine the ad's visual size.
17+
final HeadlineImageStyle? headlineImageStyle;
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
final theme = Theme.of(context);
22+
23+
// Determine the height based on the headlineImageStyle, mimicking real ad widgets.
24+
final adHeight =
25+
headlineImageStyle == HeadlineImageStyle.largeThumbnail
26+
? 250 // Height for mediumRectangle banner
27+
: 50; // Height for standard banner
28+
29+
return Card(
30+
margin: const EdgeInsets.symmetric(
31+
horizontal: AppSpacing.paddingMedium,
32+
vertical: AppSpacing.xs,
33+
),
34+
child: SizedBox(
35+
height: adHeight.toDouble(),
36+
width: double.infinity,
37+
child: Center(
38+
child: Text(
39+
AppLocalizations.of(context).demoBannerAdText,
40+
style: theme.textTheme.titleMedium?.copyWith(
41+
color: theme.colorScheme.onSurfaceVariant,
42+
),
43+
textAlign: TextAlign.center,
44+
),
45+
),
46+
),
47+
);
48+
}
49+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/app_localizations.dart';
3+
import 'package:ui_kit/ui_kit.dart';
4+
5+
/// {@template demo_interstitial_ad_dialog}
6+
/// A dialog widget that displays a placeholder for an interstitial ad in demo mode.
7+
///
8+
/// This dialog mimics a full-screen interstitial ad but contains only static
9+
/// text to indicate it's a demo.
10+
/// {@endtemplate}
11+
class DemoInterstitialAdDialog extends StatelessWidget {
12+
/// {@macro demo_interstitial_ad_dialog}
13+
const DemoInterstitialAdDialog({super.key});
14+
15+
@override
16+
Widget build(BuildContext context) {
17+
final theme = Theme.of(context);
18+
return Dialog.fullscreen(
19+
backgroundColor: theme.colorScheme.surface,
20+
child: Stack(
21+
children: [
22+
Center(
23+
child: SingleChildScrollView(
24+
child: Column(
25+
mainAxisAlignment: MainAxisAlignment.center,
26+
children: [
27+
Text(
28+
AppLocalizations.of(context).demoInterstitialAdText,
29+
style: theme.textTheme.titleLarge?.copyWith(
30+
color: theme.colorScheme.onSurface,
31+
),
32+
textAlign: TextAlign.center,
33+
),
34+
const SizedBox(height: AppSpacing.md),
35+
Text(
36+
AppLocalizations.of(context).demoInterstitialAdDescription,
37+
style: theme.textTheme.bodyMedium?.copyWith(
38+
color: theme.colorScheme.onSurfaceVariant,
39+
),
40+
textAlign: TextAlign.center,
41+
),
42+
],
43+
),
44+
),
45+
),
46+
Positioned(
47+
top: AppSpacing.lg,
48+
right: AppSpacing.lg,
49+
child: IconButton(
50+
icon: Icon(Icons.close, color: theme.colorScheme.onSurface),
51+
onPressed: () {
52+
// Dismiss the dialog.
53+
Navigator.of(context).pop();
54+
},
55+
),
56+
),
57+
],
58+
),
59+
);
60+
}
61+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import 'package:core/core.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/app_localizations.dart';
4+
import 'package:ui_kit/ui_kit.dart';
5+
6+
/// {@template demo_native_ad_widget}
7+
/// A widget that displays a placeholder for a native ad in demo mode.
8+
///
9+
/// This widget mimics the visual dimensions of a real native ad but
10+
/// contains only static text to indicate it's a demo.
11+
/// {@endtemplate}
12+
class DemoNativeAdWidget extends StatelessWidget {
13+
/// {@macro demo_native_ad_widget}
14+
const DemoNativeAdWidget({this.headlineImageStyle, super.key});
15+
16+
/// The user's preference for feed layout, used to determine the ad's visual size.
17+
final HeadlineImageStyle? headlineImageStyle;
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
final theme = Theme.of(context);
22+
final l10n = AppLocalizations.of(context);
23+
24+
// Determine the height based on the headlineImageStyle, mimicking real ad widgets.
25+
final adHeight =
26+
headlineImageStyle == HeadlineImageStyle.largeThumbnail
27+
? 340 // Height for medium native ad template
28+
: 120; // Height for small native ad template
29+
30+
return Card(
31+
margin: const EdgeInsets.symmetric(
32+
horizontal: AppSpacing.paddingMedium,
33+
vertical: AppSpacing.xs,
34+
),
35+
child: SizedBox(
36+
height: adHeight.toDouble(),
37+
width: double.infinity,
38+
child: Center(
39+
child: Text(
40+
l10n.demoNativeAdText,
41+
style: theme.textTheme.titleMedium?.copyWith(
42+
color: theme.colorScheme.onSurfaceVariant,
43+
),
44+
textAlign: TextAlign.center,
45+
),
46+
),
47+
),
48+
);
49+
}
50+
}

lib/ads/widgets/feed_ad_loader_widget.dart

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/banne
1111
import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/inline_ad.dart';
1212
import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/native_ad.dart';
1313
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/admob_inline_ad_widget.dart';
14+
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/demo_banner_ad_widget.dart';
15+
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/demo_native_ad_widget.dart';
1416
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/local_banner_ad_widget.dart';
1517
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/local_native_ad_widget.dart';
16-
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/placeholder_ad_widget.dart';
1718
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart';
19+
import 'package:flutter_news_app_mobile_client_full_source_code/app/config/app_environment.dart';
1820
import 'package:logging/logging.dart';
1921
import 'package:ui_kit/ui_kit.dart';
2022

@@ -261,6 +263,30 @@ class _FeedAdLoaderWidgetState extends State<FeedAdLoaderWidget> {
261263

262264
@override
263265
Widget build(BuildContext context) {
266+
final appEnvironment = context.read<AppBloc>().state.environment;
267+
final headlineImageStyle = context
268+
.read<AppBloc>()
269+
.state
270+
.settings
271+
.feedPreferences
272+
.headlineImageStyle;
273+
274+
// In demo environment, display placeholder ads directly.
275+
if (appEnvironment == AppEnvironment.demo) {
276+
switch (widget.adPlaceholder.adType) {
277+
case AdType.native:
278+
return DemoNativeAdWidget(headlineImageStyle: headlineImageStyle);
279+
case AdType.banner:
280+
return DemoBannerAdWidget(headlineImageStyle: headlineImageStyle);
281+
case AdType.interstitial:
282+
case AdType.video:
283+
// Interstitial and video ads are not inline, so they won't be
284+
// handled by FeedAdLoaderWidget. Fallback to a generic placeholder.
285+
return const SizedBox.shrink();
286+
}
287+
}
288+
289+
// For other environments (development, production), proceed with real ad loading.
264290
if (_isLoading) {
265291
// Show a shimmer or loading indicator while the ad is being loaded.
266292
return const Padding(
@@ -276,47 +302,32 @@ class _FeedAdLoaderWidgetState extends State<FeedAdLoaderWidget> {
276302
),
277303
);
278304
} else if (_hasError || _loadedAd == null) {
279-
// Show a placeholder or error message if ad loading failed.
280-
return const PlaceholderAdWidget();
305+
// Fallback for unsupported local ad types or errors
306+
return const SizedBox.shrink();
281307
} else {
282308
// If an ad is successfully loaded, dispatch to the appropriate
283309
// provider-specific widget for rendering.
284310
switch (_loadedAd!.provider) {
285311
case AdPlatformType.admob:
286312
return AdmobInlineAdWidget(
287313
inlineAd: _loadedAd!,
288-
headlineImageStyle: context
289-
.read<AppBloc>()
290-
.state
291-
.settings
292-
.feedPreferences
293-
.headlineImageStyle,
314+
headlineImageStyle: headlineImageStyle,
294315
);
295316
case AdPlatformType.local:
296317
if (_loadedAd is NativeAd && _loadedAd!.adObject is LocalNativeAd) {
297318
return LocalNativeAdWidget(
298319
localNativeAd: _loadedAd!.adObject as LocalNativeAd,
299-
headlineImageStyle: context
300-
.read<AppBloc>()
301-
.state
302-
.settings
303-
.feedPreferences
304-
.headlineImageStyle,
320+
headlineImageStyle: headlineImageStyle,
305321
);
306322
} else if (_loadedAd is BannerAd &&
307323
_loadedAd!.adObject is LocalBannerAd) {
308324
return LocalBannerAdWidget(
309325
localBannerAd: _loadedAd!.adObject as LocalBannerAd,
310-
headlineImageStyle: context
311-
.read<AppBloc>()
312-
.state
313-
.settings
314-
.feedPreferences
315-
.headlineImageStyle,
326+
headlineImageStyle: headlineImageStyle,
316327
);
317328
}
318329
// Fallback for unsupported local ad types or errors
319-
return const PlaceholderAdWidget();
330+
return const SizedBox.shrink();
320331
}
321332
}
322333
}

lib/ads/widgets/in_article_ad_loader_widget.dart

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/banne
1010
import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/inline_ad.dart';
1111
import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/native_ad.dart';
1212
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/admob_inline_ad_widget.dart';
13+
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/demo_banner_ad_widget.dart';
14+
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/demo_native_ad_widget.dart';
1315
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/local_banner_ad_widget.dart';
1416
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/local_native_ad_widget.dart';
15-
import 'package:flutter_news_app_mobile_client_full_source_code/ads/widgets/placeholder_ad_widget.dart';
1617
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart';
18+
import 'package:flutter_news_app_mobile_client_full_source_code/app/config/app_environment.dart';
1719
import 'package:logging/logging.dart';
1820
import 'package:ui_kit/ui_kit.dart';
1921

@@ -208,6 +210,33 @@ class _InArticleAdLoaderWidgetState extends State<InArticleAdLoaderWidget> {
208210

209211
@override
210212
Widget build(BuildContext context) {
213+
final appEnvironment = context.read<AppBloc>().state.environment;
214+
final headlineImageStyle = context
215+
.read<AppBloc>()
216+
.state
217+
.settings
218+
.feedPreferences
219+
.headlineImageStyle;
220+
221+
// In demo environment, display placeholder ads directly.
222+
if (appEnvironment == AppEnvironment.demo) {
223+
// Determine the ad type from the adConfig's articleAdConfiguration
224+
final adType =
225+
widget.adConfig.articleAdConfiguration.defaultInArticleAdType;
226+
switch (adType) {
227+
case AdType.native:
228+
return DemoNativeAdWidget(headlineImageStyle: headlineImageStyle);
229+
case AdType.banner:
230+
return DemoBannerAdWidget(headlineImageStyle: headlineImageStyle);
231+
case AdType.interstitial:
232+
case AdType.video:
233+
// Interstitial and video ads are not inline, so they won't be
234+
// handled by InArticleAdLoaderWidget. Fallback to a generic placeholder.
235+
return const SizedBox.shrink();
236+
}
237+
}
238+
239+
// For other environments (development, production), proceed with real ad loading.
211240
if (_isLoading) {
212241
return const Padding(
213242
padding: EdgeInsets.symmetric(
@@ -222,26 +251,31 @@ class _InArticleAdLoaderWidgetState extends State<InArticleAdLoaderWidget> {
222251
),
223252
);
224253
} else if (_hasError || _loadedAd == null) {
225-
return const PlaceholderAdWidget();
254+
return const SizedBox.shrink();
226255
} else {
227256
// If an ad is successfully loaded, dispatch to the appropriate
228257
// provider-specific widget for rendering.
229258
switch (_loadedAd!.provider) {
230259
case AdPlatformType.admob:
231-
return AdmobInlineAdWidget(inlineAd: _loadedAd!);
260+
return AdmobInlineAdWidget(
261+
inlineAd: _loadedAd!,
262+
headlineImageStyle: headlineImageStyle,
263+
);
232264
case AdPlatformType.local:
233265
if (_loadedAd is NativeAd && _loadedAd!.adObject is LocalNativeAd) {
234266
return LocalNativeAdWidget(
235267
localNativeAd: _loadedAd!.adObject as LocalNativeAd,
268+
headlineImageStyle: headlineImageStyle,
236269
);
237270
} else if (_loadedAd is BannerAd &&
238271
_loadedAd!.adObject is LocalBannerAd) {
239272
return LocalBannerAdWidget(
240273
localBannerAd: _loadedAd!.adObject as LocalBannerAd,
274+
headlineImageStyle: headlineImageStyle,
241275
);
242276
}
243277
// Fallback for unsupported local ad types or errors
244-
return const PlaceholderAdWidget();
278+
return const SizedBox.shrink();
245279
}
246280
}
247281
}

0 commit comments

Comments
 (0)