flutter_v2ray_client 1.1.0
flutter_v2ray_client: ^1.1.0 copied to clipboard
Flutter client plugin for Xray/V2Ray on Android (VPN mode & proxy-only) with modernized API and docs.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_v2ray_client/flutter_v2ray.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter V2Ray',
theme: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(),
),
),
home: const Scaffold(body: HomePage()),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
var v2rayStatus = ValueNotifier<V2RayStatus>(V2RayStatus());
late final V2ray flutterV2ray = V2ray(
onStatusChanged: (status) {
v2rayStatus.value = status;
},
);
final config = TextEditingController();
bool proxyOnly = false;
final bypassSubnetController = TextEditingController();
List<String> bypassSubnets = [];
String? coreVersion;
String remark = "Default Remark";
void connect() async {
if (await flutterV2ray.requestPermission()) {
flutterV2ray.startV2Ray(
remark: remark,
config: config.text,
proxyOnly: proxyOnly,
bypassSubnets: bypassSubnets,
notificationDisconnectButtonName: "DISCONNECT",
);
} else {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Permission Denied')));
}
}
}
void importConfig() async {
if (await Clipboard.hasStrings()) {
try {
final String link =
(await Clipboard.getData('text/plain'))?.text?.trim() ?? '';
final V2RayURL v2rayURL = V2ray.parseFromURL(link);
remark = v2rayURL.remark;
config.text = v2rayURL.getFullConfiguration();
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Success')));
}
} catch (error) {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error: $error')));
}
}
}
}
void delay() async {
late int delay;
if (v2rayStatus.value.state == 'CONNECTED') {
delay = await flutterV2ray.getConnectedServerDelay();
} else {
delay = await flutterV2ray.getServerDelay(config: config.text);
}
if (!mounted) return;
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('${delay}ms')));
}
void bypassSubnet() {
bypassSubnetController.text = bypassSubnets.join("\n");
showDialog(
context: context,
builder:
(context) => Dialog(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Text('Subnets:', style: TextStyle(fontSize: 16)),
const SizedBox(height: 5),
TextFormField(
controller: bypassSubnetController,
maxLines: 5,
minLines: 5,
),
const SizedBox(height: 5),
ElevatedButton(
onPressed: () {
bypassSubnets = bypassSubnetController.text.trim().split(
'\n',
);
if (bypassSubnets.first.isEmpty) {
bypassSubnets = [];
}
Navigator.of(context).pop();
},
child: const Text('Submit'),
),
],
),
),
),
);
}
@override
void initState() {
super.initState();
flutterV2ray
.initialize(
notificationIconResourceType: "mipmap",
notificationIconResourceName: "ic_launcher",
)
.then((value) async {
coreVersion = await flutterV2ray.getCoreVersion();
setState(() {});
});
}
@override
void dispose() {
config.dispose();
bypassSubnetController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 5),
const Text('V2Ray Config (json):', style: TextStyle(fontSize: 16)),
const SizedBox(height: 5),
TextFormField(controller: config, maxLines: 10, minLines: 10),
const SizedBox(height: 10),
ValueListenableBuilder(
valueListenable: v2rayStatus,
builder: (context, value, child) {
return Column(
children: [
Text(value.state),
const SizedBox(height: 10),
Text(value.duration),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Speed:'),
const SizedBox(width: 10),
Text(value.uploadSpeed.toString()),
const Text('↑'),
const SizedBox(width: 10),
Text(value.downloadSpeed.toString()),
const Text('↓'),
],
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Traffic:'),
const SizedBox(width: 10),
Text(value.upload.toString()),
const Text('↑'),
const SizedBox(width: 10),
Text(value.download.toString()),
const Text('↓'),
],
),
const SizedBox(height: 10),
Text('Core Version: $coreVersion'),
],
);
},
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.all(5.0),
child: Wrap(
spacing: 5,
runSpacing: 5,
children: [
ElevatedButton(
onPressed: connect,
child: const Text('Connect'),
),
ElevatedButton(
onPressed: () => flutterV2ray.stopV2Ray(),
child: const Text('Disconnect'),
),
ElevatedButton(
onPressed: () => setState(() => proxyOnly = !proxyOnly),
child: Text(proxyOnly ? 'Proxy Only' : 'VPN Mode'),
),
ElevatedButton(
onPressed: importConfig,
child: const Text(
'Import from v2ray share link (clipboard)',
),
),
ElevatedButton(
onPressed: delay,
child: const Text('Server Delay'),
),
ElevatedButton(
onPressed: bypassSubnet,
child: const Text('Bypass Subnet'),
),
],
),
),
],
),
),
);
}
}