fix(client): 连接时长按墙上时钟算,切后台/锁屏不漏计

iOS 锁屏/切后台后连接页时长少算:elapsed 原来靠每秒 +1 的 Timer 累加,后台
isolate 被挂起、Timer 停跳 → 后台那段时长漏掉。改为记 _connectedAt 起点,
elapsed = now - _connectedAt(墙上时钟),与 timer 是否跳无关;回前台(resumed)
立即 _refreshElapsed 补上。_connectedAt 用 ??= 保留同一会话起点,断开才清。

测试:新增「切后台 300s 时长不漏计」回归(前台 10s + 后台 300s = 310s);全 8 例过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-01 09:53:49 +08:00
parent 002eea7562
commit 58211d2fb8
2 changed files with 48 additions and 6 deletions
@@ -233,6 +233,35 @@ void main() {
await tester.pump();
});
testWidgets('连接时长按墙上时钟:切后台的时长不漏计', (tester) async {
final bridge = _FakeBridge();
var fake = DateTime(2026, 1, 1);
final c = makeContainer(bridge, now: () => fake);
addTearDown(bridge.dispose);
addTearDown(c.dispose);
await tester.pumpWidget(UncontrolledProviderScope(container: c, child: const SizedBox()));
c.read(selectedNodeCodeProvider.notifier).select('HK');
await driveOn(tester, c, bridge); // on:_connectedAt=t0
// 前台 10s:墙上时钟 +10、timer 跳一次。
fake = fake.add(const Duration(seconds: 10));
await tester.pump(const Duration(seconds: 1));
expect(c.read(connectionProvider).elapsed, const Duration(seconds: 10));
// 切后台(timer 冻结:只推墙上时钟、不 pump timer 间隔),过 300s。
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive);
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.hidden);
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.paused);
fake = fake.add(const Duration(seconds: 300));
// 回前台:应按墙上时钟补上后台 300s → 共 310s,而非只有前台的 10s。
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.hidden);
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive);
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
await tester.pump();
expect(c.read(connectionProvider).elapsed, const Duration(seconds: 310),
reason: '后台 300s 应计入连接时长');
bridge.emit(VpnStatus.off); // 收尾
await tester.pump();
});
testWidgets('节点健康 → 保持连接,不触发任何动作', (tester) async {
final bridge = _FakeBridge();
final c = makeContainer(bridge); // 都 up