From ddf8b1d4d89d30cbda810e4bc19beb2477d300be Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Mon, 29 Jun 2026 11:52:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(client):=20=E6=99=BA=E8=83=BD=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=8D=A1=E4=B9=9F=E8=B5=B0=E5=88=87=E6=8D=A2=E7=A1=AE?= =?UTF-8?q?=E8=AE=A4(=E9=98=B2=E8=AF=AF=E8=A7=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把节点卡的"已连接时切换先弹确认"逻辑抽成 _onSelect(code,name),节点卡与智能 选择卡共用。点智能选择卡:未连接直接选,已连接/连接中弹确认("切换到「智能选择」?")。 node_connect_confirm_test 增"已连接点智能选择卡弹确认→取消不切"用例。 Co-Authored-By: Claude Opus 4.8 --- client/lib/screens/nodes_page.dart | 23 ++++++++------ .../widget/node_connect_confirm_test.dart | 31 +++++++++++++++++++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/client/lib/screens/nodes_page.dart b/client/lib/screens/nodes_page.dart index 8f5013c..5764d1b 100644 --- a/client/lib/screens/nodes_page.dart +++ b/client/lib/screens/nodes_page.dart @@ -50,17 +50,20 @@ class _NodesPageState extends ConsumerState { } // 点节点:未连接(off)直接选→跳连接页,无破坏性;已连接/连接中切换会断开重连, - // 弹确认防误触(误碰节点把当前连接切走)。 - void _onTapNode(Node n) { - final phase = ref.read(connectionProvider).phase; - if (phase == VpnPhase.off) { - _pick(n.code); + // 弹确认防误触(误碰把当前连接切走)。节点卡与智能选择卡共用此逻辑。 + void _onTapNode(Node n) => _onSelect(n.code, n.localizedName(ref.read(appTextProvider).lang)); + + void _onTapSmart() => _onSelect(kSmartNodeCode, ref.read(appTextProvider).smartSelect); + + void _onSelect(String code, String name) { + if (ref.read(connectionProvider).phase == VpnPhase.off) { + _pick(code); return; } - _confirmSwitch(n); + _confirmSwitch(code, name); } - Future _confirmSwitch(Node n) async { + Future _confirmSwitch(String code, String name) async { final c = context.pangolin; final t = ref.read(appTextProvider); final ok = await showDialog( @@ -68,7 +71,7 @@ class _NodesPageState extends ConsumerState { builder: (ctx) => AlertDialog( backgroundColor: c.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(PangolinRadius.xl)), - title: Text(t.nodeSwitchTitle(n.localizedName(t.lang)), + title: Text(t.nodeSwitchTitle(name), style: PangolinText.body.copyWith(color: c.fg1, fontWeight: FontWeight.w700)), content: Text(t.nodeSwitchBody, style: PangolinText.sm.copyWith(color: c.fg2, height: 1.5)), actions: [ @@ -83,7 +86,7 @@ class _NodesPageState extends ConsumerState { ], ), ); - if (ok == true) _pick(n.code); + if (ok == true) _pick(code); } List _filtered(List nodes) { @@ -102,7 +105,7 @@ class _NodesPageState extends ConsumerState { final nodes = _filtered(ref.watch(nodesProvider).valueOrNull ?? const []); final smart = selected == kSmartNodeCode; - final smartCard = SmartSelectCard(t: t, selected: smart, onTap: () => _pick(kSmartNodeCode)); + final smartCard = SmartSelectCard(t: t, selected: smart, onTap: _onTapSmart); // 搜索框 + 刷新按钮(点击刷新);列表本身支持下拉刷新。 final searchRow = Row(children: [ Expanded(child: _SearchBox(hint: t.searchPh, onChanged: (v) => setState(() => _q = v))), diff --git a/client/test/widget/node_connect_confirm_test.dart b/client/test/widget/node_connect_confirm_test.dart index 476094a..e1e1395 100644 --- a/client/test/widget/node_connect_confirm_test.dart +++ b/client/test/widget/node_connect_confirm_test.dart @@ -14,6 +14,7 @@ import 'package:pangolin_vpn/pangolin_theme.dart'; import 'package:pangolin_vpn/screens/nodes_page.dart'; import 'package:pangolin_vpn/state/connection_provider.dart'; import 'package:pangolin_vpn/state/nodes_provider.dart'; +import 'package:pangolin_vpn/widgets/smart_select_card.dart'; import '../helpers/harness.dart'; @@ -98,4 +99,34 @@ void main() { await bridge.stop(); await tester.pump(); }); + + testWidgets('已连接:点智能选择卡也弹确认 → 取消不切换', (tester) async { + final bridge = VpnBridgeMock(connectDelay: const Duration(milliseconds: 10)); + final container = makeContainer(bridge); + addTearDown(bridge.dispose); + addTearDown(container.dispose); + // 先选具体节点(否则默认就是智能选择),便于验证取消不切到智能。 + container.read(selectedNodeCodeProvider.notifier).state = 'HK'; + await pump(tester, container); + + container.read(connectionProvider); + await bridge.start('{}'); + await tester.pump(const Duration(milliseconds: 15)); + expect(container.read(connectionProvider).phase, VpnPhase.on); + + await tester.tap(find.byType(SmartSelectCard)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 350)); + + expect(find.text(t.nodeSwitchTitle(t.smartSelect)), findsOneWidget, reason: '智能选择也应弹确认'); + expect(container.read(selectedNodeCodeProvider), 'HK', reason: '弹窗期间未切到智能'); + + await tester.tap(find.text(t.devCancel)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 350)); + expect(container.read(selectedNodeCodeProvider), 'HK', reason: '取消后维持原节点'); + + await bridge.stop(); + await tester.pump(); + }); }