feat(client): 记住上次选择的节点 + 自动连接 toast(1.0.24)
- selectedNodeCodeProvider 改为持久化 StateNotifier(select() 落盘、启动恢复,默认智能 AUTO); 自动连接复用上次选择:智能就智能、具体就具体。 - 自动连接门控加等「选中节点已恢复」(controller.loaded),避免用到未恢复的默认值。 - 自动连接触发时弹 toast「正在自动连接到 X」(全局 ScaffoldMessenger key)。 - 测试:选中节点 select 落盘 + 重启恢复(具体/智能)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+18
-2
@@ -23,6 +23,9 @@ import 'widgets/auth_screen.dart';
|
||||
import 'widgets/onboarding_screen.dart';
|
||||
import 'widgets/pangolin_logo.dart';
|
||||
|
||||
/// 全局 ScaffoldMessenger key:供无 BuildContext 处(如自动连接)弹 SnackBar/toast。
|
||||
final rootMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
// 桌面端:单实例 + 常驻系统托盘(关闭窗口隐藏到托盘而非退出)。
|
||||
@@ -98,6 +101,7 @@ class _PangolinAppState extends ConsumerState<PangolinApp> {
|
||||
final mode = ref.watch(themeModeProvider);
|
||||
return MaterialApp(
|
||||
title: '穿山甲',
|
||||
scaffoldMessengerKey: rootMessengerKey,
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: PangolinTheme.light,
|
||||
darkTheme: PangolinTheme.dark,
|
||||
@@ -131,22 +135,33 @@ class _RootFlowState extends ConsumerState<RootFlow> {
|
||||
logLine('AutoConnect', 'armed; waiting for settings loaded + nodes ready');
|
||||
ProviderSubscription<AsyncValue<List<Node>>>? nodeSub;
|
||||
ProviderSubscription<AppSettings>? settingsSub;
|
||||
ProviderSubscription<String>? selSub;
|
||||
var decided = false;
|
||||
void tryGo() {
|
||||
if (decided) return;
|
||||
final settings = ref.read(settingsProvider);
|
||||
final selLoaded = ref.read(selectedNodeCodeProvider.notifier).loaded;
|
||||
// 直接判 _connect 实际会用的「生效节点」是否就绪(uuid 非空),而非「列表里有任一就绪节点」——
|
||||
// 否则可能在 effectiveNode 尚是占位/未就绪时就触发,_connect 报「节点尚未就绪」。
|
||||
// 还要等设置 + 选中节点都从持久化恢复完,否则可能用到未恢复的默认(智能)而非上次选择。
|
||||
final node = ref.read(effectiveNodeProvider);
|
||||
if (!settings.loaded || node.uuid.isEmpty) return;
|
||||
if (!settings.loaded || !selLoaded || node.uuid.isEmpty) return;
|
||||
decided = true;
|
||||
nodeSub?.close();
|
||||
settingsSub?.close();
|
||||
selSub?.close();
|
||||
final phase = ref.read(connectionProvider).phase;
|
||||
logLine('AutoConnect', 'ready: autoConnect=${settings.autoConnect} phase=$phase node=${node.code} uuid=${node.uuid.isEmpty ? "EMPTY" : "ok"}');
|
||||
if (settings.autoConnect && phase == VpnPhase.off) {
|
||||
logLine('AutoConnect', 'triggering connect');
|
||||
logLine('AutoConnect', 'triggering connect → ${node.code}');
|
||||
ref.read(connectionProvider.notifier).toggle();
|
||||
// 自动连接 toast:告知用户「正在自动连接到 X」。
|
||||
final t = ref.read(appTextProvider);
|
||||
rootMessengerKey.currentState?.showSnackBar(SnackBar(
|
||||
content: Text(t.autoConnectingTo(node.localizedName(t.lang))),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 3),
|
||||
));
|
||||
} else {
|
||||
logLine('AutoConnect', 'NOT connecting (autoConnect=${settings.autoConnect} phase=$phase)');
|
||||
}
|
||||
@@ -154,6 +169,7 @@ class _RootFlowState extends ConsumerState<RootFlow> {
|
||||
|
||||
nodeSub = ref.listenManual(nodesProvider, (_, __) => tryGo());
|
||||
settingsSub = ref.listenManual(settingsProvider, (_, __) => tryGo());
|
||||
selSub = ref.listenManual(selectedNodeCodeProvider, (_, __) => tryGo());
|
||||
tryGo();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user