145 lines
3.6 KiB
Dart
145 lines
3.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_v2ray_client/flutter_v2ray.dart';
|
|
import 'package:ionicons/ionicons.dart';
|
|
|
|
class MainView extends StatefulWidget {
|
|
const MainView({super.key});
|
|
|
|
@override
|
|
State<MainView> createState() => _MainViewState();
|
|
}
|
|
|
|
class _MainViewState extends State<MainView> {
|
|
bool _toogleFlag = false;
|
|
bool _isProcessing = false;
|
|
String _connectionState = 'DISCONNECTED';
|
|
String _config = "";
|
|
late final V2ray _v2ray = V2ray(
|
|
onStatusChanged: (status) {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_connectionState = status.state;
|
|
_toogleFlag = status.state == 'CONNECTED';
|
|
_isProcessing = false;
|
|
});
|
|
},
|
|
);
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_initializeV2Ray();
|
|
}
|
|
|
|
Future<void> _initializeV2Ray() async {
|
|
try {
|
|
await _v2ray.initialize();
|
|
} catch (e) {
|
|
_showMessage('Init error: $e');
|
|
}
|
|
}
|
|
|
|
void _showMessage(String message) {
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(
|
|
context,
|
|
).showSnackBar(SnackBar(content: Text(message)));
|
|
}
|
|
|
|
Future<void> _setConfig() async {
|
|
try {
|
|
final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
|
|
final text = clipboardData?.text?.trim() ?? "";
|
|
if (text.isEmpty) {
|
|
_showMessage('Clipboard is empty');
|
|
return;
|
|
}
|
|
setState(() {
|
|
_config = text;
|
|
});
|
|
_showMessage('Config link loaded from clipboard');
|
|
} catch (e) {
|
|
_showMessage('Clipboard read error: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> _toogleConfig() async {
|
|
if (_isProcessing) return;
|
|
setState(() {
|
|
_isProcessing = true;
|
|
});
|
|
try {
|
|
if (_toogleFlag) {
|
|
await _v2ray.stopV2Ray();
|
|
if (mounted) {
|
|
setState(() {
|
|
_connectionState = 'DISCONNECTED';
|
|
_toogleFlag = false;
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
final link = _config.trim();
|
|
if (link.isEmpty) {
|
|
_showMessage('Config link is empty');
|
|
return;
|
|
}
|
|
final parsed = V2ray.parseFromURL(link);
|
|
final granted = await _v2ray.requestPermission();
|
|
if (!granted) {
|
|
_showMessage('VPN permission denied');
|
|
return;
|
|
}
|
|
await _v2ray.startV2Ray(
|
|
remark: parsed.remark.isEmpty ? 'VPN' : parsed.remark,
|
|
config: parsed.getFullConfiguration(),
|
|
proxyOnly: false,
|
|
);
|
|
if (mounted) {
|
|
setState(() {
|
|
_connectionState = 'CONNECTING';
|
|
});
|
|
}
|
|
} catch (e) {
|
|
_showMessage('Error: $e');
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_isProcessing = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text('Status: $_connectionState'),
|
|
const SizedBox(height: 12),
|
|
OutlinedButton.icon(
|
|
onPressed: _isProcessing ? null : _setConfig,
|
|
icon: _isProcessing
|
|
? const SizedBox(
|
|
width: 16,
|
|
height: 16,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
)
|
|
: Icon(_toogleFlag ? Ionicons.stop : Ionicons.play),
|
|
label: Text(_toogleFlag ? "Stop" : "Start"),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: _toogleConfig,
|
|
child: Icon(Ionicons.create),
|
|
),
|
|
);
|
|
}
|
|
}
|