chore: release 3.3.0 (sidebar nav fix, notification countdown, hardening)

Co-authored-by: Naveax <[email protected]>
This commit is contained in:
esefxdzandNaveax committed 2026-08-30 14:01:52 +03:00
1 parent 4eb1fca666
commit 62104d5730
28 files changed
+670 -215

No files matched your search

+14
View File
@@ -4,6 +4,9 @@ import 'package:provider/provider.dart';
import 'package:revoltvpn/logic/app_colors.dart';
import 'package:revoltvpn/logic/vpn_connection.dart';
import 'package:revoltvpn/screens/main_screen.dart';
import 'package:revoltvpn/screens/settings/in_settings/rain.dart';
import 'package:revoltvpn/screens/settings/in_settings/lightning.dart';
import 'package:revoltvpn/screens/settings/in_settings/haptic.dart';
class IntroScreen extends StatefulWidget {
const IntroScreen({super.key});
@@ -42,6 +45,11 @@ class _IntroScreenState extends State<IntroScreen> with WidgetsBindingObserver {
final elapsed = Stopwatch()..start();
final vpn = context.read<VpnConnection>();
// The overlays read these in initState, so they must land before
// MainScreen mounts.
final settingsPrefs = Future.wait(
[loadRainPref(), loadLightningPref(), loadHapticPref()]);
try {
await vpn.ready.timeout(_bootTimeout);
} on TimeoutException {
@@ -51,6 +59,12 @@ class _IntroScreenState extends State<IntroScreen> with WidgetsBindingObserver {
debugPrint('[Boot] Engine init error: $e');
}
try {
await settingsPrefs;
} catch (e) {
debugPrint('[Boot] Settings prefs load failed, using defaults: $e');
}
if (!mounted) return;
setState(() => _status = 'Ready');
+38 -5
View File
@@ -1,9 +1,44 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:revoltvpn/logic/app_colors.dart';
// A plain bool, not a ValueNotifier: this is only read at the moment of a tap,
// so nothing has to react to it changing. Default: false.
bool hapticEnabled = false;
/// Hydrates [hapticEnabled] from disk. Called at boot — the toggle tile below
/// only mounts once Settings is opened.
Future<void> loadHapticPref() async {
final prefs = await SharedPreferences.getInstance();
hapticEnabled = prefs.getBool('haptic_feedback_enabled') ?? false;
}
/// Haptic calls are deliberately best-effort: a vibration failure must never
/// block or fail a VPN connect/disconnect. `mediumImpact` is used instead of
/// `lightImpact` — the latter is imperceptible on many Android devices.
class Haptics {
Haptics._();
static Future<void> connectionChanged() async {
if (!hapticEnabled) return;
try {
await HapticFeedback.mediumImpact();
} catch (e) {
debugPrint('[Haptics] Feedback unavailable: $e');
}
}
static Future<void> previewEnabled() async {
try {
await HapticFeedback.selectionClick();
} catch (e) {
debugPrint('[Haptics] Preview unavailable: $e');
}
}
}
class HapticFeedbackTile extends StatefulWidget {
const HapticFeedbackTile({super.key});
@@ -20,12 +55,9 @@ class _HapticFeedbackTileState extends State<HapticFeedbackTile> {
_load();
}
// Loads persisted state and syncs the top-level bool.
Future<void> _load() async {
final prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('haptic_feedback_enabled') ?? false;
hapticEnabled = value;
if (mounted) setState(() => _on = value);
await loadHapticPref();
if (mounted) setState(() => _on = hapticEnabled);
}
// Writes to SharedPreferences AND updates the live bool so the connect
@@ -34,6 +66,7 @@ class _HapticFeedbackTileState extends State<HapticFeedbackTile> {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('haptic_feedback_enabled', value);
hapticEnabled = value;
if (value) unawaited(Haptics.previewEnabled());
setState(() => _on = value);
}
@@ -4,12 +4,13 @@ import 'package:revoltvpn/logic/app_colors.dart';
// ── lib/screens/settings/in_settings/lightning.dart ─────────────────────────
// Toggle for the lightning strike overlay on the main screen.
//
// Same architecture as haptic.dart: exports a top-level bool read synchronously
// by lib/components/lightning.dart. Default: true (lightning is on — opt-out).
// lib/components/lightning.dart reads this in its build method.
bool lightningEnabled = true;
final ValueNotifier<bool> lightningEnabled = ValueNotifier(true);
Future<void> loadLightningPref() async {
final prefs = await SharedPreferences.getInstance();
lightningEnabled.value = prefs.getBool('lightning_effect_enabled') ?? true;
}
class LightningToggleTile extends StatefulWidget {
const LightningToggleTile({super.key});
@@ -28,16 +29,14 @@ class _LightningToggleTileState extends State<LightningToggleTile> {
}
Future<void> _load() async {
final prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('lightning_effect_enabled') ?? true;
lightningEnabled = value;
if (mounted) setState(() => _on = value);
await loadLightningPref();
if (mounted) setState(() => _on = lightningEnabled.value);
}
Future<void> _toggle(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('lightning_effect_enabled', value);
lightningEnabled = value;
lightningEnabled.value = value;
setState(() => _on = value);
}
+9 -10
View File
@@ -4,12 +4,13 @@ import 'package:revoltvpn/logic/app_colors.dart';
// ── lib/screens/settings/in_settings/rain.dart ──────────────────────────────
// Toggle for the animated rain overlay on the main screen.
//
// Same architecture as haptic.dart: exports a top-level bool read synchronously
// by lib/components/rain.dart. Default: true (rain is on — opt-out).
// lib/components/rain.dart reads this in its build method.
bool rainEnabled = true;
final ValueNotifier<bool> rainEnabled = ValueNotifier(true);
Future<void> loadRainPref() async {
final prefs = await SharedPreferences.getInstance();
rainEnabled.value = prefs.getBool('rain_effect_enabled') ?? true;
}
class RainToggleTile extends StatefulWidget {
const RainToggleTile({super.key});
@@ -28,16 +29,14 @@ class _RainToggleTileState extends State<RainToggleTile> {
}
Future<void> _load() async {
final prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('rain_effect_enabled') ?? true;
rainEnabled = value;
if (mounted) setState(() => _on = value);
await loadRainPref();
if (mounted) setState(() => _on = rainEnabled.value);
}
Future<void> _toggle(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('rain_effect_enabled', value);
rainEnabled = value;
rainEnabled.value = value;
setState(() => _on = value);
}