import 'package:flutter/material.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'dart:io' show Platform; import 'glassmorphism_card.dart'; import '../main.dart' show enableAdMob; /// 구글 네이티브 광고 위젯 (AdMob NativeAd) /// SRP에 따라 광고 전용 위젯으로 분리 class NativeAdWidget extends StatefulWidget { const NativeAdWidget({Key? key}) : super(key: key); @override State createState() => _NativeAdWidgetState(); } class _NativeAdWidgetState extends State { NativeAd? _nativeAd; bool _isLoaded = false; String? _error; bool _isAdLoading = false; // 광고 로드 중복 방지 플래그 @override void initState() { super.initState(); // initState에서는 Theme.of(context)와 같은 InheritedWidget에 의존하지 않음 } @override void didChangeDependencies() { super.didChangeDependencies(); // 위젯이 완전히 초기화된 후 광고 로드 if (!_isAdLoading && !kIsWeb) { _loadAd(); _isAdLoading = true; // 중복 로드 방지 } } /// 네이티브 광고 로드 함수 void _loadAd() { // 웹 또는 Android/iOS가 아닌 경우 광고 로드 방지 if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) { return; } _nativeAd = NativeAd( adUnitId: _testAdUnitId(), // 실제 배포 시 교체 필요 factoryId: 'listTile', // Android/iOS 모두 동일하게 맞춰야 함 request: const AdRequest(), listener: NativeAdListener( onAdLoaded: (ad) { setState(() { _isLoaded = true; }); }, onAdFailedToLoad: (ad, error) { ad.dispose(); setState(() { _error = error.message; }); }, ), )..load(); } /// 테스트 광고 단위 ID 반환 함수 /// Theme.of(context)를 사용하지 않고 Platform 클래스 직접 사용 String _testAdUnitId() { if (Platform.isAndroid) { // Android 테스트 네이티브 광고 ID return 'ca-app-pub-3940256099942544/2247696110'; } else if (Platform.isIOS) { // iOS 테스트 네이티브 광고 ID return 'ca-app-pub-3940256099942544/3986624511'; } return ''; } @override void dispose() { _nativeAd?.dispose(); super.dispose(); } /// 웹용 광고 플레이스홀더 위젯 Widget _buildWebPlaceholder() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: GlassmorphismCard( borderRadius: 16, blur: 10, opacity: 0.1, child: Container( height: 80, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ Container( width: 64, height: 64, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(8), ), child: const Center( child: Icon( Icons.ad_units, color: Colors.grey, size: 32, ), ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Container( height: 14, width: 120, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 8), Container( height: 10, width: 180, decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(4), ), ), ], ), ), Container( width: 60, height: 24, decoration: BoxDecoration( color: Colors.blue[100], borderRadius: BorderRadius.circular(12), ), child: const Center( child: Text( '광고영역', style: TextStyle( fontSize: 12, color: Colors.blue, fontWeight: FontWeight.bold, ), ), ), ), ], ), ), ), ); } @override Widget build(BuildContext context) { // AdMob이 비활성화된 경우 빈 컨테이너 반환 if (!enableAdMob) { return const SizedBox.shrink(); } // 웹 환경인 경우 플레이스홀더 표시 if (kIsWeb) { return _buildWebPlaceholder(); } // Android/iOS가 아닌 경우 광고 위젯을 렌더링하지 않음 if (!(Platform.isAndroid || Platform.isIOS)) { return const SizedBox.shrink(); } if (_error != null) { // 광고 로드 실패 시 빈 공간 반환 return const SizedBox.shrink(); } if (!_isLoaded) { // 광고 로딩 중 로딩 인디케이터 표시 return const Padding( padding: EdgeInsets.symmetric(vertical: 12), child: Center(child: CircularProgressIndicator()), ); } // 광고 정상 노출 return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: GlassmorphismCard( borderRadius: 16, blur: 10, opacity: 0.1, child: SizedBox( height: 80, // 네이티브 광고 높이 조정 child: AdWidget(ad: _nativeAd!), ), ), ); } }