136 lines
3.7 KiB
Dart
136 lines
3.7 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:v2ray_box/v2ray_box.dart';
|
|
|
|
const MethodChannel _quickTileControlChannel = MethodChannel(
|
|
'vpn/quick_tile_control',
|
|
);
|
|
|
|
final class _QuickTileController {
|
|
final V2rayBox _box = V2rayBox();
|
|
bool _isInitialized = false;
|
|
StreamSubscription<VpnStatus>? _statusSubscription;
|
|
VpnStatus _lastStatus = VpnStatus.stopped;
|
|
|
|
Future<Map<String, dynamic>> handle(MethodCall call) async {
|
|
switch (call.method) {
|
|
case 'ping':
|
|
return <String, dynamic>{
|
|
'success': true,
|
|
'ready': true,
|
|
'status': _lastStatus.name,
|
|
'connected': _isConnectedStatus(_lastStatus),
|
|
};
|
|
case 'getStatus':
|
|
return _statusPayload();
|
|
case 'toggleVpn':
|
|
return _toggleVpn(call.arguments);
|
|
default:
|
|
return <String, dynamic>{
|
|
'success': false,
|
|
'connected': false,
|
|
'message': 'Unsupported method: ${call.method}',
|
|
'errorCode': 'unsupported_method',
|
|
'openApp': false,
|
|
};
|
|
}
|
|
}
|
|
|
|
Future<void> _ensureInitialized() async {
|
|
if (_isInitialized) {
|
|
return;
|
|
}
|
|
|
|
await _box.initialize(notificationStopButtonText: 'Stop');
|
|
_statusSubscription ??= _box.watchStatus().listen((status) {
|
|
_lastStatus = status;
|
|
});
|
|
_isInitialized = true;
|
|
}
|
|
|
|
bool _isConnectedStatus(VpnStatus status) {
|
|
return status == VpnStatus.started;
|
|
}
|
|
|
|
Map<String, dynamic> _statusPayload() {
|
|
return <String, dynamic>{
|
|
'success': true,
|
|
'ready': _isInitialized,
|
|
'status': _lastStatus.name,
|
|
'connected': _isConnectedStatus(_lastStatus),
|
|
};
|
|
}
|
|
|
|
Future<Map<String, dynamic>> _toggleVpn(dynamic arguments) async {
|
|
final args = arguments is Map
|
|
? Map<String, dynamic>.from(arguments)
|
|
: <String, dynamic>{};
|
|
final String link = (args['link'] as String? ?? '').trim();
|
|
final String name = (args['name'] as String? ?? '').trim();
|
|
final bool proxyOnly = args['proxyOnly'] as bool? ?? false;
|
|
final bool connected = args['connected'] as bool? ?? false;
|
|
|
|
if (link.isEmpty) {
|
|
return <String, dynamic>{
|
|
'success': false,
|
|
'connected': false,
|
|
'message': 'Config is empty',
|
|
'errorCode': 'empty_config',
|
|
'openApp': true,
|
|
};
|
|
}
|
|
|
|
try {
|
|
await _ensureInitialized();
|
|
|
|
if (connected) {
|
|
final bool stopped = await _box.disconnect();
|
|
return <String, dynamic>{
|
|
'success': stopped,
|
|
'connected': false,
|
|
'message': stopped ? null : 'Failed to disconnect',
|
|
'errorCode': stopped ? null : 'disconnect_failed',
|
|
'openApp': false,
|
|
'status': _lastStatus.name,
|
|
};
|
|
}
|
|
|
|
await _box.setServiceMode(proxyOnly ? VpnMode.proxy : VpnMode.vpn);
|
|
final bool started = await _box.connect(
|
|
link,
|
|
name: name.isEmpty ? 'VPN' : name,
|
|
);
|
|
|
|
return <String, dynamic>{
|
|
'success': started,
|
|
'connected': started,
|
|
'message': started ? null : 'Failed to connect',
|
|
'errorCode': started ? null : 'connect_failed',
|
|
'openApp': !started,
|
|
'status': _lastStatus.name,
|
|
};
|
|
} catch (error) {
|
|
return <String, dynamic>{
|
|
'success': false,
|
|
'connected': connected,
|
|
'message': 'Error: $error',
|
|
'errorCode': 'exception',
|
|
'openApp': true,
|
|
'status': _lastStatus.name,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
@pragma('vm:entry-point')
|
|
Future<void> quickTileDispatcher() async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
|
|
final controller = _QuickTileController();
|
|
_quickTileControlChannel.setMethodCallHandler(controller.handle);
|
|
|
|
await Completer<void>().future;
|
|
}
|