import 'dart:async'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'dart:io' show Platform; import '../utils/logger.dart'; /// 전면 광고(Interstitial Ad) 서비스 /// lunchpick 프로젝트의 AdService 패턴을 참조하여 구현 class AdService { InterstitialAd? _interstitialAd; Completer? _loadingCompleter; /// 모바일 플랫폼 여부 확인 bool get _isMobilePlatform { if (kIsWeb) return false; return Platform.isAndroid || Platform.isIOS; } /// 전면 광고 Unit ID 반환 String get _interstitialAdUnitId { if (Platform.isAndroid) { return 'ca-app-pub-6691216385521068/5281562472'; } else if (Platform.isIOS) { // iOS 테스트 광고 ID return 'ca-app-pub-3940256099942544/1033173712'; } return ''; } /// 광고를 로드하고 표시한 뒤 완료 여부를 반환 /// true: 광고 시청 완료 또는 미지원 플랫폼 /// false: 광고 로드/표시 실패 Future showInterstitialAd(BuildContext context) async { if (!_isMobilePlatform) return true; Log.i('광고 표시 시작'); // 1. 로딩 오버레이 표시 (앱이 foreground 상태 유지) final closeLoading = _showLoadingOverlay(context); // 2. 몰입형 모드 진입 await _enterImmersiveMode(); // 3. 광고 로드 final loaded = await _ensureAdLoaded(); // 4. 로딩 오버레이 닫기 closeLoading(); if (!loaded) { Log.w('광고 로드 실패, 건너뜀'); await _restoreSystemUi(); return false; } final ad = _interstitialAd; if (ad == null) { Log.w('광고 인스턴스 없음, 건너뜀'); await _restoreSystemUi(); return false; } // 현재 광고를 null로 설정 (다음 광고 미리로드) _interstitialAd = null; final completer = Completer(); ad.fullScreenContentCallback = FullScreenContentCallback( onAdShowedFullScreenContent: (ad) { Log.i('전면 광고 표시됨 (콜백)'); }, onAdDismissedFullScreenContent: (ad) { Log.i('전면 광고 닫힘 (콜백)'); ad.dispose(); _preload(); unawaited(_restoreSystemUi()); if (!completer.isCompleted) { completer.complete(true); } }, onAdFailedToShowFullScreenContent: (ad, error) { Log.e('전면 광고 표시 실패 (콜백)', error); ad.dispose(); _preload(); unawaited(_restoreSystemUi()); if (!completer.isCompleted) { completer.complete(false); } }, ); // 전체 화면으로 표시하도록 immersive 모드 설정 ad.setImmersiveMode(true); Log.i('ad.show() 호출 직전'); try { ad.show(); Log.i('ad.show() 호출 완료'); } catch (e) { Log.e('광고 show() 호출 실패', e); unawaited(_restoreSystemUi()); if (!completer.isCompleted) { completer.complete(false); } return false; } // 타임아웃 설정 (15초 후 자동 건너뜀) return completer.future.timeout( const Duration(seconds: 15), onTimeout: () { Log.w('광고 표시 타임아웃, 건너뜀'); unawaited(_restoreSystemUi()); return false; }, ); } /// 로딩 오버레이 표시 (앱이 foreground 상태 유지) VoidCallback _showLoadingOverlay(BuildContext context) { final navigator = Navigator.of(context, rootNavigator: true); showDialog( context: context, barrierDismissible: false, barrierColor: Colors.black.withValues(alpha: 0.35), builder: (_) => const Center(child: CircularProgressIndicator()), ); return () { if (navigator.mounted && navigator.canPop()) { navigator.pop(); } }; } /// 몰입형 모드 진입 (상하단 시스템 UI 숨김) Future _enterImmersiveMode() async { try { await SystemChrome.setEnabledSystemUIMode( SystemUiMode.immersiveSticky, overlays: [], ); } catch (_) {} } /// UI 복구 (main.dart의 설정과 동일하게 immersiveSticky 유지) Future _restoreSystemUi() async { try { await SystemChrome.setEnabledSystemUIMode( SystemUiMode.immersiveSticky, overlays: [SystemUiOverlay.top], // 상태바만 유지 ); } catch (_) {} } /// 광고 로드 보장 (이미 로드된 경우 즉시 반환) Future _ensureAdLoaded() async { if (_interstitialAd != null) return true; if (_loadingCompleter != null) { return _loadingCompleter!.future; } final completer = Completer(); _loadingCompleter = completer; Log.i('전면 광고 로드 시작: $_interstitialAdUnitId'); InterstitialAd.load( adUnitId: _interstitialAdUnitId, request: const AdRequest(), adLoadCallback: InterstitialAdLoadCallback( onAdLoaded: (ad) { Log.i('전면 광고 로드 성공'); _interstitialAd = ad; completer.complete(true); _loadingCompleter = null; }, onAdFailedToLoad: (error) { Log.e('전면 광고 로드 실패', error); completer.complete(false); _loadingCompleter = null; }, ), ); return completer.future; } /// 다음 광고 미리로드 void _preload() { if (_interstitialAd != null || _loadingCompleter != null) return; _ensureAdLoaded(); } }