// 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 '../../services/ad_service.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. /// /// If the slot is built before the AdMob SDK finished initializing (common on /// a cold start while the consent flow is still running), the first create /// returns null; it then retries once the [AdService.isReady] notifier flips. class BannerAdSlot extends ConsumerStatefulWidget { const BannerAdSlot({super.key}); @override ConsumerState createState() => _BannerAdSlotState(); } class _BannerAdSlotState extends ConsumerState { BannerAd? _ad; bool _loaded = false; AdService? _adService; @override void didChangeDependencies() { super.didChangeDependencies(); if (_adService == null) { _adService = ref.read(adServiceProvider); _adService!.isReady.addListener(_tryCreate); } _tryCreate(); } void _tryCreate() { if (_ad != null || !mounted) return; if (ref.read(adsRemovedProvider)) return; final ad = _adService!.createBanner( listener: BannerAdListener( onAdLoaded: (_) { if (mounted) setState(() => _loaded = true); }, onAdFailedToLoad: (ad, _) => ad.dispose(), ), ); if (ad == null) return; // SDK not ready yet; isReady will retry. _ad = ad; } @override void dispose() { _adService?.isReady.removeListener(_tryCreate); _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), ); } }