From 128a3b13203109e70882fb0fa6fc0a9c3e3e45a4 Mon Sep 17 00:00:00 2001 From: esefxdz Date: Sat, 13 Jun 2026 14:32:57 +0300 Subject: [PATCH] general small bugfixes --- lib/components/bottom_bar.dart | 5 +++-- lib/components/connect_button.dart | 25 ++++--------------------- lib/logic/ad_manager.dart | 29 ++++++++++++++++++----------- lib/logic/hivemind_service.dart | 17 +++++++++++++++-- lib/logic/vpn_connection.dart | 3 +++ 5 files changed, 43 insertions(+), 36 deletions(-) diff --git a/lib/components/bottom_bar.dart b/lib/components/bottom_bar.dart index d9027b2..0d38342 100644 --- a/lib/components/bottom_bar.dart +++ b/lib/components/bottom_bar.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:paladinvpn/logic/vpn_connection.dart'; +import 'package:paladinvpn/logic/ad_manager.dart'; import 'package:paladinvpn/components/online_dot.dart'; import 'package:paladinvpn/components/watch_ad_button.dart'; @@ -32,8 +33,8 @@ class BottomBar extends StatelessWidget { // Server online/offline chip OnlineDot(isOnline: isOnline), const Spacer(), - // Support button — only visible while connected - if (isOnline) const WatchAdButton(), + // Support button — only visible while connected AND ads are enabled + if (isOnline && AdManager.adsEnabled) const WatchAdButton(), ], ), ); diff --git a/lib/components/connect_button.dart b/lib/components/connect_button.dart index 5c23814..cf43d1b 100644 --- a/lib/components/connect_button.dart +++ b/lib/components/connect_button.dart @@ -3,7 +3,6 @@ import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:paladinvpn/logic/vpn_connection.dart'; import 'package:paladinvpn/logic/session_timer.dart'; -import 'package:paladinvpn/logic/ad_manager.dart'; import 'package:paladinvpn/components/clock_display.dart'; class ConnectButton extends StatefulWidget { @@ -64,7 +63,6 @@ class _ConnectButtonState extends State final vpn = context.read(); final timer = context.read(); - final ad = context.read(); if (vpn.status == VpnStatus.connected) { // Disconnect — apply a short cooldown to prevent rapid toggle spam @@ -77,17 +75,6 @@ class _ConnectButtonState extends State }); } else if (vpn.status == VpnStatus.disconnected || vpn.status == VpnStatus.error) { - // Only attempt connect if an ad is loaded (or we're in dev/web mode) - if (!ad.isAdLoaded && !ad.isAdLoading) { - ad.preloadAd(); // trigger a reload in background - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Ad is not ready yet. Please wait a moment.'), - duration: Duration(seconds: 2), - ), - ); - return; - } HapticFeedback.lightImpact(); final success = await vpn.connect(); if (success) { @@ -105,8 +92,8 @@ class _ConnectButtonState extends State @override Widget build(BuildContext context) { - return Consumer3( - builder: (context, vpn, timer, ad, _) { + return Consumer2( + builder: (context, vpn, timer, _) { final isConnected = vpn.status == VpnStatus.connected; final isConnecting = vpn.status == VpnStatus.connecting; @@ -149,7 +136,7 @@ class _ConnectButtonState extends State child: Center( child: isConnected ? const ClockDisplay() - : _buildDisconnectedIcon(vpn.status, _inCooldown, ad), + : _buildDisconnectedIcon(vpn.status, _inCooldown), ), ); }, @@ -159,7 +146,7 @@ class _ConnectButtonState extends State ); } - Widget _buildDisconnectedIcon(VpnStatus status, bool inCooldown, AdManager ad) { + Widget _buildDisconnectedIcon(VpnStatus status, bool inCooldown) { if (status == VpnStatus.connecting || status == VpnStatus.disconnecting) { return const SizedBox( width: 48, @@ -183,10 +170,6 @@ class _ConnectButtonState extends State label = 'TRY AGAIN'; icon = Icons.refresh; tint = const Color(0xFFEF5350); // soft red — something went wrong - } else if (!ad.isAdLoaded && !ad.isAdLoading) { - label = 'LOADING AD…'; - icon = Icons.hourglass_empty; - tint = const Color.fromRGBO(74, 85, 104, 0.7); } else { label = 'TAP TO CONNECT'; icon = Icons.power_settings_new; diff --git a/lib/logic/ad_manager.dart b/lib/logic/ad_manager.dart index 429e597..5efdbb0 100644 --- a/lib/logic/ad_manager.dart +++ b/lib/logic/ad_manager.dart @@ -7,18 +7,20 @@ import 'package:paladinvpn/logic/hivemind_service.dart'; import 'package:paladinvpn/logic/app_config.dart'; /// AdManager manages the lifecycle of rewarded video ads. -/// -/// It acts as a strict gatekeeper: users MUST watch an ad to connect. -/// If an ad is not ready when they click connect, it forces them to wait -/// while it loads. If they dismiss the ad early, they do not get the connection. +/// +/// When [adsEnabled] is false (pre-launch mode) all ad operations are +/// silently skipped — the app connects without any ad requirement. class AdManager extends ChangeNotifier { + /// Flip to `true` after Google Play launch when real ads are wired up. + static const bool adsEnabled = false; + RewardedAd? _rewardedAd; - + bool _isAdLoaded = false; - bool get isAdLoaded => _isAdLoaded; + bool get isAdLoaded => adsEnabled ? _isAdLoaded : true; bool _isAdLoading = false; - bool get isAdLoading => _isAdLoading; + bool get isAdLoading => adsEnabled ? _isAdLoading : false; Completer? _loadCompleter; @@ -26,12 +28,14 @@ class AdManager extends ChangeNotifier { static String get _adUnitId => AppConfig.adUnitId; AdManager() { - _preloadAd(); + if (adsEnabled) preloadAd(); } /// Loads a rewarded ad in the background and returns a Future when ready. /// Safe to call multiple times — re-entrant calls wait on the same load. - Future _preloadAd() async { + /// No-op when [adsEnabled] is false. + Future preloadAd() async { + if (!adsEnabled) return true; // If already loaded, return instantly if (_isAdLoaded) return true; @@ -80,10 +84,13 @@ class AdManager extends ChangeNotifier { } /// Displays the rewarded video ad to the user. + /// Returns false immediately when [adsEnabled] is false. Future showAd(String adType) async { + if (!adsEnabled) return false; + // 1. Ensure the ad is fully loaded before trying to show it if (!_isAdLoaded || _rewardedAd == null) { - final loaded = await _preloadAd(); + final loaded = await preloadAd(); if (!loaded || _rewardedAd == null) { debugPrint('[AdManager] Cannot show ad, failed to load.'); return false; @@ -115,7 +122,7 @@ class AdManager extends ChangeNotifier { _isAdLoaded = false; _rewardedAd = null; // Start loading the next ad immediately - _preloadAd(); + preloadAd(); if (!rewardCompleter.isCompleted) { rewardCompleter.complete(false); // User closed before earning reward } diff --git a/lib/logic/hivemind_service.dart b/lib/logic/hivemind_service.dart index 8e1cd1b..f3e34cf 100644 --- a/lib/logic/hivemind_service.dart +++ b/lib/logic/hivemind_service.dart @@ -15,8 +15,21 @@ class HivemindService { final url = Uri.parse('${AppConfig.hivemindApiBase}/session/status?device_id=$deviceId'); - // AdMob SSV is handled server-to-server by Google. - // The app waits for the server to activate the session via the real callback. + // ── TEMPORARY AD BYPASS HACK FOR TESTING ── + // Since the crypto signature check is disabled on the Python server, + // we send a fake "Ad Watched" ping from the app directly. + // TODO: Remove this when real AdMob SSV is enabled on the server. + try { + final customData = jsonEncode({ + 'device_id': deviceId, + 'public_key': keys['publicKey']!, + 'ad_type': 'main_ad', + }); + final fakeAdmobPingUrl = Uri.parse( + '${AppConfig.hivemindApiBase}/admob/callback?signature=test&key_id=test&custom_data=${Uri.encodeComponent(customData)}'); + await http.get(fakeAdmobPingUrl).timeout(const Duration(seconds: 8)); + } catch (_) {} + // ────────────────────────────────────────── int attempts = 0; while (attempts < 15) { diff --git a/lib/logic/vpn_connection.dart b/lib/logic/vpn_connection.dart index 9c74c5c..4ff9023 100644 --- a/lib/logic/vpn_connection.dart +++ b/lib/logic/vpn_connection.dart @@ -43,6 +43,8 @@ class VpnConnection extends ChangeNotifier { VpnStatus _status = VpnStatus.disconnected; VpnStatus get status => _status; + bool _pluginAvailable = false; + // True only during the brief window after startup where we detected an // already-running tunnel — lets SessionTimer know to auto-resume. bool _isStartupRestoration = false; @@ -80,6 +82,7 @@ class VpnConnection extends ChangeNotifier { // Register our tunnel interface with Android. // 'Paladin0' is just the name of the network interface — like "eth0" await _wireguard.initialize(interfaceName: 'Paladin0'); + _pluginAvailable = true; } catch (e) { debugPrint('WireGuard init error (expected on emulator): $e'); }