The clock sat at 00:00:00 on a connected tunnel, and the notification countdown froze with it. SessionTimer is built lazily, so when the app reattaches to a tunnel that outlived its UI process, VpnConnection had already reported CONNECTED before the timer existed. A change listener does not replay events it was not present for, so the timer never learned it should be running. The notification text is pushed by that same timer, which is why the countdown froze at the same moment -- one cause, both symptoms. The timer now evaluates the current connection state once at construction rather than waiting for a transition, and is created eagerly alongside VpnConnection so it is listening from the start. Resuming also no longer requires adoptedRunningRuntime or a previous successful sync. Both are false in a freshly started process, which is exactly the case that needed to work. A connected tunnel with a stopped clock is now always resumed. Version 3.3.3+29. Co-Authored-By: Claude Opus 5 <[email protected]>
93 lines
3.0 KiB
Dart
93 lines
3.0 KiB
Dart
import 'dart:ui';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:revoltvpn/logic/ad_manager.dart';
|
|
import 'package:revoltvpn/logic/app_colors.dart';
|
|
import 'package:revoltvpn/logic/connection_settings.dart';
|
|
import 'package:revoltvpn/logic/haptic_settings.dart';
|
|
import 'package:revoltvpn/logic/server_list.dart';
|
|
import 'package:revoltvpn/logic/session_timer.dart';
|
|
import 'package:revoltvpn/logic/vpn_connection.dart';
|
|
import 'package:revoltvpn/screens/intro.dart';
|
|
|
|
Future<void> main() async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
|
|
await HapticSettings.initialize();
|
|
await ConnectionSettings.initialize();
|
|
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
|
|
|
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
|
statusBarColor: Colors.transparent,
|
|
statusBarIconBrightness: Brightness.light,
|
|
systemNavigationBarColor: Color(0xFF0D1117),
|
|
systemNavigationBarIconBrightness: Brightness.light,
|
|
));
|
|
|
|
FlutterError.onError = (details) {
|
|
debugPrint('[FlutterError] ${details.exception}');
|
|
};
|
|
|
|
PlatformDispatcher.instance.onError = (error, stack) {
|
|
debugPrint('[PlatformError] $error\n$stack');
|
|
return true;
|
|
};
|
|
|
|
runApp(const ReVoltApp());
|
|
}
|
|
|
|
class ReVoltApp extends StatelessWidget {
|
|
const ReVoltApp({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MultiProvider(
|
|
providers: [
|
|
ChangeNotifierProvider(create: (_) => VpnConnection(), lazy: false),
|
|
ChangeNotifierProxyProvider<VpnConnection, SessionTimer>(
|
|
// Eager, like VpnConnection: the timer has to be listening before the
|
|
// engine reports an already-running tunnel, not after the intro.
|
|
lazy: false,
|
|
create: (ctx) =>
|
|
SessionTimer(vpnConnection: ctx.read<VpnConnection>()),
|
|
update: (_, vpn, prev) =>
|
|
prev ?? SessionTimer(vpnConnection: vpn),
|
|
),
|
|
ChangeNotifierProvider(create: (_) => AdManager()),
|
|
ChangeNotifierProvider(
|
|
create: (_) => ServerList()..init(),
|
|
lazy: false,
|
|
),
|
|
],
|
|
child: MaterialApp(
|
|
title: 'ReVolt VPN',
|
|
debugShowCheckedModeBanner: false,
|
|
theme: _buildDarkTheme(),
|
|
home: const IntroScreen(),
|
|
),
|
|
);
|
|
}
|
|
|
|
ThemeData _buildDarkTheme() {
|
|
return ThemeData(
|
|
brightness: Brightness.dark,
|
|
scaffoldBackgroundColor: AppColors.bgDeep,
|
|
colorScheme: const ColorScheme.dark(
|
|
primary: AppColors.accent,
|
|
secondary: AppColors.accent,
|
|
surface: AppColors.bgSurface,
|
|
),
|
|
fontFamily: 'Roboto',
|
|
snackBarTheme: SnackBarThemeData(
|
|
backgroundColor: AppColors.bgCard,
|
|
contentTextStyle: const TextStyle(color: AppColors.textWhite),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
behavior: SnackBarBehavior.floating,
|
|
),
|
|
useMaterial3: true,
|
|
);
|
|
}
|
|
}
|