polish: strip verbose comments, simplify codebase
This commit is contained in:
1 parent
a32252c56d
commit
35bfeb61c4
16 files changed
+25
-146
No files matched your search
@@ -80,3 +80,4 @@ technical_debt_*.md
|
|||||||
server_full_picture_2.txt
|
server_full_picture_2.txt
|
||||||
hivemind_backup.py
|
hivemind_backup.py
|
||||||
UI_POSITIONING.md
|
UI_POSITIONING.md
|
||||||
|
SERVER_SETUP.md
|
||||||
@@ -3,13 +3,6 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:revoltvpn/logic/app_colors.dart';
|
import 'package:revoltvpn/logic/app_colors.dart';
|
||||||
import 'package:revoltvpn/logic/session_timer.dart';
|
import 'package:revoltvpn/logic/session_timer.dart';
|
||||||
|
|
||||||
/// ClockDisplay renders inside the ConnectButton circle when the VPN is active.
|
|
||||||
///
|
|
||||||
/// Shows a circular quota-progress arc, a glowing countdown timer, and the
|
|
||||||
/// current transfer speed — all sized to fit within the 260px button.
|
|
||||||
///
|
|
||||||
/// Before the first server sync arrives, a subtle "Syncing…" placeholder
|
|
||||||
/// is shown instead of a misleading 00:00:00.
|
|
||||||
class ClockDisplay extends StatelessWidget {
|
class ClockDisplay extends StatelessWidget {
|
||||||
const ClockDisplay({super.key});
|
const ClockDisplay({super.key});
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,6 @@ import 'package:revoltvpn/logic/vpn_connection.dart';
|
|||||||
import 'package:revoltvpn/logic/session_timer.dart';
|
import 'package:revoltvpn/logic/session_timer.dart';
|
||||||
import 'package:revoltvpn/logic/ad_manager.dart';
|
import 'package:revoltvpn/logic/ad_manager.dart';
|
||||||
|
|
||||||
/// ConnectButton — circle with brand.png + pulsing glow when connected.
|
|
||||||
///
|
|
||||||
/// Rules:
|
|
||||||
/// disconnected → connect (ad → config → tunnel)
|
|
||||||
/// connecting → disconnect (cancel)
|
|
||||||
/// connected → disconnect
|
|
||||||
/// error → connect (retry)
|
|
||||||
/// disconnecting → ignore
|
|
||||||
class ConnectButton extends StatefulWidget {
|
class ConnectButton extends StatefulWidget {
|
||||||
const ConnectButton({super.key});
|
const ConnectButton({super.key});
|
||||||
|
|
||||||
@@ -59,7 +51,7 @@ class _ConnectButtonState extends State<ConnectButton>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleTap() async {
|
Future<void> _handleTap() async {
|
||||||
// 1-second cooldown to prevent spam
|
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
if (now.difference(_lastTap).inMilliseconds < 1000) return;
|
if (now.difference(_lastTap).inMilliseconds < 1000) return;
|
||||||
_lastTap = now;
|
_lastTap = now;
|
||||||
@@ -70,7 +62,7 @@ class _ConnectButtonState extends State<ConnectButton>
|
|||||||
final timer = context.read<SessionTimer>();
|
final timer = context.read<SessionTimer>();
|
||||||
final ad = context.read<AdManager>();
|
final ad = context.read<AdManager>();
|
||||||
|
|
||||||
// ── Disconnect if connected or connecting ───────────────────────────
|
|
||||||
if (vpn.status == VpnStatus.connected || vpn.status == VpnStatus.connecting) {
|
if (vpn.status == VpnStatus.connected || vpn.status == VpnStatus.connecting) {
|
||||||
_busy = false;
|
_busy = false;
|
||||||
timer.stop(); // MUST stop timer BEFORE disconnect — prevents auto-reconnect
|
timer.stop(); // MUST stop timer BEFORE disconnect — prevents auto-reconnect
|
||||||
@@ -78,10 +70,10 @@ class _ConnectButtonState extends State<ConnectButton>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Ignore if disconnecting ─────────────────────────────────────────
|
|
||||||
if (vpn.status == VpnStatus.disconnecting) return;
|
if (vpn.status == VpnStatus.disconnecting) return;
|
||||||
|
|
||||||
// ── Connect (or retry) ─────────────────────────────────────────────
|
|
||||||
if (_busy) return;
|
if (_busy) return;
|
||||||
|
|
||||||
setState(() => _busy = true);
|
setState(() => _busy = true);
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:revoltvpn/logic/app_colors.dart';
|
import 'package:revoltvpn/logic/app_colors.dart';
|
||||||
|
|
||||||
/// OnlineDot displays a glowing green dot when the server is healthy,
|
|
||||||
/// and a muted gray dot when disconnected or still syncing.
|
|
||||||
class OnlineDot extends StatelessWidget {
|
class OnlineDot extends StatelessWidget {
|
||||||
/// Whether the server has been confirmed reachable.
|
|
||||||
final bool isOnline;
|
final bool isOnline;
|
||||||
|
|
||||||
/// Optional custom label. Falls back to "Server Online" / "Disconnected".
|
|
||||||
final String? label;
|
final String? label;
|
||||||
|
|
||||||
const OnlineDot({
|
const OnlineDot({
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:revoltvpn/logic/app_colors.dart';
|
|||||||
class DiscordLinkButton extends StatelessWidget {
|
class DiscordLinkButton extends StatelessWidget {
|
||||||
const DiscordLinkButton({super.key});
|
const DiscordLinkButton({super.key});
|
||||||
|
|
||||||
/// Public entry point for the sidebar or any other widget.
|
|
||||||
static Future<void> launchDiscord() async {
|
static Future<void> launchDiscord() async {
|
||||||
final Uri url = Uri.parse('https://discord.gg/revoltvpn');
|
final Uri url = Uri.parse('https://discord.gg/revoltvpn');
|
||||||
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
|
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
|
||||||
|
|||||||
@@ -4,17 +4,9 @@ import 'package:google_mobile_ads/google_mobile_ads.dart';
|
|||||||
import 'package:revoltvpn/logic/app_colors.dart';
|
import 'package:revoltvpn/logic/app_colors.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
/// GdprButton lets the user review or change their ad consent choices.
|
|
||||||
///
|
|
||||||
/// When tapped, it tries to show Google's UMP consent form so the user
|
|
||||||
/// can adjust their preferences (personalized vs non-personalized ads).
|
|
||||||
/// If the form isn't available (e.g. user is outside the EEA), it falls
|
|
||||||
/// back to opening the privacy policy instead.
|
|
||||||
class GdprButton extends StatelessWidget {
|
class GdprButton extends StatelessWidget {
|
||||||
const GdprButton({super.key});
|
const GdprButton({super.key});
|
||||||
|
|
||||||
/// Public entry point so the sidebar (or any other widget) can trigger
|
|
||||||
/// the consent flow without embedding an IconButton.
|
|
||||||
static Future<void> showConsentForm() async {
|
static Future<void> showConsentForm() async {
|
||||||
try {
|
try {
|
||||||
// If the form is already available, show it directly — no need to
|
// If the form is already available, show it directly — no need to
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:revoltvpn/logic/app_colors.dart';
|
|||||||
class InfoButton extends StatelessWidget {
|
class InfoButton extends StatelessWidget {
|
||||||
const InfoButton({super.key});
|
const InfoButton({super.key});
|
||||||
|
|
||||||
/// Public entry point for the sidebar or any other widget.
|
|
||||||
static Future<void> launchGitHub() async {
|
static Future<void> launchGitHub() async {
|
||||||
final Uri url = Uri.parse('https://github.com/esefxdz/ReVoltVPN');
|
final Uri url = Uri.parse('https://github.com/esefxdz/ReVoltVPN');
|
||||||
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
|
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:revoltvpn/logic/app_colors.dart';
|
|||||||
class PrivacyPolicyButton extends StatelessWidget {
|
class PrivacyPolicyButton extends StatelessWidget {
|
||||||
const PrivacyPolicyButton({super.key});
|
const PrivacyPolicyButton({super.key});
|
||||||
|
|
||||||
/// Public entry point for the sidebar or any other widget.
|
|
||||||
static Future<void> launchPrivacyPolicy() async {
|
static Future<void> launchPrivacyPolicy() async {
|
||||||
final Uri url = Uri.parse('https://github.com/esefxdz/ReVoltVPN/blob/main/PRIVACY_POLICY.md');
|
final Uri url = Uri.parse('https://github.com/esefxdz/ReVoltVPN/blob/main/PRIVACY_POLICY.md');
|
||||||
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
|
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import 'package:revoltvpn/components/sidebar_contents/privacy_policy_button.dart
|
|||||||
import 'package:revoltvpn/components/sidebar_contents/info_button.dart';
|
import 'package:revoltvpn/components/sidebar_contents/info_button.dart';
|
||||||
import 'package:revoltvpn/components/sidebar_contents/discord_link_button.dart';
|
import 'package:revoltvpn/components/sidebar_contents/discord_link_button.dart';
|
||||||
|
|
||||||
/// Right-edge drawer. Four fully-tappable rows — tap anywhere on a row
|
|
||||||
/// to trigger its action and close the drawer.
|
|
||||||
class SidebarDrawer extends StatelessWidget {
|
class SidebarDrawer extends StatelessWidget {
|
||||||
const SidebarDrawer({super.key});
|
const SidebarDrawer({super.key});
|
||||||
|
|
||||||
@@ -19,7 +17,6 @@ class SidebarDrawer extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
// ── Header ──────────────────────────────────────────────────
|
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 20),
|
padding: const EdgeInsets.fromLTRB(20, 24, 20, 20),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
@@ -51,7 +48,6 @@ class SidebarDrawer extends StatelessWidget {
|
|||||||
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
// ── Ad Consent (fully tappable) ─────────────────────────────
|
|
||||||
_Row(
|
_Row(
|
||||||
icon: Icons.admin_panel_settings_outlined,
|
icon: Icons.admin_panel_settings_outlined,
|
||||||
label: 'Ad Consent',
|
label: 'Ad Consent',
|
||||||
@@ -61,7 +57,6 @@ class SidebarDrawer extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
// ── Privacy Policy (fully tappable) ─────────────────────────
|
|
||||||
_Row(
|
_Row(
|
||||||
icon: Icons.privacy_tip_outlined,
|
icon: Icons.privacy_tip_outlined,
|
||||||
label: 'Privacy Policy',
|
label: 'Privacy Policy',
|
||||||
@@ -71,7 +66,6 @@ class SidebarDrawer extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
// ── About (fully tappable) ──────────────────────────────────
|
|
||||||
_Row(
|
_Row(
|
||||||
icon: Icons.help_outline,
|
icon: Icons.help_outline,
|
||||||
label: 'About',
|
label: 'About',
|
||||||
@@ -81,7 +75,6 @@ class SidebarDrawer extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
// ── Discord (fully tappable) ────────────────────────────────
|
|
||||||
_Row(
|
_Row(
|
||||||
icon: Icons.discord,
|
icon: Icons.discord,
|
||||||
label: 'Discord',
|
label: 'Discord',
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:revoltvpn/logic/app_colors.dart';
|
import 'package:revoltvpn/logic/app_colors.dart';
|
||||||
import 'package:revoltvpn/logic/session_timer.dart';
|
import 'package:revoltvpn/logic/session_timer.dart';
|
||||||
|
|
||||||
/// SpeedBox — a small card showing current network speed or "Idle".
|
|
||||||
class SpeedBox extends StatelessWidget {
|
class SpeedBox extends StatelessWidget {
|
||||||
const SpeedBox({super.key});
|
const SpeedBox({super.key});
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import 'package:revoltvpn/logic/vpn_connection.dart';
|
|||||||
import 'package:revoltvpn/logic/session_timer.dart';
|
import 'package:revoltvpn/logic/session_timer.dart';
|
||||||
import 'package:revoltvpn/components/online_dot.dart';
|
import 'package:revoltvpn/components/online_dot.dart';
|
||||||
|
|
||||||
/// StatusBar — unified bottom card combining the Finland connection badge
|
|
||||||
/// and the server health indicator in one box.
|
|
||||||
class StatusBar extends StatelessWidget {
|
class StatusBar extends StatelessWidget {
|
||||||
const StatusBar({super.key});
|
const StatusBar({super.key});
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:revoltvpn/logic/app_colors.dart';
|
import 'package:revoltvpn/logic/app_colors.dart';
|
||||||
import 'package:revoltvpn/logic/session_timer.dart';
|
import 'package:revoltvpn/logic/session_timer.dart';
|
||||||
|
|
||||||
/// TimerBox — a rectangular card showing the session countdown.
|
|
||||||
class TimerBox extends StatelessWidget {
|
class TimerBox extends StatelessWidget {
|
||||||
const TimerBox({super.key});
|
const TimerBox({super.key});
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// app_colors.dart — Single source of truth for every color in ReVoltVPN
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Import this file and reference AppColors.* instead of hardcoding hex values
|
|
||||||
// or Color.fromRGBO() calls anywhere else in the app.
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
abstract final class AppColors {
|
abstract final class AppColors {
|
||||||
AppColors._();
|
AppColors._();
|
||||||
|
|
||||||
@@ -39,8 +32,7 @@ abstract final class AppColors {
|
|||||||
static const Color green50 = Color(0x8000E676); // 50 % opacity
|
static const Color green50 = Color(0x8000E676); // 50 % opacity
|
||||||
static const Color red = Color(0xFFEF5350);
|
static const Color red = Color(0xFFEF5350);
|
||||||
|
|
||||||
// ── Glass / Borders ────────────────────────────────────────────────────────
|
static const Color glassBg = Color(0x22FFFFFF);
|
||||||
static const Color glassBg = Color(0x22FFFFFF); // subtle glass background
|
static const Color glassBorder = Color(0x33FFFFFF);
|
||||||
static const Color glassBorder = Color(0x33FFFFFF); // muted translucent border
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,46 +7,31 @@ import 'package:revoltvpn/logic/vpn_connection.dart';
|
|||||||
import 'package:revoltvpn/logic/crypto_service.dart';
|
import 'package:revoltvpn/logic/crypto_service.dart';
|
||||||
import 'package:revoltvpn/components/notification.dart';
|
import 'package:revoltvpn/components/notification.dart';
|
||||||
|
|
||||||
/// SessionTimer is the client-side session authority for the VPN tunnel.
|
|
||||||
///
|
|
||||||
/// Polls the Hivemind API to sync remaining time, data quotas, speed, and
|
|
||||||
/// throttling status. Rather than guessing the session length optimistically,
|
|
||||||
/// it waits for the first successful sync before the local countdown begins.
|
|
||||||
class SessionTimer extends ChangeNotifier {
|
class SessionTimer extends ChangeNotifier {
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
int _tickCount = 0;
|
int _tickCount = 0;
|
||||||
|
|
||||||
final VpnConnection vpnConnection;
|
final VpnConnection vpnConnection;
|
||||||
|
|
||||||
// ── Session State ──────────────────────────────────────────────────────────
|
|
||||||
int _remainingSeconds = 0;
|
int _remainingSeconds = 0;
|
||||||
int _quotaBytes = 0;
|
int _quotaBytes = 0;
|
||||||
int _usedBytes = 0;
|
int _usedBytes = 0;
|
||||||
|
|
||||||
// ── Sync / Network Health ──────────────────────────────────────────────────
|
|
||||||
bool _hasSyncedOnce = false;
|
bool _hasSyncedOnce = false;
|
||||||
int _consecutiveFailures = 0;
|
int _consecutiveFailures = 0;
|
||||||
bool _isDisconnecting = false;
|
bool _isDisconnecting = false;
|
||||||
bool _userInitiatedStop = false; // true when user tapped disconnect
|
bool _userInitiatedStop = false;
|
||||||
|
|
||||||
/// Maximum consecutive sync failures before the local countdown is paused
|
|
||||||
/// to avoid showing a false "expired" state while the network is down.
|
|
||||||
static const int _maxConsecutiveFailures = 3;
|
static const int _maxConsecutiveFailures = 3;
|
||||||
|
|
||||||
/// How long we tolerate total silence from the server before forcibly
|
|
||||||
/// killing the VPN. Prevents the "orphaned tunnel" scenario where the
|
|
||||||
/// server is gone but the VPN stays up forever.
|
|
||||||
static const int _maxOfflineSeconds = 120;
|
static const int _maxOfflineSeconds = 120;
|
||||||
int _offlineSeconds = 0;
|
int _offlineSeconds = 0;
|
||||||
|
|
||||||
/// Seconds between server polls (reduced from 3 to ease server load).
|
|
||||||
static const int _pollIntervalSeconds = 5;
|
static const int _pollIntervalSeconds = 5;
|
||||||
|
|
||||||
// ── Speed State ────────────────────────────────────────────────────────────
|
|
||||||
int _lastUsedBytes = 0;
|
int _lastUsedBytes = 0;
|
||||||
double _currentSpeedKBps = 0.0; // kilobytes per second
|
double _currentSpeedKBps = 0.0;
|
||||||
|
|
||||||
// ── Notification dedup ─────────────────────────────────────────────────────
|
|
||||||
String _lastNotifTime = '';
|
String _lastNotifTime = '';
|
||||||
String _lastNotifSpeed = '';
|
String _lastNotifSpeed = '';
|
||||||
|
|
||||||
@@ -54,8 +39,6 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
vpnConnection.addListener(_onVpnConnectionChanged);
|
vpnConnection.addListener(_onVpnConnectionChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Public Getters ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
int get remaining => _remainingSeconds;
|
int get remaining => _remainingSeconds;
|
||||||
bool get isRunning => _timer != null && _timer!.isActive;
|
bool get isRunning => _timer != null && _timer!.isActive;
|
||||||
bool get isExpired => _remainingSeconds <= 0 && !isRunning;
|
bool get isExpired => _remainingSeconds <= 0 && !isRunning;
|
||||||
@@ -94,11 +77,11 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
return (_quotaBytes - _usedBytes) / _quotaBytes;
|
return (_quotaBytes - _usedBytes) / _quotaBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
// Lifecycle
|
||||||
|
|
||||||
/// Fires whenever VpnConnection state changes.
|
/// Fires whenever VpnConnection state changes.
|
||||||
void _onVpnConnectionChanged() {
|
void _onVpnConnectionChanged() {
|
||||||
// ── Startup restoration: app killed while VPN was running ──────────
|
|
||||||
if (vpnConnection.status == VpnStatus.connected &&
|
if (vpnConnection.status == VpnStatus.connected &&
|
||||||
!isRunning &&
|
!isRunning &&
|
||||||
vpnConnection.isStartupRestoration) {
|
vpnConnection.isStartupRestoration) {
|
||||||
@@ -106,7 +89,7 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Network-blip reconnect: VPN dropped and came back on its own ──
|
|
||||||
// The underlying VLESS tunnel may briefly flap. If we weren't
|
// The underlying VLESS tunnel may briefly flap. If we weren't
|
||||||
// explicitly stopped by the user, resume where we left off.
|
// explicitly stopped by the user, resume where we left off.
|
||||||
if (vpnConnection.status == VpnStatus.connected &&
|
if (vpnConnection.status == VpnStatus.connected &&
|
||||||
@@ -118,16 +101,12 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Auto-cleanup when the VPN actually goes down ──────────────────
|
|
||||||
if (vpnConnection.status == VpnStatus.disconnected && isRunning) {
|
if (vpnConnection.status == VpnStatus.disconnected && isRunning) {
|
||||||
_stopInternal();
|
_stopInternal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts the local countdown and begins server polling.
|
|
||||||
///
|
|
||||||
/// Values are initialised at 0 — the UI shows "Syncing…" until the first
|
|
||||||
/// successful [_syncWithHivemind] returns real data.
|
|
||||||
Future<void> start(String adType) async {
|
Future<void> start(String adType) async {
|
||||||
_remainingSeconds = 0;
|
_remainingSeconds = 0;
|
||||||
_quotaBytes = 0;
|
_quotaBytes = 0;
|
||||||
@@ -151,7 +130,7 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _tick(Timer t) {
|
void _tick(Timer t) {
|
||||||
// ── Local countdown ──────────────────────────────────────────────────
|
|
||||||
// Decrement the local clock as long as we're not in a network blackout.
|
// Decrement the local clock as long as we're not in a network blackout.
|
||||||
if (_hasSyncedOnce && _consecutiveFailures < _maxConsecutiveFailures) {
|
if (_hasSyncedOnce && _consecutiveFailures < _maxConsecutiveFailures) {
|
||||||
if (_remainingSeconds > 0) {
|
if (_remainingSeconds > 0) {
|
||||||
@@ -159,7 +138,7 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Offline watchdog ─────────────────────────────────────────────────
|
|
||||||
// If the server has been unreachable for too long, kill the VPN.
|
// If the server has been unreachable for too long, kill the VPN.
|
||||||
// Don't let an orphaned tunnel keep running forever.
|
// Don't let an orphaned tunnel keep running forever.
|
||||||
if (_consecutiveFailures >= _maxConsecutiveFailures) {
|
if (_consecutiveFailures >= _maxConsecutiveFailures) {
|
||||||
@@ -171,7 +150,7 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Local expiry ─────────────────────────────────────────────────────
|
|
||||||
// When our clock hits zero, disconnect immediately. Don't wait for the
|
// When our clock hits zero, disconnect immediately. Don't wait for the
|
||||||
// next server poll — the session is over and the user expects the VPN
|
// next server poll — the session is over and the user expects the VPN
|
||||||
// to drop right now.
|
// to drop right now.
|
||||||
@@ -186,7 +165,7 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
_syncWithHivemind();
|
_syncWithHivemind();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Update notification every tick for smooth countdown ─────────────
|
|
||||||
// Uses the locally-decremented time so the notification stays in sync
|
// Uses the locally-decremented time so the notification stays in sync
|
||||||
// with the in-app display, not gated behind the 5 s server poll.
|
// with the in-app display, not gated behind the 5 s server poll.
|
||||||
if (_hasSyncedOnce) {
|
if (_hasSyncedOnce) {
|
||||||
@@ -206,8 +185,6 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force-disconnects the VPN and stops the timer with a status message.
|
|
||||||
/// Used as a local fallback when the server can't be reached.
|
|
||||||
void _forceDisconnect(String reason) {
|
void _forceDisconnect(String reason) {
|
||||||
if (_isDisconnecting) return; // Already tearing down
|
if (_isDisconnecting) return; // Already tearing down
|
||||||
debugPrint('[Timer] $reason — forcing disconnect.');
|
debugPrint('[Timer] $reason — forcing disconnect.');
|
||||||
@@ -222,7 +199,7 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
vpnConnection.disconnect();
|
vpnConnection.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Server Sync ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
Future<void> _syncWithHivemind() async {
|
Future<void> _syncWithHivemind() async {
|
||||||
// Never sync while the user is explicitly disconnecting.
|
// Never sync while the user is explicitly disconnecting.
|
||||||
@@ -249,7 +226,7 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
_quotaBytes = data['quota_bytes'] ?? _quotaBytes;
|
_quotaBytes = data['quota_bytes'] ?? _quotaBytes;
|
||||||
_usedBytes = data['used_bytes'] ?? _usedBytes;
|
_usedBytes = data['used_bytes'] ?? _usedBytes;
|
||||||
|
|
||||||
// ── Speed calculation over the polling interval ────────────────────
|
|
||||||
final int deltaBytes = _usedBytes - _lastUsedBytes;
|
final int deltaBytes = _usedBytes - _lastUsedBytes;
|
||||||
if (_hasSyncedOnce && deltaBytes > 0) {
|
if (_hasSyncedOnce && deltaBytes > 0) {
|
||||||
_currentSpeedKBps = (deltaBytes / _pollIntervalSeconds) / 1000;
|
_currentSpeedKBps = (deltaBytes / _pollIntervalSeconds) / 1000;
|
||||||
@@ -258,7 +235,7 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
_lastUsedBytes = _usedBytes;
|
_lastUsedBytes = _usedBytes;
|
||||||
|
|
||||||
// ── Network is healthy — reset failure counters ────────────────────
|
|
||||||
_consecutiveFailures = 0;
|
_consecutiveFailures = 0;
|
||||||
_offlineSeconds = 0;
|
_offlineSeconds = 0;
|
||||||
_hasSyncedOnce = true;
|
_hasSyncedOnce = true;
|
||||||
@@ -277,8 +254,6 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Increments the failure counter; pauses the local countdown after
|
|
||||||
/// [_maxConsecutiveFailures] to avoid displaying a false "expired" clock.
|
|
||||||
void _markSyncFailure() {
|
void _markSyncFailure() {
|
||||||
_consecutiveFailures++;
|
_consecutiveFailures++;
|
||||||
if (_consecutiveFailures >= _maxConsecutiveFailures) {
|
if (_consecutiveFailures >= _maxConsecutiveFailures) {
|
||||||
@@ -286,12 +261,8 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Stop ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Called externally (e.g. from ConnectButton) to begin tearing down.
|
|
||||||
/// Cancels the timer but keeps the notification alive —
|
|
||||||
/// [_onVpnConnectionChanged] will do final cleanup when the VPN actually
|
|
||||||
/// confirms it's disconnected.
|
|
||||||
void stop() {
|
void stop() {
|
||||||
_userInitiatedStop = true;
|
_userInitiatedStop = true;
|
||||||
_isDisconnecting = true;
|
_isDisconnecting = true;
|
||||||
@@ -303,9 +274,6 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restarts the periodic timer without resetting session state.
|
|
||||||
/// Used when the VPN reconnects after a brief network blip so we don't
|
|
||||||
/// lose track of remaining time / data.
|
|
||||||
void _resumeTicking() {
|
void _resumeTicking() {
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_timer = Timer.periodic(const Duration(seconds: 1), _tick);
|
_timer = Timer.periodic(const Duration(seconds: 1), _tick);
|
||||||
@@ -314,8 +282,6 @@ class SessionTimer extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal cleanup — called when VpnConnection confirms disconnected.
|
|
||||||
/// Cancels the persistent notification since the tunnel is truly down.
|
|
||||||
void _stopInternal() {
|
void _stopInternal() {
|
||||||
_isDisconnecting = true;
|
_isDisconnecting = true;
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
// =============================================================================
|
|
||||||
// vpn_connection.dart — VLESS VPN Tunnel Manager
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_vless/flutter_vless.dart';
|
import 'package:flutter_vless/flutter_vless.dart';
|
||||||
import 'package:revoltvpn/logic/hivemind_service.dart';
|
import 'package:revoltvpn/logic/hivemind_service.dart';
|
||||||
|
|
||||||
// ── VPN States ────────────────────────────────────────────────────────────────
|
|
||||||
enum VpnStatus {
|
enum VpnStatus {
|
||||||
disconnected,
|
disconnected,
|
||||||
connecting,
|
connecting,
|
||||||
@@ -99,7 +94,6 @@ class VpnConnection extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Connect ───────────────────────────────────────────────────────────────
|
|
||||||
Future<bool> connect() async {
|
Future<bool> connect() async {
|
||||||
if (_status == VpnStatus.connected || _status == VpnStatus.connecting) {
|
if (_status == VpnStatus.connected || _status == VpnStatus.connecting) {
|
||||||
return false;
|
return false;
|
||||||
@@ -121,7 +115,6 @@ class VpnConnection extends ChangeNotifier {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Step 1: Fetch VLESS URL from Hivemind ──────────────────────────
|
|
||||||
String vlessUrl;
|
String vlessUrl;
|
||||||
try {
|
try {
|
||||||
vlessUrl = await HivemindService.fetchConfigWithPolling(
|
vlessUrl = await HivemindService.fetchConfigWithPolling(
|
||||||
@@ -149,13 +142,11 @@ class VpnConnection extends ChangeNotifier {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Step 2: Check for cancellation ────────────────────────────────
|
|
||||||
if (_cancelled) {
|
if (_cancelled) {
|
||||||
_setStatus(VpnStatus.disconnected, 'Tap to connect');
|
_setStatus(VpnStatus.disconnected, 'Tap to connect');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Step 3: Parse and start VLESS tunnel ───────────────────────────
|
|
||||||
try {
|
try {
|
||||||
final parsed = FlutterVless.parse(vlessUrl);
|
final parsed = FlutterVless.parse(vlessUrl);
|
||||||
final config = parsed.getFullConfiguration();
|
final config = parsed.getFullConfiguration();
|
||||||
@@ -199,7 +190,6 @@ class VpnConnection extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Disconnect ────────────────────────────────────────────────────────────
|
|
||||||
Future<void> disconnect() async {
|
Future<void> disconnect() async {
|
||||||
_cancelled = true;
|
_cancelled = true;
|
||||||
HivemindService.cancel(); // abort any in-progress polling
|
HivemindService.cancel(); // abort any in-progress polling
|
||||||
|
|||||||
+4
-32
@@ -10,63 +10,36 @@ import 'package:revoltvpn/logic/consent_manager.dart';
|
|||||||
import 'package:revoltvpn/logic/app_colors.dart';
|
import 'package:revoltvpn/logic/app_colors.dart';
|
||||||
import 'package:revoltvpn/screens/intro.dart';
|
import 'package:revoltvpn/screens/intro.dart';
|
||||||
|
|
||||||
/// The entry point for the REVOLT VPN application.
|
|
||||||
void main() async {
|
void main() async {
|
||||||
// Ensure that the Flutter engine is fully initialized before using platform channels
|
|
||||||
// (e.g., for SystemChrome or MobileAds).
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
// Lock the application to portrait orientation since the dashboard is a single-screen utility app
|
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
SystemChrome.setPreferredOrientations([
|
|
||||||
DeviceOrientation.portraitUp,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Configure a stealth system bar layout: transparent status bar with light icons,
|
|
||||||
// blending seamlessly into the dark background gradient.
|
|
||||||
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||||
statusBarColor: Colors.transparent,
|
statusBarColor: Colors.transparent,
|
||||||
statusBarIconBrightness: Brightness.light,
|
statusBarIconBrightness: Brightness.light,
|
||||||
systemNavigationBarColor: Color(0xFF0D1117), // Deep space black
|
systemNavigationBarColor: Color(0xFF0D1117),
|
||||||
systemNavigationBarIconBrightness: Brightness.light,
|
systemNavigationBarIconBrightness: Brightness.light,
|
||||||
));
|
));
|
||||||
|
|
||||||
// ── GDPR Consent (Google UMP) ──────────────────────────────────────────
|
|
||||||
// Must run BEFORE MobileAds initialization. Shows consent dialog to
|
|
||||||
// EEA/UK users; no-op for everyone else.
|
|
||||||
await ConsentManager.requestConsentIfNeeded();
|
await ConsentManager.requestConsentIfNeeded();
|
||||||
|
|
||||||
// ── Initialize AdMob ───────────────────────────────────────────────────
|
|
||||||
// Only after consent is resolved so AdMob respects user preferences.
|
|
||||||
await MobileAds.instance.initialize();
|
await MobileAds.instance.initialize();
|
||||||
|
|
||||||
// Inflate the root widget
|
|
||||||
runApp(const ReVoltApp());
|
runApp(const ReVoltApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The root application widget wrapping the app in necessary state providers and themes.
|
|
||||||
class ReVoltApp extends StatelessWidget {
|
class ReVoltApp extends StatelessWidget {
|
||||||
const ReVoltApp({super.key});
|
const ReVoltApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// MultiProvider injects global state controllers down the widget tree.
|
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
// Manages the VLESS tunnel lifecycle
|
|
||||||
ChangeNotifierProvider(create: (_) => VpnConnection()),
|
ChangeNotifierProvider(create: (_) => VpnConnection()),
|
||||||
|
|
||||||
// Manages the 1-hour session clock.
|
|
||||||
// Uses ProxyProvider to automatically inject the VpnConnection,
|
|
||||||
// allowing the timer to auto-disconnect the VPN when time expires.
|
|
||||||
ChangeNotifierProxyProvider<VpnConnection, SessionTimer>(
|
ChangeNotifierProxyProvider<VpnConnection, SessionTimer>(
|
||||||
create: (ctx) => SessionTimer(
|
create: (ctx) => SessionTimer(vpnConnection: ctx.read<VpnConnection>()),
|
||||||
vpnConnection: ctx.read<VpnConnection>(),
|
update: (_, vpn, prev) => prev ?? SessionTimer(vpnConnection: vpn),
|
||||||
),
|
),
|
||||||
update: (_, vpn, prev) =>
|
|
||||||
prev ?? SessionTimer(vpnConnection: vpn),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Manages rewarded ad lifecycle — preloads on startup.
|
|
||||||
ChangeNotifierProvider(create: (_) => AdManager()),
|
ChangeNotifierProvider(create: (_) => AdManager()),
|
||||||
],
|
],
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
@@ -78,7 +51,6 @@ class ReVoltApp extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs the global dark theme used across the application.
|
|
||||||
ThemeData _buildDarkTheme() {
|
ThemeData _buildDarkTheme() {
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
|
|||||||
Reference in new issue
Block a user