fix(client): 设备页每 15s 静默刷新列表,其他设备状态变化 ~15s 内可见

原设备列表打开时加载一次就不动(感觉十几分钟才同步)。加透传式轮询包装器
_DevicesAutoRefresh:在设备页期间每 15s 调 DevicesNotifier.refresh()(静默、不闪 loading、
失败保留旧列表)。配合服务端会话轮询刷 last_seen + 90s 窗口,其他设备上/下线 ~15s 内反映。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-30 08:05:32 +08:00
parent 3167ca8a74
commit d192d96d95
2 changed files with 45 additions and 1 deletions
+12
View File
@@ -89,6 +89,18 @@ class DevicesNotifier extends AsyncNotifier<List<Device>> {
await future;
}
/// 静默刷新(不闪 loading):重新拉列表、成功才替换。供设备页 ~15s 轮询,让其他设备的
/// 在线/状态变化 ~15s 内可见(配合服务端会话轮询刷 last_seen)。
Future<void> refresh() async {
if (!ref.read(authProvider).isLoggedIn) return;
try {
final list = await ref.read(accountApiProvider).devices();
state = AsyncData(list);
} catch (_) {
// 失败保留旧列表,不闪错。
}
}
/// 强制退出(吊销该设备会话,设备保留)后刷新列表。
Future<void> forceLogout(String uuid) async {
await ref.read(accountApiProvider).forceLogout(uuid);
+33 -1
View File
@@ -2,6 +2,8 @@
//
// 文案全部经 AppText(l10n,单显)。套餐数字以 design/CLAUDE.md §7 为准。
// App 内无任何支付表单——购买仅引导至外部渠道。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -162,7 +164,8 @@ class DevicesScreen extends ConsumerWidget {
]),
);
return _SubScaffold(title: t.myDevices, onBack: onBack, embedded: embedded, child: content());
return _SubScaffold(
title: t.myDevices, onBack: onBack, embedded: embedded, child: _DevicesAutoRefresh(child: content()));
}
Widget _row(BuildContext context, WidgetRef ref, PangolinScheme c, Device d, bool isLocal, bool divider) {
@@ -493,3 +496,32 @@ class ContactScreen extends StatelessWidget {
);
}
}
/// 透传式轮询包装器:在设备页期间每 15s 静默刷新设备列表(不闪 loading),
/// 让其他设备的在线/状态变化 ~15s 内可见。原样返回 child,不改布局。
class _DevicesAutoRefresh extends ConsumerStatefulWidget {
const _DevicesAutoRefresh({required this.child});
final Widget child;
@override
ConsumerState<_DevicesAutoRefresh> createState() => _DevicesAutoRefreshState();
}
class _DevicesAutoRefreshState extends ConsumerState<_DevicesAutoRefresh> {
Timer? _poll;
@override
void initState() {
super.initState();
_poll = Timer.periodic(const Duration(seconds: 15), (_) {
if (mounted) ref.read(devicesProvider.notifier).refresh();
});
}
@override
void dispose() {
_poll?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) => widget.child;
}