fix(app): resume the session clock when attaching to a live tunnel
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]>
This commit is contained in:
1 parent
186dc906da
commit
dfeb7f161c
4 files changed
+31
-11
No files matched your search
@@ -5,6 +5,21 @@ Versions follow [semver](https://semver.org/): MAJOR.MINOR.PATCH
|
||||
|
||||
---
|
||||
|
||||
## [3.3.3] — 2026-09-01
|
||||
|
||||
### Fixed
|
||||
- **The session clock stayed at 00:00:00 on a live 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 — and a change listener does not replay events
|
||||
it was not present for. The timer now evaluates the current connection state once at
|
||||
construction instead of waiting for a transition, and is created eagerly alongside
|
||||
`VpnConnection`. Because the timer is what pushes text to the foreground
|
||||
notification, a stopped clock also meant a frozen countdown; both come from this.
|
||||
- Resuming the clock no longer requires `adoptedRunningRuntime` or a previous
|
||||
successful sync, both of which are false in a freshly started process. A connected
|
||||
tunnel with a stopped clock is now always treated as something to resume.
|
||||
|
||||
## [3.3.2] — 2026-09-01
|
||||
|
||||
Background reliability. 3.3.1 could show "connected" against a tunnel that was no
|
||||
|
||||
@@ -47,6 +47,11 @@ class SessionTimer extends ChangeNotifier with WidgetsBindingObserver {
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
vpnConnection.addListener(_onVpnConnectionChanged);
|
||||
unawaited(_loadSupportRewardState());
|
||||
// This provider is built lazily, so the tunnel may already be up and
|
||||
// VpnConnection may already have fired its change before we attached.
|
||||
// A listener does not replay history -- evaluate the current state once.
|
||||
// Deferred so we never notify during the build that created us.
|
||||
unawaited(Future.microtask(_onVpnConnectionChanged));
|
||||
}
|
||||
|
||||
int get remaining => _remainingSeconds;
|
||||
@@ -102,18 +107,15 @@ class SessionTimer extends ChangeNotifier with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
void _onVpnConnectionChanged() {
|
||||
// A live tunnel with a stopped clock is always wrong, however we got here:
|
||||
// adopting a tunnel that outlived the UI process, recovering from a blip, or
|
||||
// attaching late to one that was already up. Previously this required either
|
||||
// adoptedRunningRuntime or _hasSyncedOnce, both false on a fresh process, so
|
||||
// the clock stayed at 00:00:00 and the notification countdown froze with it.
|
||||
if (vpnConnection.status == VpnStatus.connected &&
|
||||
!isRunning &&
|
||||
vpnConnection.adoptedRunningRuntime) {
|
||||
_resumeTicking();
|
||||
return;
|
||||
}
|
||||
|
||||
if (vpnConnection.status == VpnStatus.connected &&
|
||||
!isRunning &&
|
||||
!_isDisconnecting &&
|
||||
_hasSyncedOnce) {
|
||||
debugPrint('[Timer] VPN reconnected after blip - resuming.');
|
||||
!_isDisconnecting) {
|
||||
debugPrint('[Timer] Connected with a stopped clock - resuming.');
|
||||
_resumeTicking();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ class ReVoltApp extends StatelessWidget {
|
||||
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) =>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 3.3.2+28
|
||||
version: 3.3.3+29
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
Reference in new issue
Block a user