Files
ReVoltVPN/lib/logic/ad_manager.dart
T
2026-08-02 00:56:55 +03:00

146 lines
4.0 KiB
Dart

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<bool>? _loadCompleter;
// Google-provided test ad unit
static String get _adUnitId => AppConfig.adUnitId;
AdManager() {
if (adsEnabled) preloadAd();
}
Future<bool> preloadAd() async {
if (!adsEnabled) return true;
if (_isAdLoaded) return true;
if (_isAdLoading) return _loadCompleter?.future ?? Future.value(false);
_isAdLoading = true;
_loadCompleter = Completer<bool>();
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<bool> 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<bool> rewardCompleter = Completer<bool>();
_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();
}
}