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:
@@ -89,6 +89,18 @@ class DevicesNotifier extends AsyncNotifier<List<Device>> {
|
|||||||
await future;
|
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 {
|
Future<void> forceLogout(String uuid) async {
|
||||||
await ref.read(accountApiProvider).forceLogout(uuid);
|
await ref.read(accountApiProvider).forceLogout(uuid);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
//
|
//
|
||||||
// 文案全部经 AppText(l10n,单显)。套餐数字以 design/CLAUDE.md §7 为准。
|
// 文案全部经 AppText(l10n,单显)。套餐数字以 design/CLAUDE.md §7 为准。
|
||||||
// App 内无任何支付表单——购买仅引导至外部渠道。
|
// App 内无任何支付表单——购买仅引导至外部渠道。
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.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) {
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user