vpn_plugin 0.1.0
vpn_plugin: ^0.1.0 copied to clipboard
Flutter VPN engine embedding the sing-box core (iOS NetworkExtension / Android VpnService), with share-link parsing and config generation. GPL-3.0.
import 'package:flutter/material.dart';
import 'package:vpn_plugin/vpn_plugin.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatefulWidget {
const ExampleApp({super.key});
@override
State<ExampleApp> createState() => _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
final _vpn = SingboxVpn();
final _link = TextEditingController();
VpnState _state = VpnState.disconnected;
String? _message;
String _version = '';
@override
void initState() {
super.initState();
_vpn.statusStream().listen((s) => setState(() {
_state = s.state;
_message = s.message;
}));
_vpn.coreVersion().then((v) => setState(() => _version = v));
}
@override
void dispose() {
_link.dispose();
super.dispose();
}
Future<void> _toggle() async {
if (_state.isActive) {
await _vpn.stop();
return;
}
final outbound = parseShareLink(_link.text.trim());
if (outbound == null) {
_snack('无法解析该链接');
return;
}
try {
await _vpn.startOutbound(outbound, options: const SingboxConfigOptions(routeMode: 'rule'));
} catch (e) {
_snack('连接失败:$e');
}
}
void _snack(String m) =>
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(m)));
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('vpn_plugin ($_version)')),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
controller: _link,
minLines: 2,
maxLines: 4,
decoration: const InputDecoration(
labelText: '分享链接 (vless:// / vmess:// / ...)',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
Text('状态: ${_state.name}${_message != null ? ' — $_message' : ''}'),
const SizedBox(height: 16),
FilledButton(
onPressed: _state.isBusy ? null : _toggle,
child: Text(_state.isActive ? '断开' : '连接'),
),
],
),
),
),
);
}
}