v2.0.0: VLESS cleanup — remove bonus ads, throttling, fix Xray compliance, rebrand colors
This commit is contained in:
1 parent
10a1450292
commit
d106356ca6
20 files changed
+318
-308
No files matched your search
@@ -2,17 +2,13 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:revoltvpn/logic/vpn_connection.dart';
|
||||
import 'package:revoltvpn/logic/session_timer.dart';
|
||||
import 'package:revoltvpn/logic/ad_manager.dart';
|
||||
import 'package:revoltvpn/logic/app_colors.dart';
|
||||
import 'package:revoltvpn/components/online_dot.dart';
|
||||
import 'package:revoltvpn/components/watch_ad_button.dart';
|
||||
|
||||
/// BottomBar is the bottom utility panel that surfaces server connectivity status
|
||||
/// and offers the user an option to support the server when the VPN tunnel is active.
|
||||
/// BottomBar is the bottom utility panel that surfaces server connectivity status.
|
||||
class BottomBar extends StatelessWidget {
|
||||
const BottomBar({super.key});
|
||||
|
||||
static const Color _cardBg = Color(0xFF1E2533);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer2<VpnConnection, SessionTimer>(
|
||||
@@ -26,10 +22,10 @@ class BottomBar extends StatelessWidget {
|
||||
margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: _cardBg,
|
||||
color: AppColors.bgCard,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: const Color.fromRGBO(74, 85, 104, 0.15),
|
||||
color: AppColors.slate15,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
@@ -43,9 +39,6 @@ class BottomBar extends StatelessWidget {
|
||||
? 'Server Online'
|
||||
: 'Disconnected',
|
||||
),
|
||||
const Spacer(),
|
||||
// Support button — only visible while connected AND ads are enabled
|
||||
if (isConnected && AdManager.adsEnabled) const WatchAdButton(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:revoltvpn/logic/app_colors.dart';
|
||||
import 'package:revoltvpn/logic/session_timer.dart';
|
||||
|
||||
/// ClockDisplay renders inside the ConnectButton circle when the VPN is active.
|
||||
@@ -36,7 +37,7 @@ class ClockDisplay extends StatelessWidget {
|
||||
height: 32,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Color.fromRGBO(0, 229, 255, 0.5),
|
||||
color: AppColors.cyan50,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
@@ -45,7 +46,7 @@ class ClockDisplay extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color.fromRGBO(0, 229, 255, 0.6),
|
||||
color: AppColors.cyan60,
|
||||
letterSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
@@ -56,11 +57,11 @@ class ClockDisplay extends StatelessWidget {
|
||||
style: const TextStyle(
|
||||
fontSize: 40,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.white,
|
||||
color: AppColors.textWhite,
|
||||
letterSpacing: 2,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Color.fromRGBO(0, 229, 255, 0.5),
|
||||
color: AppColors.cyan50,
|
||||
blurRadius: 16,
|
||||
),
|
||||
],
|
||||
@@ -72,7 +73,7 @@ class ClockDisplay extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.speed, color: Colors.white54, size: 14),
|
||||
const Icon(Icons.speed, color: AppColors.textDim, size: 14),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
timer.currentSpeedKBps > 0.5
|
||||
@@ -80,7 +81,7 @@ class ClockDisplay extends StatelessWidget {
|
||||
: 'Idle',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white54,
|
||||
color: AppColors.textDim,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:revoltvpn/logic/app_colors.dart';
|
||||
import 'package:revoltvpn/logic/vpn_connection.dart';
|
||||
import 'package:revoltvpn/logic/session_timer.dart';
|
||||
import 'package:revoltvpn/logic/ad_manager.dart';
|
||||
import 'package:revoltvpn/components/clock_display.dart';
|
||||
|
||||
class ConnectButton extends StatefulWidget {
|
||||
@@ -17,9 +19,6 @@ class _ConnectButtonState extends State<ConnectButton>
|
||||
late AnimationController _pulseController;
|
||||
late Animation<double> _pulseAnim;
|
||||
|
||||
static const Color _cyanGlow = Color(0xFF00E5FF);
|
||||
static const Color _bgDark = Color(0xFF1A202C);
|
||||
|
||||
bool _inCooldown = false;
|
||||
|
||||
@override
|
||||
@@ -78,6 +77,18 @@ class _ConnectButtonState extends State<ConnectButton>
|
||||
} else if (vpn.status == VpnStatus.disconnected ||
|
||||
vpn.status == VpnStatus.error) {
|
||||
HapticFeedback.lightImpact();
|
||||
|
||||
// ── Show rewarded ad ──
|
||||
// In production the server creates the session via AdMob SSV callback
|
||||
// only after a real ad is watched. The debug bypass in HivemindService
|
||||
// handles session creation when adsEnabled is false.
|
||||
final adMgr = context.read<AdManager>();
|
||||
final adWatched = await adMgr.showAd('main_ad');
|
||||
if (AdManager.adsEnabled && !adWatched) {
|
||||
// User closed the ad before earning the reward — don't connect.
|
||||
return;
|
||||
}
|
||||
|
||||
final success = await vpn.connect();
|
||||
if (success) {
|
||||
timer.start('main_ad');
|
||||
@@ -104,9 +115,9 @@ class _ConnectButtonState extends State<ConnectButton>
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
backgroundColor: const Color(0xFF1A202C),
|
||||
backgroundColor: AppColors.bgDark,
|
||||
title: const Text('VPN Permission Required',
|
||||
style: TextStyle(color: Colors.white)),
|
||||
style: TextStyle(color: AppColors.textWhite)),
|
||||
content: const Text(
|
||||
'ReVoltVPN needs permission to set up a VPN connection.\n\n'
|
||||
'Since the permission was denied once, Android won\'t ask again. '
|
||||
@@ -115,16 +126,16 @@ class _ConnectButtonState extends State<ConnectButton>
|
||||
'2. Go to Apps → ReVoltVPN\n'
|
||||
'3. Tap "VPN" and enable it\n'
|
||||
'4. Return to ReVoltVPN and tap connect',
|
||||
style: TextStyle(color: Color(0xFFA0AEC0)),
|
||||
style: TextStyle(color: AppColors.textMuted),
|
||||
),
|
||||
actions: [
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF00E5FF),
|
||||
backgroundColor: AppColors.cyan,
|
||||
),
|
||||
child: const Text('Got it',
|
||||
style: TextStyle(color: Color(0xFF0D1117))),
|
||||
style: TextStyle(color: AppColors.bgDeep)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -153,15 +164,15 @@ class _ConnectButtonState extends State<ConnectButton>
|
||||
height: 260,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _bgDark,
|
||||
color: AppColors.bgDark,
|
||||
border: Border.all(
|
||||
color: _inCooldown
|
||||
? Colors.grey.withValues(alpha: 0.5)
|
||||
? Colors.grey.withValues(alpha: 0.5) // dynamic, OK inline
|
||||
: isConnected
|
||||
? _cyanGlow
|
||||
? AppColors.cyan
|
||||
: isConnecting
|
||||
? const Color.fromRGBO(0, 229, 255, 0.5)
|
||||
: const Color.fromRGBO(74, 85, 104, 0.5),
|
||||
? AppColors.cyan50
|
||||
: AppColors.slate50,
|
||||
width: isConnected && !_inCooldown ? 3 : 2,
|
||||
),
|
||||
boxShadow: isConnected
|
||||
@@ -194,7 +205,7 @@ class _ConnectButtonState extends State<ConnectButton>
|
||||
height: 48,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.5,
|
||||
color: Color.fromRGBO(0, 229, 255, 0.7),
|
||||
color: AppColors.cyan70,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -206,15 +217,15 @@ class _ConnectButtonState extends State<ConnectButton>
|
||||
if (inCooldown) {
|
||||
label = 'PLEASE WAIT';
|
||||
icon = Icons.hourglass_bottom;
|
||||
tint = Colors.grey;
|
||||
tint = Colors.grey; // dynamic, OK inline
|
||||
} else if (status == VpnStatus.error) {
|
||||
label = 'TRY AGAIN';
|
||||
icon = Icons.refresh;
|
||||
tint = const Color(0xFFEF5350); // soft red — something went wrong
|
||||
tint = AppColors.red; // soft red — something went wrong
|
||||
} else {
|
||||
label = 'TAP TO CONNECT';
|
||||
icon = Icons.power_settings_new;
|
||||
tint = const Color.fromRGBO(74, 85, 104, 0.7);
|
||||
tint = AppColors.slate70;
|
||||
}
|
||||
|
||||
return Column(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||
import 'package:revoltvpn/logic/app_colors.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
/// GdprButton lets the user review or change their ad consent choices.
|
||||
@@ -83,7 +84,7 @@ class GdprButton extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.admin_panel_settings_outlined,
|
||||
color: Colors.white, size: 28),
|
||||
color: AppColors.textWhite, size: 28),
|
||||
onPressed: _showConsentForm,
|
||||
tooltip: 'Ad consent & privacy choices',
|
||||
splashRadius: 24,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:revoltvpn/logic/app_colors.dart';
|
||||
|
||||
/// Header displays the ReVolt brand logo, title, and subtitle.
|
||||
///
|
||||
@@ -7,9 +8,6 @@ import 'package:flutter/material.dart';
|
||||
class Header extends StatelessWidget {
|
||||
const Header({super.key});
|
||||
|
||||
// cyber-revolt cyan glow accent color used as a fallback for the shield icon
|
||||
static const Color _cyanGlow = Color(0xFF00E5FF);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
@@ -23,7 +21,7 @@ class Header extends StatelessWidget {
|
||||
errorBuilder: (context, error, stackTrace) => const Icon(
|
||||
Icons.shield,
|
||||
size: 120,
|
||||
color: _cyanGlow,
|
||||
color: AppColors.cyan,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8), // Standard padding between the logo and the title
|
||||
@@ -35,7 +33,7 @@ class Header extends StatelessWidget {
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: 8,
|
||||
color: Colors.white,
|
||||
color: AppColors.textWhite,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2), // Close spacing for secondary information
|
||||
@@ -47,7 +45,7 @@ class Header extends StatelessWidget {
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 4,
|
||||
color: Color(0xAA4A5568), // Semi-transparent slate gray
|
||||
color: AppColors.slateAA, // Semi-transparent slate gray
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:revoltvpn/logic/app_colors.dart';
|
||||
|
||||
class InfoButton extends StatelessWidget {
|
||||
const InfoButton({super.key});
|
||||
@@ -14,7 +15,7 @@ class InfoButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.help_outline, color: Colors.white, size: 28),
|
||||
icon: const Icon(Icons.help_outline, color: AppColors.textWhite, size: 28),
|
||||
onPressed: _launchGitHub,
|
||||
tooltip: 'About ReVoltVPN',
|
||||
splashRadius: 24,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.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.
|
||||
@@ -30,11 +31,11 @@ class OnlineDot extends StatelessWidget {
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: isOnline ? const Color(0xFF00E676) : const Color(0xFF4A5568),
|
||||
color: isOnline ? AppColors.green : AppColors.slate,
|
||||
boxShadow: isOnline
|
||||
? const [
|
||||
BoxShadow(
|
||||
color: Color.fromRGBO(0, 230, 118, 0.5),
|
||||
color: AppColors.green50,
|
||||
blurRadius: 6,
|
||||
),
|
||||
]
|
||||
@@ -48,8 +49,8 @@ class OnlineDot extends StatelessWidget {
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isOnline
|
||||
? const Color.fromRGBO(255, 255, 255, 0.8)
|
||||
: const Color(0xFF4A5568),
|
||||
? AppColors.textWhite80
|
||||
: AppColors.slate,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:revoltvpn/logic/app_colors.dart';
|
||||
|
||||
class PrivacyPolicyButton extends StatelessWidget {
|
||||
const PrivacyPolicyButton({super.key});
|
||||
@@ -14,7 +15,7 @@ class PrivacyPolicyButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.privacy_tip_outlined, color: Colors.white, size: 28),
|
||||
icon: const Icon(Icons.privacy_tip_outlined, color: AppColors.textWhite, size: 28),
|
||||
onPressed: _launchPrivacyPolicy,
|
||||
tooltip: 'Privacy Policy',
|
||||
splashRadius: 24,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:revoltvpn/logic/app_colors.dart';
|
||||
import 'package:revoltvpn/logic/vpn_connection.dart';
|
||||
|
||||
/// StatusText displays the status of the VPN tunnel.
|
||||
@@ -22,12 +23,12 @@ class StatusText extends StatelessWidget {
|
||||
key: ValueKey(vpn.status),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0x22FFFFFF), // Subtle glass effect background
|
||||
color: AppColors.glassBg, // Subtle glass effect background
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(
|
||||
color: isConnected
|
||||
? const Color(0x8800E5FF) // Glowing cyan border when connected
|
||||
: const Color(0x33FFFFFF), // Muted translucent border when disconnected/connecting
|
||||
? AppColors.cyanGlow // Glowing cyan border when connected
|
||||
: AppColors.glassBorder, // Muted translucent border when disconnected/connecting
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
@@ -45,7 +46,7 @@ class StatusText extends StatelessWidget {
|
||||
const Text(
|
||||
'Helsinki, Finland',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF00E5FF),
|
||||
color: AppColors.cyan,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
letterSpacing: 0.5,
|
||||
@@ -58,22 +59,22 @@ class StatusText extends StatelessWidget {
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1.5,
|
||||
color: Color(0xFF00E5FF),
|
||||
color: AppColors.cyan,
|
||||
),
|
||||
)
|
||||
else
|
||||
const Icon(
|
||||
Icons.shield_outlined,
|
||||
size: 18,
|
||||
color: Color(0xAA4A5568),
|
||||
color: AppColors.slateAA,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
vpn.statusMessage.toUpperCase(),
|
||||
style: TextStyle(
|
||||
color: isConnecting
|
||||
? const Color(0xFF00E5FF)
|
||||
: const Color(0xAA4A5568),
|
||||
? AppColors.cyan
|
||||
: AppColors.slateAA,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
letterSpacing: 1,
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:revoltvpn/logic/session_timer.dart';
|
||||
import 'package:revoltvpn/logic/ad_manager.dart';
|
||||
|
||||
/// WatchAdButton is a voluntary option allowing the user to watch a rewarded video ad
|
||||
/// and extend their active VPN tunnel session by 30 minutes as a reward.
|
||||
class WatchAdButton extends StatelessWidget {
|
||||
const WatchAdButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () async {
|
||||
final adWatched = await context.read<AdManager>().showAd('bonus_ad');
|
||||
if (!adWatched || !context.mounted) return;
|
||||
|
||||
// The actual session extension happens server-side via AdMob SSV.
|
||||
// addBonusTime() syncs the new limits from the server.
|
||||
final synced = await context.read<SessionTimer>().addBonusTime();
|
||||
if (!context.mounted) return;
|
||||
|
||||
if (synced) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: const Text(
|
||||
'+30 minutes added!',
|
||||
style: TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
backgroundColor: const Color(0xFF006064),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Could not confirm bonus. Please check your connection.',
|
||||
style: TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
backgroundColor: Color(0xFF5D4037),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0x1A00E5FF), Color(0x0D00E5FF)],
|
||||
),
|
||||
border: Border.all(
|
||||
color: const Color.fromRGBO(0, 229, 255, 0.25),
|
||||
),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('❤️', style: TextStyle(fontSize: 14)),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'Support +30m',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF00E5FF),
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user