diff --git a/lib/components/clock_display.dart b/lib/components/clock_display.dart index fc0ad2f..85c9a35 100644 --- a/lib/components/clock_display.dart +++ b/lib/components/clock_display.dart @@ -22,31 +22,6 @@ class ClockDisplay extends StatelessWidget { child: Stack( alignment: Alignment.center, children: [ - // Background quota track ring - const CustomPaint( - size: Size(200, 200), - painter: _ArcPainter( - progress: 1.0, - color: Color.fromRGBO(74, 85, 104, 0.2), - strokeWidth: 4, - ), - ), - - // Live quota progress ring - TweenAnimationBuilder( - tween: Tween(begin: 0, end: timer.progress), - duration: const Duration(milliseconds: 400), - builder: (context, value, _) => CustomPaint( - size: const Size(200, 200), - painter: _ArcPainter( - progress: value, - color: _cyanGlow, - strokeWidth: 4, - glow: true, - ), - ), - ), - // Center content: countdown + speed Column( mainAxisSize: MainAxisSize.min, @@ -94,45 +69,3 @@ class ClockDisplay extends StatelessWidget { ); } } - -class _ArcPainter extends CustomPainter { - final double progress; - final Color color; - final double strokeWidth; - final bool glow; - - const _ArcPainter({ - required this.progress, - required this.color, - this.strokeWidth = 4, - this.glow = false, - }); - - @override - void paint(Canvas canvas, Size size) { - final rect = Rect.fromLTWH(0, 0, size.width, size.height) - .deflate(strokeWidth / 2); - - if (glow) { - final glowPaint = Paint() - ..color = const Color.fromRGBO(0, 229, 255, 0.15) - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth + 8 - ..strokeCap = StrokeCap.round - ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 8); - canvas.drawArc(rect, -pi / 2, 2 * pi * progress, false, glowPaint); - } - - final paint = Paint() - ..color = color - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth - ..strokeCap = StrokeCap.round; - - canvas.drawArc(rect, -pi / 2, 2 * pi * progress, false, paint); - } - - @override - bool shouldRepaint(covariant _ArcPainter old) => - old.progress != progress || old.color != color; -} diff --git a/lib/logic/session_timer.dart b/lib/logic/session_timer.dart index 6694945..5d11a37 100644 --- a/lib/logic/session_timer.dart +++ b/lib/logic/session_timer.dart @@ -27,7 +27,19 @@ class SessionTimer extends ChangeNotifier { int _lastUsedBytes = 0; double _currentSpeedKbps = 0.0; // Total speed (RX+TX) - SessionTimer({required this.vpnConnection}); + SessionTimer({required this.vpnConnection}) { + vpnConnection.addListener(_onVpnConnectionChanged); + } + + /// Fires whenever VpnConnection state changes. + /// Only acts during startup restoration — normal connects go through ConnectButton. + void _onVpnConnectionChanged() { + if (vpnConnection.status == VpnStatus.connected && + !isRunning && + vpnConnection.isStartupRestoration) { + start('resume'); + } + } int get remaining => _remainingSeconds; bool get isRunning => _timer != null && _timer!.isActive; @@ -181,6 +193,7 @@ class SessionTimer extends ChangeNotifier { @override void dispose() { + vpnConnection.removeListener(_onVpnConnectionChanged); _timer?.cancel(); VpnNotificationManager.cancel(); super.dispose(); diff --git a/lib/logic/vpn_connection.dart b/lib/logic/vpn_connection.dart index d312ef8..1206685 100644 --- a/lib/logic/vpn_connection.dart +++ b/lib/logic/vpn_connection.dart @@ -43,6 +43,11 @@ class VpnConnection extends ChangeNotifier { VpnStatus _status = VpnStatus.disconnected; VpnStatus get status => _status; + // True only during the brief window after startup where we detected an + // already-running tunnel — lets SessionTimer know to auto-resume. + bool _isStartupRestoration = false; + bool get isStartupRestoration => _isStartupRestoration; + // Short message shown to the user under the connect button String _statusMessage = 'Tap to connect'; String get statusMessage => _statusMessage; @@ -84,6 +89,20 @@ class VpnConnection extends ChangeNotifier { try { _stageSub = _wireguard.vpnStageSnapshot.listen(_mapStage); } catch (_) {} + + // Check if the VPN was already running before the app started. + // This handles the case where the user swiped the app away and reopened it. + try { + final currentStage = await _wireguard.stage(); + if (currentStage == VpnStage.connected) { + _isStartupRestoration = true; + _mapStage(currentStage); + // Clear the flag after a few seconds — only needed during app startup + Future.delayed(const Duration(seconds: 5), () { + _isStartupRestoration = false; + }); + } + } catch (_) {} } // ── Native State Mapping ────────────────────────────────────────────────── diff --git a/pubspec.yaml b/pubspec.yaml index 91642f0..b43792b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: 1.0.1+3 +version: 1.0.2+4 environment: sdk: ">=3.0.0 <4.0.0"