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]>
85 lines
2.5 KiB
Dart
85 lines
2.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:revoltvpn/logic/app_colors.dart';
|
|
import 'package:revoltvpn/logic/connection_settings.dart';
|
|
import 'package:revoltvpn/logic/vpn_connection.dart';
|
|
|
|
class ResilienceModeTile extends StatefulWidget {
|
|
const ResilienceModeTile({super.key});
|
|
|
|
@override
|
|
State<ResilienceModeTile> createState() => _ResilienceModeTileState();
|
|
}
|
|
|
|
class _ResilienceModeTileState extends State<ResilienceModeTile> {
|
|
ResilienceMode _mode = ConnectionSettings.resilienceMode;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_load();
|
|
}
|
|
|
|
Future<void> _load() async {
|
|
await ConnectionSettings.initialize();
|
|
if (mounted) setState(() => _mode = ConnectionSettings.resilienceMode);
|
|
}
|
|
|
|
bool _vpnBusy() {
|
|
final status = context.read<VpnConnection>().status;
|
|
return status == VpnStatus.connected ||
|
|
status == VpnStatus.connecting ||
|
|
status == VpnStatus.disconnecting;
|
|
}
|
|
|
|
Future<void> _change(ResilienceMode? next) async {
|
|
if (next == null || next == _mode) return;
|
|
if (_vpnBusy()) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Disconnect before changing resilience mode.'),
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
|
|
await ConnectionSettings.setResilienceMode(next);
|
|
if (mounted) setState(() => _mode = next);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final extreme = _mode == ResilienceMode.extreme;
|
|
|
|
return ListTile(
|
|
title: const Text(
|
|
'Network resilience',
|
|
style: TextStyle(color: AppColors.textWhite, fontSize: 15),
|
|
),
|
|
subtitle: Text(
|
|
extreme
|
|
? 'Extreme: keeps the exact server transport unchanged and makes up to two controlled recovery attempts after each real Xray disconnect.'
|
|
: 'Standard: keeps the exact server transport unchanged and does not force health-check reconnects.',
|
|
style: const TextStyle(color: AppColors.textDim, fontSize: 12),
|
|
),
|
|
trailing: DropdownButtonHideUnderline(
|
|
child: DropdownButton<ResilienceMode>(
|
|
value: _mode,
|
|
dropdownColor: AppColors.bgCard,
|
|
onChanged: _change,
|
|
items: const [
|
|
DropdownMenuItem(
|
|
value: ResilienceMode.standard,
|
|
child: Text('Standard'),
|
|
),
|
|
DropdownMenuItem(
|
|
value: ResilienceMode.extreme,
|
|
child: Text('Extreme'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|