refactor(app): debloat 3.3.1 and fix startup restoration
Maintainer changes on top of PR #2 (Naveax). Reduces the release to two deliberate connection modes and resolves the issues found in review: - Lock-screen privacy: move VISIBILITY_PRIVATE and a generic publicVersion into MainActivity.updateVpnNotification. The PR's fix lived in an unreferenced class and never reached the live notification. - Haptics: restore the default to off, and reduce HapticSettings to a single success() on connect/disconnect via the native channel. - SOCKS5: make it a real proxyOnly local-proxy mode rather than a TUN connection under a different label. TUN stays the default. - Health probe: restore the idle-only guard so /health is no longer polled every 30s while connected. - Recovery: add a _connectionEpoch counter so connect() aborts an in-flight Extreme recovery. - Startup restoration: rebuild the recovery snapshot from the still-active session after an app-process restart (TUN), and surface an honest orphaned state with a Reconnect action in proxy mode. Drops per-app routing entirely. It only ever functioned in TUN -- addDisallowedApplication lives inside the VPN-interface setup that proxyOnly skips -- and its allowlist half had no Dart caller. Also removes flutter_local_notifications, the global tap-haptic surface, and unused runtime telemetry. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
a71161e7e5
commit
6fcde9dd7b
18 files changed
+271
-1027
No files matched your search
@@ -1,65 +0,0 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:revoltvpn/logic/haptic_settings.dart';
|
||||
|
||||
/// Gives short touch taps the same haptic feedback across the app.
|
||||
/// Scroll gestures are ignored by checking travel distance and touch duration.
|
||||
class AppHapticSurface extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const AppHapticSurface({super.key, required this.child});
|
||||
|
||||
@override
|
||||
State<AppHapticSurface> createState() => _AppHapticSurfaceState();
|
||||
}
|
||||
|
||||
class _PointerStart {
|
||||
final Offset position;
|
||||
final DateTime time;
|
||||
|
||||
const _PointerStart(this.position, this.time);
|
||||
}
|
||||
|
||||
class _AppHapticSurfaceState extends State<AppHapticSurface> {
|
||||
static const double _maxTapTravel = 18;
|
||||
static const Duration _maxTapDuration = Duration(milliseconds: 700);
|
||||
|
||||
final Map<int, _PointerStart> _starts = <int, _PointerStart>{};
|
||||
|
||||
bool _supportsHaptics(PointerDeviceKind kind) {
|
||||
return kind == PointerDeviceKind.touch ||
|
||||
kind == PointerDeviceKind.stylus ||
|
||||
kind == PointerDeviceKind.invertedStylus;
|
||||
}
|
||||
|
||||
void _onDown(PointerDownEvent event) {
|
||||
if (!_supportsHaptics(event.kind)) return;
|
||||
_starts[event.pointer] = _PointerStart(event.position, DateTime.now());
|
||||
}
|
||||
|
||||
void _onCancel(PointerCancelEvent event) {
|
||||
_starts.remove(event.pointer);
|
||||
}
|
||||
|
||||
void _onUp(PointerUpEvent event) {
|
||||
final start = _starts.remove(event.pointer);
|
||||
if (start == null || !_supportsHaptics(event.kind)) return;
|
||||
|
||||
final travelled = (event.position - start.position).distance;
|
||||
final elapsed = DateTime.now().difference(start.time);
|
||||
if (travelled <= _maxTapTravel && elapsed <= _maxTapDuration) {
|
||||
HapticSettings.tap();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Listener(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onPointerDown: _onDown,
|
||||
onPointerUp: _onUp,
|
||||
onPointerCancel: _onCancel,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ 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/logic/haptic_settings.dart';
|
||||
|
||||
class ConnectButton extends StatefulWidget {
|
||||
const ConnectButton({super.key});
|
||||
@@ -82,6 +83,7 @@ class _ConnectButtonState extends State<ConnectButton>
|
||||
if (vpn.status == VpnStatus.connected || vpn.status == VpnStatus.connecting) {
|
||||
_busy = false;
|
||||
await timer.disconnect();
|
||||
HapticSettings.success();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,7 +95,10 @@ class _ConnectButtonState extends State<ConnectButton>
|
||||
if (!adWatched && AdManager.adsEnabled) return;
|
||||
|
||||
final ok = await vpn.connect();
|
||||
if (ok) await timer.start();
|
||||
if (ok) {
|
||||
await timer.start();
|
||||
HapticSettings.success();
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _busy = false);
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
|
||||
class VpnNotificationManager {
|
||||
static final FlutterLocalNotificationsPlugin _plugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
static bool _initialized = false;
|
||||
static String? _lastTimeLeft;
|
||||
static String? _lastSpeedKbps;
|
||||
|
||||
// Android channels are immutable — bump this if settings change.
|
||||
static const _channelId = 'revolt_vpn_v4';
|
||||
static const _channelName = 'Revolt VPN';
|
||||
static const _notifId = 888;
|
||||
|
||||
static Future<void> init() async {
|
||||
if (_initialized) return;
|
||||
|
||||
const androidSettings =
|
||||
AndroidInitializationSettings('@drawable/notification_icon');
|
||||
const initSettings = InitializationSettings(android: androidSettings);
|
||||
|
||||
await _plugin.initialize(initSettings);
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
final androidPlugin = _plugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>();
|
||||
await androidPlugin?.requestNotificationsPermission();
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
static Future<void> showOrUpdateStatus({
|
||||
required String timeLeft,
|
||||
required String speedKbps,
|
||||
}) async {
|
||||
if (timeLeft == _lastTimeLeft && speedKbps == _lastSpeedKbps) return;
|
||||
_lastTimeLeft = timeLeft;
|
||||
_lastSpeedKbps = speedKbps;
|
||||
|
||||
if (!_initialized) await init();
|
||||
|
||||
final bodyText = 'Time: $timeLeft • $speedKbps';
|
||||
|
||||
final bigPictureStyle = BigPictureStyleInformation(
|
||||
const DrawableResourceAndroidBitmap('notification_banner'),
|
||||
largeIcon: const DrawableResourceAndroidBitmap('notification_icon'),
|
||||
contentTitle: 'Revolt VPN',
|
||||
summaryText: bodyText,
|
||||
htmlFormatContentTitle: false,
|
||||
htmlFormatSummaryText: false,
|
||||
);
|
||||
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
_channelId,
|
||||
_channelName,
|
||||
channelDescription: 'Session timer and speed',
|
||||
importance: Importance.low,
|
||||
priority: Priority.low,
|
||||
ongoing: true,
|
||||
autoCancel: false,
|
||||
playSound: false,
|
||||
enableVibration: false,
|
||||
showWhen: false,
|
||||
// Session time/speed is usage metadata. Keep it out of public lock-screen
|
||||
// previews while leaving the ongoing VPN status notification intact.
|
||||
visibility: NotificationVisibility.private,
|
||||
usesChronometer: false,
|
||||
category: AndroidNotificationCategory.service,
|
||||
colorized: true,
|
||||
color: const ui.Color(0xFF0D1117),
|
||||
styleInformation: bigPictureStyle,
|
||||
);
|
||||
|
||||
final details = NotificationDetails(android: androidDetails);
|
||||
|
||||
try {
|
||||
await _plugin.show(_notifId, _channelName, bodyText, details);
|
||||
} catch (e) {
|
||||
_initialized = false;
|
||||
try {
|
||||
await init();
|
||||
await _plugin.show(_notifId, _channelName, bodyText, details);
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> cancel() async {
|
||||
if (!_initialized) return;
|
||||
await _plugin.cancel(_notifId);
|
||||
}
|
||||
}
|
||||
@@ -104,6 +104,18 @@ class StatusBar extends StatelessWidget {
|
||||
label: statusLabel,
|
||||
),
|
||||
const Spacer(),
|
||||
if (vpn.networkTransport != 'unknown' &&
|
||||
vpn.networkTransport != 'none')
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Text(
|
||||
vpn.networkTransport,
|
||||
style: const TextStyle(
|
||||
color: AppColors.textDim,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isConnected)
|
||||
const Icon(
|
||||
Icons.lock,
|
||||
|
||||
Reference in new issue
Block a user