feat(ads): home/map banner slot (hidden when removed or unloaded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import '../../game/models/season.dart';
|
|||||||
import '../../game/models/stage.dart';
|
import '../../game/models/stage.dart';
|
||||||
import '../../l10n/gen/app_localizations.dart';
|
import '../../l10n/gen/app_localizations.dart';
|
||||||
import '../../state/providers.dart';
|
import '../../state/providers.dart';
|
||||||
|
import '../widgets/banner_ad_slot.dart';
|
||||||
import '../widgets/season_background.dart';
|
import '../widgets/season_background.dart';
|
||||||
import 'game_screen.dart';
|
import 'game_screen.dart';
|
||||||
import 'season_map_screen.dart';
|
import 'season_map_screen.dart';
|
||||||
@@ -32,6 +33,9 @@ class HomeScreen extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
const SeasonBackground(theme: SeasonTheme.fallback),
|
const SeasonBackground(theme: SeasonTheme.fallback),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -111,6 +115,10 @@ class HomeScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const BannerAdSlot(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import '../../game/models/season.dart';
|
import '../../game/models/season.dart';
|
||||||
import '../../state/providers.dart';
|
import '../../state/providers.dart';
|
||||||
import '../theme/palette.dart';
|
import '../theme/palette.dart';
|
||||||
|
import '../widgets/banner_ad_slot.dart';
|
||||||
import '../widgets/map_layout.dart';
|
import '../widgets/map_layout.dart';
|
||||||
import '../widgets/season_background.dart';
|
import '../widgets/season_background.dart';
|
||||||
import '../widgets/tile_painter.dart';
|
import '../widgets/tile_painter.dart';
|
||||||
@@ -74,7 +75,11 @@ class _JourneyMapState extends ConsumerState<_JourneyMap> {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
body: Stack(
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
SeasonBackground(theme: pack.theme),
|
SeasonBackground(theme: pack.theme),
|
||||||
@@ -124,8 +129,8 @@ class _JourneyMapState extends ConsumerState<_JourneyMap> {
|
|||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
top: MediaQuery.of(context).padding.top + 6,
|
top: 6,
|
||||||
bottom: 12,
|
bottom: 12,
|
||||||
left: 8,
|
left: 8,
|
||||||
right: 16,
|
right: 16,
|
||||||
@@ -169,6 +174,11 @@ class _JourneyMapState extends ConsumerState<_JourneyMap> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
const BannerAdSlot(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// lib/ui/widgets/banner_ad_slot.dart
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||||
|
|
||||||
|
import '../../state/providers.dart';
|
||||||
|
|
||||||
|
/// A self-loading 320x50 banner for the home/map screens. Renders nothing
|
||||||
|
/// when ads are removed or the banner has not loaded — never reserves blank
|
||||||
|
/// space and never appears on the game screen.
|
||||||
|
class BannerAdSlot extends ConsumerStatefulWidget {
|
||||||
|
const BannerAdSlot({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<BannerAdSlot> createState() => _BannerAdSlotState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BannerAdSlotState extends ConsumerState<BannerAdSlot> {
|
||||||
|
BannerAd? _ad;
|
||||||
|
bool _loaded = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
if (_ad != null) return;
|
||||||
|
if (ref.read(adsRemovedProvider)) return;
|
||||||
|
final ad = ref.read(adServiceProvider).createBanner(
|
||||||
|
listener: BannerAdListener(
|
||||||
|
onAdLoaded: (_) {
|
||||||
|
if (mounted) setState(() => _loaded = true);
|
||||||
|
},
|
||||||
|
onAdFailedToLoad: (ad, _) => ad.dispose(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (ad == null) return;
|
||||||
|
_ad = ad;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_ad?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ad = _ad;
|
||||||
|
if (ad == null || !_loaded || ref.watch(adsRemovedProvider)) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return SizedBox(
|
||||||
|
width: ad.size.width.toDouble(),
|
||||||
|
height: ad.size.height.toDouble(),
|
||||||
|
child: AdWidget(ad: ad),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user