feat(bridge): 桥接 API 面 Channel 契约 + 三端原生骨架 [tsk_iP5-_Ztq5THx]

MethodChannel pangolin/vpn (start/stop/getStatus/selectOutbound/
getActiveOutbound/setKillSwitch) + 双 EventChannel (status/stats)
契约定义在 lib/bridge/vpn_bridge.dart 头部注释冻结。

- Dart: VpnBridge 抽象接口 + VpnNativeBridge 原生实现 + VpnBridgeMock
  假内核(含 connectDelay/statsInterval 可配参数)
- Dart 桌面: kernel_process.dart KernelProcess/ClashApiClient 接口骨架
- Android: PangolinVpnService (VpnService 骨架 + 前台通知占位) +
  VpnEventBus 解耦总线 + MainActivity MethodChannel/EventChannel 注册
- iOS: PacketTunnelProvider (NEPacketTunnelProvider 骨架) + VpnManager
  (NETunnelProviderManager + NEVPNStatus 订阅) + AppDelegate 通道注册
  + Xcode project.pbxproj 添加 PacketTunnel extension target
- Widget: VpnStatus enum 迁移至 bridge/vpn_bridge.dart (+error 态),
  connect_button/home_shell 更新穷举匹配
- Test: test/bridge/vpn_bridge_mock_test.dart 覆盖 start→connecting→on
  →stop→off 序列、stats 周期推帧、selectOutbound 重连序列、schema 回测

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-13 12:06:03 +08:00
parent 787151245e
commit 4992d916d0
16 changed files with 1677 additions and 11 deletions
+6 -1
View File
@@ -1,9 +1,12 @@
// connect_button.dart — 核心连接键(三态:off / connecting / on)
// VpnStatus 枚举定义在 lib/bridge/vpn_bridge.dart(含 error 扩展态)
import 'package:flutter/material.dart';
import '../bridge/vpn_bridge.dart' show VpnStatus;
import '../pangolin_theme.dart';
import 'pangolin_icons.dart';
enum VpnStatus { off, connecting, on }
// 重导出便于其他 widget 从 connect_button.dart 引入(向后兼容)
export '../bridge/vpn_bridge.dart' show VpnStatus;
class ConnectButton extends StatefulWidget {
const ConnectButton({
@@ -47,12 +50,14 @@ class _ConnectButtonState extends State<ConnectButton> with SingleTickerProvider
VpnStatus.off => c.bgSubtle,
VpnStatus.connecting => c.accent,
VpnStatus.on => c.success,
VpnStatus.error => c.danger,
};
final Color fg = s == VpnStatus.off ? c.accent : PangolinColors.white;
final IconData icon = switch (s) {
VpnStatus.off => PangolinIcons.power,
VpnStatus.connecting => PangolinIcons.loader,
VpnStatus.on => PangolinIcons.shieldCheck,
VpnStatus.error => PangolinIcons.power,
};
final List<BoxShadow> glow = s == VpnStatus.off
+1
View File
@@ -117,6 +117,7 @@ class _ConnectPage extends StatelessWidget {
VpnStatus.off => zh ? '未连接 · 轻点连接' : 'Tap to connect',
VpnStatus.connecting => zh ? '连接中…' : 'Connecting…',
VpnStatus.on => zh ? '已连接 · 网络已加密' : 'Connected · Encrypted',
VpnStatus.error => zh ? '连接错误' : 'Connection error',
};
return Column(children: [
_TopBar(zh: zh, trailing: Text(status == VpnStatus.on ? (zh ? '● 在线' : '● Online') : (zh ? '○ 离线' : '○ Offline'),