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 'dart:async'; // Glass 제거: Material 3 Card 사용 import '../main.dart' show enableAdMob; import '../theme/ui_constants.dart'; /// 구글 네이티브 광고 위젯 (AdMob NativeAd) /// SRP에 따라 광고 전용 위젯으로 분리 class NativeAdWidget extends StatefulWidget { final bool useOuterPadding; // true이면 외부에서 페이지 패딩을 제공 const NativeAdWidget({Key? key, this.useOuterPadding = false}) : super(key: key); @override State createState() => _NativeAdWidgetState(); } class _NativeAdWidgetState extends State { NativeAd? _nativeAd; bool _isLoaded = false; String? _error; bool _isAdLoading = false; // 광고 로드 중복 방지 플래그 Timer? _refreshTimer; // 주기적 리프레시 타이머 @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; } try { // 기존 광고 해제 및 상태 초기화 _refreshTimer?.cancel(); _nativeAd?.dispose(); _error = null; _isLoaded = false; _nativeAd = NativeAd( adUnitId: _testAdUnitId(), // 실제 광고 단위 ID // 네이티브 템플릿을 사용하면 NativeAdFactory 등록 없이도 동작합니다. nativeTemplateStyle: NativeTemplateStyle( templateType: TemplateType.small, mainBackgroundColor: const Color(0x00000000), cornerRadius: 12, ), request: const AdRequest(), listener: NativeAdListener( onAdLoaded: (ad) { setState(() { _isLoaded = true; }); _scheduleRefresh(); }, onAdFailedToLoad: (ad, error) { ad.dispose(); setState(() { _error = error.message; }); // 실패 시에도 일정 시간 후 재시도 _scheduleRefresh(); }, ), )..load(); } catch (e) { // 템플릿 미지원 등 예외 시 광고를 비활성화하고 크래시 방지 setState(() { _error = e.toString(); }); _scheduleRefresh(); } } /// 30초 후 새 광고로 교체 void _scheduleRefresh() { _refreshTimer?.cancel(); _refreshTimer = Timer(const Duration(seconds: 30), _refreshAd); } void _refreshAd() { if (!mounted) return; // 다음 로드를 위해 상태 초기화 후 새 광고 요청 try { _nativeAd?.dispose(); } catch (_) {} setState(() { _nativeAd = null; _isLoaded = false; _error = null; }); _loadAd(); } /// 광고 단위 ID 반환 함수 /// Theme.of(context)를 사용하지 않고 Platform 클래스 직접 사용 String _testAdUnitId() { if (Platform.isAndroid) { // Android 네이티브 광고 ID return 'ca-app-pub-6691216385521068/4512709971'; } else if (Platform.isIOS) { // iOS 네이티브 광고 ID return 'ca-app-pub-6691216385521068/4512709971'; } return ''; } @override void dispose() { _nativeAd?.dispose(); _refreshTimer?.cancel(); super.dispose(); } /// 웹용 광고 플레이스홀더 위젯 Widget _buildWebPlaceholder() { return Padding( padding: EdgeInsets.symmetric( horizontal: widget.useOuterPadding ? 0 : UIConstants.pageHorizontalPadding, vertical: UIConstants.adVerticalPadding, ), child: Card( elevation: 1, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.zero, ), child: Container( height: UIConstants.adCardHeight, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ Container( width: 64, height: 64, decoration: BoxDecoration( color: Theme.of(context) .colorScheme .surfaceContainerHighest .withValues(alpha: 0.5), borderRadius: BorderRadius.zero, ), child: Center( child: Icon( Icons.ad_units, color: Theme.of(context).colorScheme.onSurfaceVariant, size: 32, ), ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Container( height: 14, width: 120, decoration: BoxDecoration( color: Theme.of(context) .colorScheme .surfaceContainerHighest .withValues(alpha: 0.5), borderRadius: BorderRadius.zero, ), ), const SizedBox(height: 8), Container( height: 10, width: 180, decoration: BoxDecoration( color: Theme.of(context) .colorScheme .surfaceContainerHighest .withValues(alpha: 0.4), borderRadius: BorderRadius.zero, ), ), ], ), ), Container( width: 60, height: 24, decoration: BoxDecoration( color: Theme.of(context) .colorScheme .primary .withValues(alpha: 0.15), borderRadius: BorderRadius.zero, ), child: Center( child: Text( 'ads', style: TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.primary, 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 _buildWebPlaceholder(); } if (!_isLoaded) { // 로딩 중에도 실제 광고와 동일한 높이의 스켈레톤을 유지 return _buildWebPlaceholder(); } // 광고 정상 노출 return Padding( padding: EdgeInsets.symmetric( horizontal: widget.useOuterPadding ? 0 : UIConstants.pageHorizontalPadding, vertical: UIConstants.adVerticalPadding, ), child: Card( elevation: 1, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.zero, ), child: SizedBox( height: UIConstants.adCardHeight, child: AdWidget(ad: _nativeAd!), ), ), ); } }