import 'dart:async'; import 'dart:convert'; import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:revoltvpn/logic/crypto_service.dart'; import 'package:revoltvpn/logic/hivemind_service.dart'; import 'package:revoltvpn/logic/app_config.dart'; class AdManager extends ChangeNotifier { static const bool adsEnabled = false; RewardedAd? _rewardedAd; bool _isAdLoaded = false; bool get isAdLoaded => adsEnabled ? _isAdLoaded : true; bool _isAdLoading = false; bool get isAdLoading => adsEnabled ? _isAdLoading : false; Completer? _loadCompleter; // Google-provided test ad unit static String get _adUnitId => AppConfig.adUnitId; AdManager() { if (adsEnabled) preloadAd(); } Future preloadAd() async { if (!adsEnabled) return true; if (_isAdLoaded) return true; if (_isAdLoading) return _loadCompleter?.future ?? Future.value(false); _isAdLoading = true; _loadCompleter = Completer(); notifyListeners(); if (kIsWeb) { await Future.delayed(const Duration(milliseconds: 500)); _isAdLoaded = true; _isAdLoading = false; notifyListeners(); _loadCompleter?.complete(true); return true; } RewardedAd.load( adUnitId: _adUnitId, request: const AdRequest(), rewardedAdLoadCallback: RewardedAdLoadCallback( onAdLoaded: (ad) { _rewardedAd = ad; _isAdLoaded = true; _isAdLoading = false; notifyListeners(); if (!(_loadCompleter?.isCompleted ?? true)) { _loadCompleter?.complete(true); } }, onAdFailedToLoad: (error) { debugPrint('Rewarded ad failed to load: ${error.message}'); _isAdLoaded = false; _isAdLoading = false; notifyListeners(); if (!(_loadCompleter?.isCompleted ?? true)) { _loadCompleter?.complete(false); } }, ), ); return _loadCompleter!.future; } Future showAd(String adType) async { if (!adsEnabled) return false; if (!_isAdLoaded || _rewardedAd == null) { final loaded = await preloadAd(); if (!loaded || _rewardedAd == null) { debugPrint('[AdManager] Cannot show ad, failed to load.'); return false; } } final deviceId = await CryptoService.getDeviceId(); final nonce = '${Random().nextInt(0x7FFFFFFF)}-${DateTime.now().millisecondsSinceEpoch}'; HivemindService.setExpectedNonce(nonce); debugPrint('[AdManager] Ad nonce: $nonce'); final ssvOptions = ServerSideVerificationOptions( customData: jsonEncode({ 'device_id': deviceId, 'ad_type': adType, 'nonce': nonce, }), ); Completer rewardCompleter = Completer(); _rewardedAd!.fullScreenContentCallback = FullScreenContentCallback( onAdShowedFullScreenContent: (ad) => debugPrint('[AdManager] Ad showing.'), onAdDismissedFullScreenContent: (ad) { debugPrint('[AdManager] Ad dismissed.'); ad.dispose(); _isAdLoaded = false; _rewardedAd = null; preloadAd(); if (!rewardCompleter.isCompleted) { rewardCompleter.complete(false); } }, onAdFailedToShowFullScreenContent: (ad, error) { debugPrint('[AdManager] Ad failed to show: $error'); ad.dispose(); _isAdLoaded = false; _rewardedAd = null; if (!rewardCompleter.isCompleted) { rewardCompleter.complete(false); } }, ); // Show with SSV options _rewardedAd!.setServerSideOptions(ssvOptions); await _rewardedAd!.show( onUserEarnedReward: (AdWithoutView ad, RewardItem reward) { debugPrint('[AdManager] Reward earned: ${reward.amount} ${reward.type}'); if (!rewardCompleter.isCompleted) { rewardCompleter.complete(true); } }, ); return rewardCompleter.future; } @override void dispose() { _rewardedAd?.dispose(); super.dispose(); } }