Optimize app logic

Support windows administrator auto launch

Support android close vpn
This commit is contained in:
chen08209
2024-08-15 16:18:00 +08:00
parent 0f8cfa20b2
commit 5dfb95a22d
65 changed files with 2784 additions and 1020 deletions

View File

@@ -4,6 +4,7 @@ import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/proxy_container.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
@@ -95,7 +96,9 @@ class ApplicationState extends State<Application> {
if (system.isDesktop) {
return WindowContainer(
child: TrayContainer(
child: app,
child: ProxyContainer(
child: app,
),
),
);
}
@@ -121,59 +124,65 @@ class ApplicationState extends State<Application> {
@override
Widget build(context) {
return AppStateContainer(
child: ClashContainer(
child: Selector2<AppState, Config, ApplicationSelectorState>(
selector: (_, appState, config) => ApplicationSelectorState(
locale: config.locale,
themeMode: config.themeMode,
primaryColor: config.primaryColor,
prueBlack: config.prueBlack,
),
builder: (_, state, child) {
return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
_updateSystemColorSchemes(lightDynamic, darkDynamic);
return MaterialApp(
navigatorKey: globalState.navigatorKey,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
builder: (_, child) {
return _buildApp(child!);
},
scrollBehavior: BaseScrollBehavior(),
title: appName,
locale: other.getLocaleForString(state.locale),
supportedLocales: AppLocalizations.delegate.supportedLocales,
themeMode: state.themeMode,
theme: ThemeData(
useMaterial3: true,
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.light,
systemColorSchemes: systemColorSchemes,
primaryColor: state.primaryColor,
return _buildApp(
AppStateContainer(
child: ClashContainer(
child: Selector2<AppState, Config, ApplicationSelectorState>(
selector: (_, appState, config) => ApplicationSelectorState(
locale: config.locale,
themeMode: config.themeMode,
primaryColor: config.primaryColor,
prueBlack: config.prueBlack,
),
builder: (_, state, child) {
return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
_updateSystemColorSchemes(lightDynamic, darkDynamic);
return MaterialApp(
navigatorKey: globalState.navigatorKey,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
builder: (_, child) {
if (system.isDesktop) {
return WindowHeaderContainer(child: child!);
}
return child!;
},
scrollBehavior: BaseScrollBehavior(),
title: appName,
locale: other.getLocaleForString(state.locale),
supportedLocales:
AppLocalizations.delegate.supportedLocales,
themeMode: state.themeMode,
theme: ThemeData(
useMaterial3: true,
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.light,
systemColorSchemes: systemColorSchemes,
primaryColor: state.primaryColor,
),
),
),
darkTheme: ThemeData(
useMaterial3: true,
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.dark,
systemColorSchemes: systemColorSchemes,
primaryColor: state.primaryColor,
).toPrueBlack(state.prueBlack),
),
home: child,
);
},
);
},
child: const HomePage(),
darkTheme: ThemeData(
useMaterial3: true,
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.dark,
systemColorSchemes: systemColorSchemes,
primaryColor: state.primaryColor,
).toPrueBlack(state.prueBlack),
),
home: child,
);
},
);
},
child: const HomePage(),
),
),
),
);

View File

@@ -237,6 +237,14 @@ class ClashCore {
malloc.free(paramsChar);
}
start() {
clashFFI.start();
}
stop() {
clashFFI.stop();
}
Future<Delay> getDelay(String proxyName) {
final delayParams = {
"proxy-name": proxyName,

View File

@@ -5144,6 +5144,22 @@ class ClashFFI {
late final __FCmulcr =
__FCmulcrPtr.asFunction<_Fcomplex Function(_Fcomplex, double)>();
void start() {
return _start();
}
late final _startPtr =
_lookup<ffi.NativeFunction<ffi.Void Function()>>('start');
late final _start = _startPtr.asFunction<void Function()>();
void stop() {
return _stop();
}
late final _stopPtr =
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stop');
late final _stop = _stopPtr.asFunction<void Function()>();
int initClash(
ffi.Pointer<ffi.Char> homeDirStr,
) {

View File

@@ -23,6 +23,6 @@ export 'app_localizations.dart';
export 'function.dart';
export 'package.dart';
export 'measure.dart';
export 'service.dart';
export 'windows.dart';
export 'iterable.dart';
export 'scroll.dart';

View File

@@ -24,17 +24,71 @@ class AutoLaunch {
return await launchAtStartup.isEnabled();
}
Future<bool> get windowsIsEnable async {
final res = await Process.run(
'schtasks',
['/Query', '/TN', appName, '/V', "/FO", "LIST"],
runInShell: true,
);
return res.stdout.toString().contains(Platform.resolvedExecutable);
}
Future<bool> enable() async {
return await launchAtStartup.enable();
}
windowsDisable() async {
final res = await Process.run(
'schtasks',
[
'/Delete',
'/TN',
appName,
'/F',
],
runInShell: true,
);
return res.exitCode == 0;
}
windowsEnable() async {
await Process.run(
'schtasks',
[
'/Create',
'/SC',
'ONLOGON',
'/TN',
appName,
'/TR',
Platform.resolvedExecutable,
'/RL',
'HIGHEST',
'/F'
],
runInShell: true,
);
}
Future<bool> disable() async {
return await launchAtStartup.disable();
}
updateStatus(bool value) async {
final isEnable = await this.isEnable;
if (isEnable == value) return;
final currentEnable =
Platform.isWindows ? await windowsIsEnable : await isEnable;
if (value == currentEnable) {
return;
}
if (Platform.isWindows) {
if (value) {
enable();
windowsEnable();
} else {
windowsDisable();
}
return;
}
if (value == true) {
enable();
} else {

View File

@@ -6,7 +6,6 @@ import 'dart:typed_data';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
import 'package:lpinyin/lpinyin.dart';
import 'package:zxing2/qrcode.dart';
import 'package:image/image.dart' as img;
@@ -192,7 +191,6 @@ class Other {
return ViewMode.desktop;
}
int getProxiesColumns(double viewWidth, ProxiesLayout proxiesLayout) {
final columns = max((viewWidth / 300).ceil(), 2);
return switch (proxiesLayout) {

View File

@@ -1,37 +1,4 @@
import 'package:fl_clash/common/datetime.dart';
import 'package:fl_clash/plugins/proxy.dart';
import 'package:proxy/proxy.dart' as proxy_plugin;
import 'package:proxy/proxy_platform_interface.dart';
import 'package:fl_clash/common/system.dart';
import 'package:proxy/proxy.dart';
class ProxyManager {
static ProxyManager? _instance;
late ProxyPlatform _proxy;
ProxyManager._internal() {
_proxy = proxy ?? proxy_plugin.Proxy();
}
bool get isStart => startTime != null && startTime!.isBeforeNow;
DateTime? get startTime => _proxy.startTime;
Future<bool?> startProxy({required int port}) async {
return await _proxy.startProxy(port);
}
Future<bool?> stopProxy() async {
return await _proxy.stopProxy();
}
Future<DateTime?> updateStartTime() async {
if (_proxy is! Proxy) return null;
return await (_proxy as Proxy).updateStartTime();
}
factory ProxyManager() {
_instance ??= ProxyManager._internal();
return _instance!;
}
}
final proxyManager = ProxyManager();
final proxy = system.isDesktop ? Proxy() : null;

View File

@@ -101,6 +101,9 @@ class Request {
return source.value(response.data!);
}
} catch (e) {
if(cancelToken?.isCancelled == true){
throw "cancelled";
}
continue;
}
}

View File

@@ -1,110 +0,0 @@
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';
typedef CreateServiceNative = IntPtr Function(
IntPtr hSCManager,
Pointer<Utf16> lpServiceName,
Pointer<Utf16> lpDisplayName,
Uint32 dwDesiredAccess,
Uint32 dwServiceType,
Uint32 dwStartType,
Uint32 dwErrorControl,
Pointer<Utf16> lpBinaryPathName,
Pointer<Utf16> lpLoadOrderGroup,
Pointer<Uint32> lpdwTagId,
Pointer<Utf16> lpDependencies,
Pointer<Utf16> lpServiceStartName,
Pointer<Utf16> lpPassword,
);
typedef CreateServiceDart = int Function(
int hSCManager,
Pointer<Utf16> lpServiceName,
Pointer<Utf16> lpDisplayName,
int dwDesiredAccess,
int dwServiceType,
int dwStartType,
int dwErrorControl,
Pointer<Utf16> lpBinaryPathName,
Pointer<Utf16> lpLoadOrderGroup,
Pointer<Uint32> lpdwTagId,
Pointer<Utf16> lpDependencies,
Pointer<Utf16> lpServiceStartName,
Pointer<Utf16> lpPassword,
);
const _SERVICE_ALL_ACCESS = 0xF003F;
const _SERVICE_WIN32_OWN_PROCESS = 0x00000010;
const _SERVICE_AUTO_START = 0x00000002;
const _SERVICE_ERROR_NORMAL = 0x00000001;
typedef GetLastErrorNative = Uint32 Function();
typedef GetLastErrorDart = int Function();
class Service {
static Service? _instance;
late DynamicLibrary _advapi32;
Service._internal() {
_advapi32 = DynamicLibrary.open('advapi32.dll');
}
factory Service() {
_instance ??= Service._internal();
return _instance!;
}
Future<void> createService() async {
final int scManager = OpenSCManager(nullptr, nullptr, _SERVICE_ALL_ACCESS);
if (scManager == 0) return;
final serviceName = 'FlClash Service'.toNativeUtf16();
final displayName = 'FlClash Service'.toNativeUtf16();
final binaryPathName = "C:\\Application\\Clash.Verge_1.6.6_x64_portable\\resources\\clash-verge-service.exe".toNativeUtf16();
final createService =
_advapi32.lookupFunction<CreateServiceNative, CreateServiceDart>(
'CreateServiceW',
);
final getLastError = DynamicLibrary.open('kernel32.dll')
.lookupFunction<GetLastErrorNative, GetLastErrorDart>('GetLastError');
final serviceHandle = createService(
scManager,
serviceName,
displayName,
_SERVICE_ALL_ACCESS,
_SERVICE_WIN32_OWN_PROCESS,
_SERVICE_AUTO_START,
_SERVICE_ERROR_NORMAL,
binaryPathName,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
);
print("serviceHandle $serviceHandle");
final errorCode = GetLastError();
print('Error code: $errorCode');
final result = StartService(serviceHandle, 0, nullptr);
if (result == 0) {
print('Failed to start the service.');
} else {
print('Service started successfully.');
}
calloc.free(serviceName);
calloc.free(displayName);
calloc.free(binaryPathName);
}
}
final service = Platform.isWindows ? Service() : null;

View File

@@ -34,7 +34,7 @@ class Window {
// await windowManager.setTitleBarStyle(TitleBarStyle.hidden);
// }
await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.setPreventClose(true);
// await windowManager.setPreventClose(true);
});
}

58
lib/common/windows.dart Normal file
View File

@@ -0,0 +1,58 @@
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
class Windows {
static Windows? _instance;
late DynamicLibrary _shell32;
Windows._internal() {
_shell32 = DynamicLibrary.open('shell32.dll');
}
factory Windows() {
_instance ??= Windows._internal();
return _instance!;
}
void runAsAdministrator(String command, String arguments) async {
final commandPtr = command.toNativeUtf16();
final argumentsPtr = arguments.toNativeUtf16();
final operationPtr = 'runas'.toNativeUtf16();
final shellExecute = _shell32.lookupFunction<
Int32 Function(
Pointer<Utf16> hwnd,
Pointer<Utf16> lpOperation,
Pointer<Utf16> lpFile,
Pointer<Utf16> lpParameters,
Pointer<Utf16> lpDirectory,
Int32 nShowCmd),
int Function(
Pointer<Utf16> hwnd,
Pointer<Utf16> lpOperation,
Pointer<Utf16> lpFile,
Pointer<Utf16> lpParameters,
Pointer<Utf16> lpDirectory,
int nShowCmd)>('ShellExecuteW');
final result = shellExecute(
nullptr,
operationPtr,
commandPtr,
argumentsPtr,
nullptr,
1,
);
calloc.free(commandPtr);
calloc.free(argumentsPtr);
calloc.free(operationPtr);
if (result <= 32) {
throw Exception('Failed to launch $command with UAC');
}
}
}
final windows = Platform.isWindows ? Windows() : null;

View File

@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'dart:math';
import 'package:archive/archive.dart';
import 'package:fl_clash/common/archive.dart';
@@ -44,10 +43,9 @@ class AppController {
measure = Measure.of(context);
}
Future<void> updateSystemProxy(bool isStart) async {
updateStatus(bool isStart) async {
if (isStart) {
await globalState.startSystemProxy(
appState: appState,
await globalState.handleStart(
config: config,
clashConfig: clashConfig,
);
@@ -60,7 +58,7 @@ class AppController {
if (Platform.isAndroid) return;
await applyProfile(isPrue: true);
} else {
await globalState.stopSystemProxy();
await globalState.handleStop();
clashCore.resetTraffic();
appState.traffics = [];
appState.totalTraffic = Traffic();
@@ -74,8 +72,9 @@ class AppController {
}
updateRunTime() {
if (proxyManager.startTime != null) {
final startTimeStamp = proxyManager.startTime!.millisecondsSinceEpoch;
final startTime = globalState.startTime;
if (startTime != null) {
final startTimeStamp = startTime.millisecondsSinceEpoch;
final nowTimeStamp = DateTime.now().millisecondsSinceEpoch;
appState.runTime = nowTimeStamp - startTimeStamp;
} else {
@@ -103,7 +102,7 @@ class AppController {
final updateId = config.profiles.first.id;
changeProfile(updateId);
} else {
updateSystemProxy(false);
updateStatus(false);
}
}
}
@@ -229,7 +228,7 @@ class AppController {
}
handleExit() async {
await updateSystemProxy(false);
await updateStatus(false);
await savePreferences();
clashCore.shutdown();
system.exit();
@@ -298,11 +297,13 @@ class AppController {
if (!config.silentLaunch) {
window?.show();
}
await proxyManager.updateStartTime();
if (proxyManager.isStart) {
await updateSystemProxy(true);
if (Platform.isAndroid) {
globalState.updateStartTime();
}
if (globalState.isStart) {
await updateStatus(true);
} else {
await updateSystemProxy(config.autoRun);
await updateStatus(config.autoRun);
}
autoUpdateProfiles();
autoCheckUpdate();
@@ -415,7 +416,6 @@ class AppController {
addProfileFormURL(url);
}
updateViewWidth(double width) {
WidgetsBinding.instance.addPostFrameCallback((_) {
appState.viewWidth = width;

View File

@@ -76,7 +76,7 @@ class ApplicationSettingFragment extends StatelessWidget {
selector: (_, config) => config.autoRun,
builder: (_, autoRun, child) {
return ListItem.switchItem(
leading: const Icon(Icons.start),
leading: const Icon(Icons.not_started),
title: Text(appLocalizations.autoRun),
subtitle: Text(appLocalizations.autoRunDesc),
delegate: SwitchDelegate(

View File

@@ -27,7 +27,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
final mixedPort = int.parse(port);
if (mixedPort < 1024 || mixedPort > 49151) throw "Invalid port";
globalState.appController.clashConfig.mixedPort = mixedPort;
globalState.appController.updateClashConfigDebounce();
} catch (e) {
globalState.showMessage(
title: appLocalizations.proxyPort,
@@ -62,7 +61,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
}
final appController = globalState.appController;
appController.clashConfig.logLevel = value;
appController.updateClashConfigDebounce();
Navigator.of(context).pop();
},
),
@@ -100,7 +98,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
onChanged: (String? value) {
final appController = globalState.appController;
appController.clashConfig.globalRealUa = value;
appController.updateClashConfigDebounce();
Navigator.of(context).pop();
},
),
@@ -125,7 +122,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
throw "Invalid url";
}
globalState.appController.config.testUrl = newTestUrl;
globalState.appController.updateClashConfigDebounce();
} catch (e) {
globalState.showMessage(
title: appLocalizations.testUrl,
@@ -172,7 +168,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
items: [
if (Platform.isAndroid) ...[
Selector<Config, bool>(
selector: (_, config) => config.allowBypass,
selector: (_, config) => config.vpnProps.allowBypass,
builder: (_, allowBypass, __) {
return ListItem.switchItem(
leading: const Icon(Icons.arrow_forward_outlined),
@@ -181,15 +177,18 @@ class _ConfigFragmentState extends State<ConfigFragment> {
delegate: SwitchDelegate(
value: allowBypass,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.config.allowBypass = value;
final config = globalState.appController.config;
final vpnProps = config.vpnProps;
config.vpnProps = vpnProps.copyWith(
allowBypass: value,
);
},
),
);
},
),
Selector<Config, bool>(
selector: (_, config) => config.systemProxy,
selector: (_, config) => config.vpnProps.systemProxy,
builder: (_, systemProxy, __) {
return ListItem.switchItem(
leading: const Icon(Icons.settings_ethernet),
@@ -198,8 +197,11 @@ class _ConfigFragmentState extends State<ConfigFragment> {
delegate: SwitchDelegate(
value: systemProxy,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.config.systemProxy = value;
final config = globalState.appController.config;
final vpnProps = config.vpnProps;
config.vpnProps = vpnProps.copyWith(
systemProxy: value,
);
},
),
);
@@ -351,7 +353,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
onChanged: (bool value) async {
final appController = globalState.appController;
appController.clashConfig.ipv6 = value;
appController.updateClashConfigDebounce();
},
),
);
@@ -369,7 +370,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
onChanged: (bool value) async {
final clashConfig = context.read<ClashConfig>();
clashConfig.allowLan = value;
globalState.appController.updateClashConfigDebounce();
},
),
);
@@ -387,7 +387,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
onChanged: (bool value) async {
final appController = globalState.appController;
appController.clashConfig.unifiedDelay = value;
appController.updateClashConfigDebounce();
},
),
);
@@ -407,7 +406,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
final appController = globalState.appController;
appController.clashConfig.findProcessMode =
value ? FindProcessMode.always : FindProcessMode.off;
appController.updateClashConfigDebounce();
},
),
);
@@ -425,7 +423,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
onChanged: (bool value) async {
final appController = globalState.appController;
appController.clashConfig.tcpConcurrent = value;
appController.updateClashConfigDebounce();
},
),
);
@@ -446,7 +443,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
appController.clashConfig.geodataLoader = value
? geodataLoaderMemconservative
: geodataLoaderStandard;
appController.updateClashConfigDebounce();
},
),
);
@@ -466,7 +462,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
final appController = globalState.appController;
appController.clashConfig.externalController =
value ? defaultExternalController : '';
appController.updateClashConfigDebounce();
},
),
);
@@ -493,7 +488,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
onChanged: (bool value) async {
final clashConfig = context.read<ClashConfig>();
clashConfig.tun = Tun(enable: value);
globalState.appController.updateClashConfigDebounce();
},
),
);
@@ -652,8 +646,9 @@ class _KeepAliveIntervalFormDialogState
@override
void initState() {
super.initState();
keepAliveIntervalController =
TextEditingController(text: "${widget.keepAliveInterval}");
keepAliveIntervalController = TextEditingController(
text: "${widget.keepAliveInterval}",
);
}
_handleUpdate() async {

View File

@@ -1,6 +1,9 @@
import 'dart:io';
import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/fragments/dashboard/intranet_ip.dart';
import 'package:fl_clash/fragments/dashboard/status_switch.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/material.dart';
import 'package:fl_clash/widgets/widgets.dart';
@@ -28,34 +31,51 @@ class _DashboardFragmentState extends State<DashboardFragment> {
child: Align(
alignment: Alignment.topCenter,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16).copyWith(
bottom: 88,
),
child: Selector<AppState, double>(
selector: (_, appState) => appState.viewWidth,
builder: (_, viewWidth, ___) {
// final viewMode = other.getViewMode(viewWidth);
// final isDesktop = viewMode == ViewMode.desktop;
final columns = max(4 * ((viewWidth / 350).ceil()), 8);
final int switchCount = (4 / columns) * viewWidth < 200 ? 8 : 4;
return Grid(
crossAxisCount: max(4 * ((viewWidth / 350).ceil()), 8),
crossAxisCount: columns,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
children: const [
GridItem(
children: [
const GridItem(
crossAxisCellCount: 8,
child: NetworkSpeed(),
),
GridItem(
if (Platform.isAndroid)
GridItem(
crossAxisCellCount: switchCount,
child: const VPNSwitch(),
),
if (system.isDesktop) ...[
GridItem(
crossAxisCellCount: switchCount,
child: const TUNSwitch(),
),
GridItem(
crossAxisCellCount: switchCount,
child: const ProxySwitch(),
),
],
const GridItem(
crossAxisCellCount: 4,
child: OutboundMode(),
),
GridItem(
const GridItem(
crossAxisCellCount: 4,
child: NetworkDetection(),
),
GridItem(
const GridItem(
crossAxisCellCount: 4,
child: TrafficUsage(),
),
GridItem(
const GridItem(
crossAxisCellCount: 4,
child: IntranetIP(),
),

View File

@@ -14,8 +14,12 @@ class NetworkDetection extends StatefulWidget {
}
class _NetworkDetectionState extends State<NetworkDetection> {
final ipInfoNotifier = ValueNotifier<IpInfo?>(null);
final timeoutNotifier = ValueNotifier<bool>(false);
final networkDetectionState = ValueNotifier<NetworkDetectionState>(
const NetworkDetectionState(
isTesting: true,
ipInfo: null,
),
);
bool? _preIsStart;
Function? _checkIpDebounce;
CancelToken? cancelToken;
@@ -23,26 +27,28 @@ class _NetworkDetectionState extends State<NetworkDetection> {
_checkIp() async {
final appState = globalState.appController.appState;
final isInit = appState.isInit;
final isStart = appState.isStart;
if (!isInit) return;
timeoutNotifier.value = false;
final isStart = appState.isStart;
if (_preIsStart == false && _preIsStart == isStart) return;
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: true,
ipInfo: null,
);
_preIsStart = isStart;
ipInfoNotifier.value = null;
if (cancelToken != null) {
cancelToken!.cancel();
_preIsStart = null;
timeoutNotifier.value == false;
cancelToken = null;
}
cancelToken = CancelToken();
final ipInfo = await request.checkIp(cancelToken: cancelToken);
if (ipInfo == null) {
timeoutNotifier.value = true;
return;
try {
final ipInfo = await request.checkIp(cancelToken: cancelToken);
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: false,
ipInfo: ipInfo,
);
} catch (_) {
}
timeoutNotifier.value = false;
ipInfoNotifier.value = ipInfo;
}
_checkIpContainer(Widget child) {
@@ -63,8 +69,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
@override
void dispose() {
super.dispose();
ipInfoNotifier.dispose();
timeoutNotifier.dispose();
networkDetectionState.dispose();
}
String countryCodeToEmoji(String countryCode) {
@@ -81,9 +86,11 @@ class _NetworkDetectionState extends State<NetworkDetection> {
Widget build(BuildContext context) {
_checkIpDebounce ??= debounce(_checkIp);
return _checkIpContainer(
ValueListenableBuilder<IpInfo?>(
valueListenable: ipInfoNotifier,
builder: (_, ipInfo, __) {
ValueListenableBuilder<NetworkDetectionState>(
valueListenable: networkDetectionState,
builder: (_, state, __) {
final ipInfo = state.ipInfo;
final isTesting = state.isTesting;
return CommonCard(
onPressed: () {},
child: Column(
@@ -104,46 +111,38 @@ class _NetworkDetectionState extends State<NetworkDetection> {
Flexible(
flex: 1,
child: FadeBox(
child: ipInfo != null
? Container(
alignment: Alignment.centerLeft,
height: globalState.appController.measure
.titleMediumHeight,
child: Text(
countryCodeToEmoji(ipInfo.countryCode),
style: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(
fontFamily: "Twemoji",
),
),
child: isTesting
? Text(
appLocalizations.checking,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style:
Theme.of(context).textTheme.titleMedium,
)
: ValueListenableBuilder(
valueListenable: timeoutNotifier,
builder: (_, timeout, __) {
if (timeout) {
return Text(
appLocalizations.checkError,
: ipInfo != null
? Container(
alignment: Alignment.centerLeft,
height: globalState.appController
.measure.titleMediumHeight,
child: Text(
countryCodeToEmoji(
ipInfo.countryCode),
style: Theme.of(context)
.textTheme
.titleMedium,
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
}
return TooltipText(
text: Text(
appLocalizations.checking,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.titleMedium,
.titleLarge
?.copyWith(
fontFamily: "Twemoji",
),
),
);
},
),
)
: Text(
appLocalizations.checkError,
style: Theme.of(context)
.textTheme
.titleMedium,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
],
@@ -176,28 +175,24 @@ class _NetworkDetectionState extends State<NetworkDetection> {
),
],
)
: ValueListenableBuilder(
valueListenable: timeoutNotifier,
builder: (_, timeout, __) {
if (timeout) {
return Text(
"timeout",
style: context.textTheme.titleLarge
?.copyWith(color: Colors.red)
.toSoftBold
.toMinus,
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
}
return Container(
padding: const EdgeInsets.all(2),
child: const AspectRatio(
aspectRatio: 1,
child: CircularProgressIndicator(),
),
);
},
: FadeBox(
child: isTesting == false && ipInfo == null
? Text(
"timeout",
style: context.textTheme.titleLarge
?.copyWith(color: Colors.red)
.toSoftBold
.toMinus,
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
: Container(
padding: const EdgeInsets.all(2),
child: const AspectRatio(
aspectRatio: 1,
child: CircularProgressIndicator(),
),
),
),
),
)

View File

@@ -114,7 +114,7 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
onPressed: () {},
info: Info(
label: appLocalizations.networkSpeed,
iconData: Icons.speed,
iconData: Icons.speed_sharp,
),
child: Selector<AppState, List<Traffic>>(
selector: (_, appState) => appState.traffics,

View File

@@ -15,7 +15,6 @@ class OutboundMode extends StatelessWidget {
final clashConfig = appController.clashConfig;
if (value == null || clashConfig.mode == value) return;
clashConfig.mode = value;
await appController.updateClashConfig();
appController.addCheckIpNumDebounce();
}
@@ -28,7 +27,7 @@ class OutboundMode extends StatelessWidget {
onPressed: () {},
info: Info(
label: appLocalizations.outboundMode,
iconData: Icons.call_split,
iconData: Icons.call_split_sharp,
),
child: Padding(
padding: const EdgeInsets.only(bottom: 16),

View File

@@ -37,7 +37,7 @@ class _StartButtonState extends State<StartButton>
if (isStart == appController.appState.isStart) {
isStart = !isStart;
updateController();
appController.updateSystemProxy(isStart);
appController.updateStatus(isStart);
}
}
@@ -53,7 +53,7 @@ class _StartButtonState extends State<StartButton>
return Selector<AppState, bool>(
selector: (_, appState) => appState.isStart,
builder: (_, isStart, child) {
if(isStart != this.isStart){
if (isStart != this.isStart) {
this.isStart = isStart;
updateController();
}

View File

@@ -0,0 +1,121 @@
import 'package:fl_clash/common/app_localizations.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class VPNSwitch extends StatelessWidget {
const VPNSwitch({super.key});
@override
Widget build(BuildContext context) {
return SwitchContainer(
info: const Info(
label: "VPN",
iconData: Icons.stacked_line_chart,
),
child: Selector<Config, bool>(
selector: (_, config) => config.vpnProps.enable,
builder: (_, enable, __) {
return Switch(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
value: enable,
onChanged: (value) {
final config = globalState.appController.config;
config.vpnProps = config.vpnProps.copyWith(
enable: value,
);
},
);
},
),
);
}
}
class TUNSwitch extends StatelessWidget {
const TUNSwitch({super.key});
@override
Widget build(BuildContext context) {
return SwitchContainer(
info: Info(
label: appLocalizations.tun,
iconData: Icons.stacked_line_chart,
),
child: Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.tun.enable,
builder: (_, enable, __) {
return Switch(
value: enable,
onChanged: (value) {
final clashConfig = globalState.appController.clashConfig;
clashConfig.tun = clashConfig.tun.copyWith(
enable: value,
);
},
);
},
),
);
}
}
class ProxySwitch extends StatelessWidget {
const ProxySwitch({super.key});
@override
Widget build(BuildContext context) {
return SwitchContainer(
info: Info(
label: appLocalizations.systemProxy,
iconData: Icons.shuffle,
),
child: Selector<Config, bool>(
selector: (_, config) => config.desktopProps.systemProxy,
builder: (_, systemProxy, __) {
return Switch(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
value: systemProxy,
onChanged: (value) {
final config = globalState.appController.config;
config.desktopProps =
config.desktopProps.copyWith(systemProxy: value);
},
);
},
),
);
}
}
class SwitchContainer extends StatelessWidget {
final Info info;
final Widget child;
const SwitchContainer({
super.key,
required this.info,
required this.child,
});
@override
Widget build(BuildContext context) {
return CommonCard(
onPressed: () {},
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoHeader(
info: info,
actions: [
child,
],
),
],
),
);
}
}

View File

@@ -91,7 +91,6 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
final appController = globalState.appController;
appController.clashConfig.geoXUrl =
Map.from(appController.clashConfig.geoXUrl)..[geoItem.key] = newUrl;
appController.updateClashConfigDebounce();
} catch (e) {
globalState.showMessage(
title: geoItem.label,

View File

@@ -37,7 +37,7 @@
"overrideDesc": "Override Proxy related config",
"allowLan": "AllowLan",
"allowLanDesc": "Allow access proxy through the LAN",
"tun": "TUN mode",
"tun": "TUN",
"tunDesc": "only effective in administrator mode",
"minimizeOnExit": "Minimize on exit",
"minimizeOnExitDesc": "Modify the default system exit event",
@@ -117,7 +117,7 @@
"logLevel": "LogLevel",
"show": "Show",
"exit": "Exit",
"systemProxy": "SystemProxy",
"systemProxy": "System proxy",
"project": "Project",
"core": "Core",
"tabAnimation": "Tab animation",

View File

@@ -37,7 +37,7 @@
"overrideDesc": "覆写代理相关配置",
"allowLan": "局域网代理",
"allowLanDesc": "允许通过局域网访问代理",
"tun": "TUN模式",
"tun": "虚拟网卡",
"tunDesc": "仅在管理员模式生效",
"minimizeOnExit": "退出时最小化",
"minimizeOnExitDesc": "修改系统默认退出事件",

View File

@@ -321,7 +321,7 @@ class MessageLookup extends MessageLookupByLibrary {
"style": MessageLookupByLibrary.simpleMessage("Style"),
"submit": MessageLookupByLibrary.simpleMessage("Submit"),
"sync": MessageLookupByLibrary.simpleMessage("Sync"),
"systemProxy": MessageLookupByLibrary.simpleMessage("SystemProxy"),
"systemProxy": MessageLookupByLibrary.simpleMessage("System proxy"),
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(
"Attach HTTP proxy to VpnService"),
"tab": MessageLookupByLibrary.simpleMessage("Tab"),
@@ -343,7 +343,7 @@ class MessageLookup extends MessageLookupByLibrary {
"tip": MessageLookupByLibrary.simpleMessage("tip"),
"tools": MessageLookupByLibrary.simpleMessage("Tools"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"),
"tun": MessageLookupByLibrary.simpleMessage("TUN mode"),
"tun": MessageLookupByLibrary.simpleMessage("TUN"),
"tunDesc": MessageLookupByLibrary.simpleMessage(
"only effective in administrator mode"),
"twoColumns": MessageLookupByLibrary.simpleMessage("Two columns"),

View File

@@ -278,7 +278,7 @@ class MessageLookup extends MessageLookupByLibrary {
"tip": MessageLookupByLibrary.simpleMessage("提示"),
"tools": MessageLookupByLibrary.simpleMessage("工具"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),
"tun": MessageLookupByLibrary.simpleMessage("TUN模式"),
"tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
"tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"),
"twoColumns": MessageLookupByLibrary.simpleMessage("两列"),
"unableToUpdateCurrentProfileDesc":

View File

@@ -430,10 +430,10 @@ class AppLocalizations {
);
}
/// `TUN mode`
/// `TUN`
String get tun {
return Intl.message(
'TUN mode',
'TUN',
name: 'tun',
desc: '',
args: [],
@@ -1230,10 +1230,10 @@ class AppLocalizations {
);
}
/// `SystemProxy`
/// `System proxy`
String get systemProxy {
return Intl.message(
'SystemProxy',
'System proxy',
name: 'systemProxy',
desc: '',
args: [],

View File

@@ -3,8 +3,8 @@ import 'dart:io';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/proxy.dart';
import 'package:fl_clash/plugins/tile.dart';
import 'package:fl_clash/plugins/vpn.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
@@ -61,14 +61,14 @@ Future<void> vpnService() async {
clashConfig: clashConfig,
);
proxy?.setServiceMessageHandler(
vpn?.setServiceMessageHandler(
ServiceMessageHandler(
onProtect: (Fd fd) async {
await proxy?.setProtect(fd.value);
await vpn?.setProtect(fd.value);
clashCore.setFdMap(fd.id);
},
onProcess: (Process process) async {
var packageName = await app?.resolverProcess(process);
final packageName = await app?.resolverProcess(process);
clashCore.setProcessMap(
ProcessMapItem(
id: process.id,
@@ -76,8 +76,8 @@ Future<void> vpnService() async {
),
);
},
onStarted: (String runTime) {
globalState.applyProfile(
onStarted: (String runTime) async {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
@@ -100,8 +100,7 @@ Future<void> vpnService() async {
WidgetsBinding.instance.platformDispatcher.locale,
);
await app?.tip(appLocalizations.startVpn);
await globalState.startSystemProxy(
appState: appState,
await globalState.handleStart(
config: config,
clashConfig: clashConfig,
);
@@ -110,7 +109,7 @@ Future<void> vpnService() async {
TileListenerWithVpn(
onStop: () async {
await app?.tip(appLocalizations.stopVpn);
await globalState.stopSystemProxy();
await globalState.handleStop();
clashCore.shutdown();
exit(0);
},

View File

@@ -38,6 +38,7 @@ class CoreState with _$CoreState {
const factory CoreState({
AccessControl? accessControl,
required String currentProfileName,
required bool enable,
required bool allowBypass,
required bool systemProxy,
required int mixedPort,
@@ -58,10 +59,30 @@ class WindowProps with _$WindowProps {
}) = _WindowProps;
factory WindowProps.fromJson(Map<String, Object?>? json) =>
json == null ? defaultWindowProps : _$WindowPropsFromJson(json);
json == null ? const WindowProps() : _$WindowPropsFromJson(json);
}
const defaultWindowProps = WindowProps();
@freezed
class VpnProps with _$VpnProps {
const factory VpnProps({
@Default(true) bool enable,
@Default(false) bool systemProxy,
@Default(true) bool allowBypass,
}) = _VpnProps;
factory VpnProps.fromJson(Map<String, Object?>? json) =>
json == null ? const VpnProps() : _$VpnPropsFromJson(json);
}
@freezed
class DesktopProps with _$DesktopProps {
const factory DesktopProps({
@Default(true) bool systemProxy,
}) = _DesktopProps;
factory DesktopProps.fromJson(Map<String, Object?>? json) =>
json == null ? const DesktopProps() : _$DesktopPropsFromJson(json);
}
@JsonSerializable()
class Config extends ChangeNotifier {
@@ -81,8 +102,6 @@ class Config extends ChangeNotifier {
AccessControl _accessControl;
bool _isAnimateToPage;
bool _autoCheckUpdate;
bool _allowBypass;
bool _systemProxy;
bool _isExclude;
DAV? _dav;
bool _isCloseConnections;
@@ -93,6 +112,9 @@ class Config extends ChangeNotifier {
WindowProps _windowProps;
bool _onlyProxy;
bool _prueBlack;
VpnProps _vpnProps;
DesktopProps _desktopProps;
bool _showLabel;
Config()
: _profiles = [],
@@ -108,18 +130,19 @@ class Config extends ChangeNotifier {
_isMinimizeOnExit = true,
_isAccessControl = false,
_autoCheckUpdate = true,
_systemProxy = false,
_testUrl = defaultTestUrl,
_accessControl = const AccessControl(),
_isAnimateToPage = true,
_allowBypass = true,
_isExclude = false,
_proxyCardType = ProxyCardType.expand,
_windowProps = defaultWindowProps,
_windowProps = const WindowProps(),
_proxiesType = ProxiesType.tab,
_prueBlack = false,
_onlyProxy = false,
_proxiesLayout = ProxiesLayout.standard;
_proxiesLayout = ProxiesLayout.standard,
_vpnProps = const VpnProps(),
_desktopProps = const DesktopProps(),
_showLabel = false;
deleteProfileById(String id) {
_profiles = profiles.where((element) => element.id != id).toList();
@@ -409,30 +432,6 @@ class Config extends ChangeNotifier {
}
}
@JsonKey(defaultValue: true)
bool get allowBypass {
return _allowBypass;
}
set allowBypass(bool value) {
if (_allowBypass != value) {
_allowBypass = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get systemProxy {
return _systemProxy;
}
set systemProxy(bool value) {
if (_systemProxy != value) {
_systemProxy = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get onlyProxy {
return _onlyProxy;
@@ -521,6 +520,33 @@ class Config extends ChangeNotifier {
}
}
VpnProps get vpnProps => _vpnProps;
set vpnProps(VpnProps value) {
if (_vpnProps != value) {
_vpnProps = value;
notifyListeners();
}
}
DesktopProps get desktopProps => _desktopProps;
set desktopProps(DesktopProps value) {
if (_desktopProps != value) {
_desktopProps = value;
notifyListeners();
}
}
bool get showLabel => _showLabel;
set showLabel(bool value) {
if (_showLabel != value) {
_showLabel = value;
notifyListeners();
}
}
update([
Config? config,
RecoveryOption recoveryOptions = RecoveryOption.all,
@@ -545,7 +571,6 @@ class Config extends ChangeNotifier {
_openLog = config._openLog;
_themeMode = config._themeMode;
_locale = config._locale;
_allowBypass = config._allowBypass;
_primaryColor = config._primaryColor;
_proxiesSortType = config._proxiesSortType;
_isMinimizeOnExit = config._isMinimizeOnExit;
@@ -557,6 +582,8 @@ class Config extends ChangeNotifier {
_testUrl = config._testUrl;
_isExclude = config._isExclude;
_windowProps = config._windowProps;
_vpnProps = config._vpnProps;
_desktopProps = config._desktopProps;
}
notifyListeners();
}

View File

@@ -270,6 +270,7 @@ CoreState _$CoreStateFromJson(Map<String, dynamic> json) {
mixin _$CoreState {
AccessControl? get accessControl => throw _privateConstructorUsedError;
String get currentProfileName => throw _privateConstructorUsedError;
bool get enable => throw _privateConstructorUsedError;
bool get allowBypass => throw _privateConstructorUsedError;
bool get systemProxy => throw _privateConstructorUsedError;
int get mixedPort => throw _privateConstructorUsedError;
@@ -289,6 +290,7 @@ abstract class $CoreStateCopyWith<$Res> {
$Res call(
{AccessControl? accessControl,
String currentProfileName,
bool enable,
bool allowBypass,
bool systemProxy,
int mixedPort,
@@ -312,6 +314,7 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
$Res call({
Object? accessControl = freezed,
Object? currentProfileName = null,
Object? enable = null,
Object? allowBypass = null,
Object? systemProxy = null,
Object? mixedPort = null,
@@ -326,6 +329,10 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
? _value.currentProfileName
: currentProfileName // ignore: cast_nullable_to_non_nullable
as String,
enable: null == enable
? _value.enable
: enable // ignore: cast_nullable_to_non_nullable
as bool,
allowBypass: null == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
@@ -369,6 +376,7 @@ abstract class _$$CoreStateImplCopyWith<$Res>
$Res call(
{AccessControl? accessControl,
String currentProfileName,
bool enable,
bool allowBypass,
bool systemProxy,
int mixedPort,
@@ -391,6 +399,7 @@ class __$$CoreStateImplCopyWithImpl<$Res>
$Res call({
Object? accessControl = freezed,
Object? currentProfileName = null,
Object? enable = null,
Object? allowBypass = null,
Object? systemProxy = null,
Object? mixedPort = null,
@@ -405,6 +414,10 @@ class __$$CoreStateImplCopyWithImpl<$Res>
? _value.currentProfileName
: currentProfileName // ignore: cast_nullable_to_non_nullable
as String,
enable: null == enable
? _value.enable
: enable // ignore: cast_nullable_to_non_nullable
as bool,
allowBypass: null == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
@@ -431,6 +444,7 @@ class _$CoreStateImpl implements _CoreState {
const _$CoreStateImpl(
{this.accessControl,
required this.currentProfileName,
required this.enable,
required this.allowBypass,
required this.systemProxy,
required this.mixedPort,
@@ -444,6 +458,8 @@ class _$CoreStateImpl implements _CoreState {
@override
final String currentProfileName;
@override
final bool enable;
@override
final bool allowBypass;
@override
final bool systemProxy;
@@ -454,7 +470,7 @@ class _$CoreStateImpl implements _CoreState {
@override
String toString() {
return 'CoreState(accessControl: $accessControl, currentProfileName: $currentProfileName, allowBypass: $allowBypass, systemProxy: $systemProxy, mixedPort: $mixedPort, onlyProxy: $onlyProxy)';
return 'CoreState(accessControl: $accessControl, currentProfileName: $currentProfileName, enable: $enable, allowBypass: $allowBypass, systemProxy: $systemProxy, mixedPort: $mixedPort, onlyProxy: $onlyProxy)';
}
@override
@@ -466,6 +482,7 @@ class _$CoreStateImpl implements _CoreState {
other.accessControl == accessControl) &&
(identical(other.currentProfileName, currentProfileName) ||
other.currentProfileName == currentProfileName) &&
(identical(other.enable, enable) || other.enable == enable) &&
(identical(other.allowBypass, allowBypass) ||
other.allowBypass == allowBypass) &&
(identical(other.systemProxy, systemProxy) ||
@@ -478,8 +495,15 @@ class _$CoreStateImpl implements _CoreState {
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, accessControl,
currentProfileName, allowBypass, systemProxy, mixedPort, onlyProxy);
int get hashCode => Object.hash(
runtimeType,
accessControl,
currentProfileName,
enable,
allowBypass,
systemProxy,
mixedPort,
onlyProxy);
@JsonKey(ignore: true)
@override
@@ -499,6 +523,7 @@ abstract class _CoreState implements CoreState {
const factory _CoreState(
{final AccessControl? accessControl,
required final String currentProfileName,
required final bool enable,
required final bool allowBypass,
required final bool systemProxy,
required final int mixedPort,
@@ -512,6 +537,8 @@ abstract class _CoreState implements CoreState {
@override
String get currentProfileName;
@override
bool get enable;
@override
bool get allowBypass;
@override
bool get systemProxy;
@@ -715,3 +742,318 @@ abstract class _WindowProps implements WindowProps {
_$$WindowPropsImplCopyWith<_$WindowPropsImpl> get copyWith =>
throw _privateConstructorUsedError;
}
VpnProps _$VpnPropsFromJson(Map<String, dynamic> json) {
return _VpnProps.fromJson(json);
}
/// @nodoc
mixin _$VpnProps {
bool get enable => throw _privateConstructorUsedError;
bool get systemProxy => throw _privateConstructorUsedError;
bool get allowBypass => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$VpnPropsCopyWith<VpnProps> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $VpnPropsCopyWith<$Res> {
factory $VpnPropsCopyWith(VpnProps value, $Res Function(VpnProps) then) =
_$VpnPropsCopyWithImpl<$Res, VpnProps>;
@useResult
$Res call({bool enable, bool systemProxy, bool allowBypass});
}
/// @nodoc
class _$VpnPropsCopyWithImpl<$Res, $Val extends VpnProps>
implements $VpnPropsCopyWith<$Res> {
_$VpnPropsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? enable = null,
Object? systemProxy = null,
Object? allowBypass = null,
}) {
return _then(_value.copyWith(
enable: null == enable
? _value.enable
: enable // ignore: cast_nullable_to_non_nullable
as bool,
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
allowBypass: null == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
/// @nodoc
abstract class _$$VpnPropsImplCopyWith<$Res>
implements $VpnPropsCopyWith<$Res> {
factory _$$VpnPropsImplCopyWith(
_$VpnPropsImpl value, $Res Function(_$VpnPropsImpl) then) =
__$$VpnPropsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool enable, bool systemProxy, bool allowBypass});
}
/// @nodoc
class __$$VpnPropsImplCopyWithImpl<$Res>
extends _$VpnPropsCopyWithImpl<$Res, _$VpnPropsImpl>
implements _$$VpnPropsImplCopyWith<$Res> {
__$$VpnPropsImplCopyWithImpl(
_$VpnPropsImpl _value, $Res Function(_$VpnPropsImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? enable = null,
Object? systemProxy = null,
Object? allowBypass = null,
}) {
return _then(_$VpnPropsImpl(
enable: null == enable
? _value.enable
: enable // ignore: cast_nullable_to_non_nullable
as bool,
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
allowBypass: null == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
@JsonSerializable()
class _$VpnPropsImpl implements _VpnProps {
const _$VpnPropsImpl(
{this.enable = true, this.systemProxy = false, this.allowBypass = true});
factory _$VpnPropsImpl.fromJson(Map<String, dynamic> json) =>
_$$VpnPropsImplFromJson(json);
@override
@JsonKey()
final bool enable;
@override
@JsonKey()
final bool systemProxy;
@override
@JsonKey()
final bool allowBypass;
@override
String toString() {
return 'VpnProps(enable: $enable, systemProxy: $systemProxy, allowBypass: $allowBypass)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$VpnPropsImpl &&
(identical(other.enable, enable) || other.enable == enable) &&
(identical(other.systemProxy, systemProxy) ||
other.systemProxy == systemProxy) &&
(identical(other.allowBypass, allowBypass) ||
other.allowBypass == allowBypass));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, enable, systemProxy, allowBypass);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$VpnPropsImplCopyWith<_$VpnPropsImpl> get copyWith =>
__$$VpnPropsImplCopyWithImpl<_$VpnPropsImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$VpnPropsImplToJson(
this,
);
}
}
abstract class _VpnProps implements VpnProps {
const factory _VpnProps(
{final bool enable,
final bool systemProxy,
final bool allowBypass}) = _$VpnPropsImpl;
factory _VpnProps.fromJson(Map<String, dynamic> json) =
_$VpnPropsImpl.fromJson;
@override
bool get enable;
@override
bool get systemProxy;
@override
bool get allowBypass;
@override
@JsonKey(ignore: true)
_$$VpnPropsImplCopyWith<_$VpnPropsImpl> get copyWith =>
throw _privateConstructorUsedError;
}
DesktopProps _$DesktopPropsFromJson(Map<String, dynamic> json) {
return _DesktopProps.fromJson(json);
}
/// @nodoc
mixin _$DesktopProps {
bool get systemProxy => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$DesktopPropsCopyWith<DesktopProps> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $DesktopPropsCopyWith<$Res> {
factory $DesktopPropsCopyWith(
DesktopProps value, $Res Function(DesktopProps) then) =
_$DesktopPropsCopyWithImpl<$Res, DesktopProps>;
@useResult
$Res call({bool systemProxy});
}
/// @nodoc
class _$DesktopPropsCopyWithImpl<$Res, $Val extends DesktopProps>
implements $DesktopPropsCopyWith<$Res> {
_$DesktopPropsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? systemProxy = null,
}) {
return _then(_value.copyWith(
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
/// @nodoc
abstract class _$$DesktopPropsImplCopyWith<$Res>
implements $DesktopPropsCopyWith<$Res> {
factory _$$DesktopPropsImplCopyWith(
_$DesktopPropsImpl value, $Res Function(_$DesktopPropsImpl) then) =
__$$DesktopPropsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool systemProxy});
}
/// @nodoc
class __$$DesktopPropsImplCopyWithImpl<$Res>
extends _$DesktopPropsCopyWithImpl<$Res, _$DesktopPropsImpl>
implements _$$DesktopPropsImplCopyWith<$Res> {
__$$DesktopPropsImplCopyWithImpl(
_$DesktopPropsImpl _value, $Res Function(_$DesktopPropsImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? systemProxy = null,
}) {
return _then(_$DesktopPropsImpl(
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
@JsonSerializable()
class _$DesktopPropsImpl implements _DesktopProps {
const _$DesktopPropsImpl({this.systemProxy = true});
factory _$DesktopPropsImpl.fromJson(Map<String, dynamic> json) =>
_$$DesktopPropsImplFromJson(json);
@override
@JsonKey()
final bool systemProxy;
@override
String toString() {
return 'DesktopProps(systemProxy: $systemProxy)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$DesktopPropsImpl &&
(identical(other.systemProxy, systemProxy) ||
other.systemProxy == systemProxy));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, systemProxy);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$DesktopPropsImplCopyWith<_$DesktopPropsImpl> get copyWith =>
__$$DesktopPropsImplCopyWithImpl<_$DesktopPropsImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$DesktopPropsImplToJson(
this,
);
}
}
abstract class _DesktopProps implements DesktopProps {
const factory _DesktopProps({final bool systemProxy}) = _$DesktopPropsImpl;
factory _DesktopProps.fromJson(Map<String, dynamic> json) =
_$DesktopPropsImpl.fromJson;
@override
bool get systemProxy;
@override
@JsonKey(ignore: true)
_$$DesktopPropsImplCopyWith<_$DesktopPropsImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -36,8 +36,6 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
..isAnimateToPage = json['isAnimateToPage'] as bool? ?? true
..isCompatible = json['isCompatible'] as bool? ?? true
..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true
..allowBypass = json['allowBypass'] as bool? ?? true
..systemProxy = json['systemProxy'] as bool? ?? false
..onlyProxy = json['onlyProxy'] as bool? ?? false
..prueBlack = json['prueBlack'] as bool? ?? false
..isCloseConnections = json['isCloseConnections'] as bool? ?? false
@@ -51,7 +49,10 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
json['test-url'] as String? ?? 'https://www.gstatic.com/generate_204'
..isExclude = json['isExclude'] as bool? ?? false
..windowProps =
WindowProps.fromJson(json['windowProps'] as Map<String, dynamic>?);
WindowProps.fromJson(json['windowProps'] as Map<String, dynamic>?)
..vpnProps = VpnProps.fromJson(json['vpnProps'] as Map<String, dynamic>?)
..desktopProps =
DesktopProps.fromJson(json['desktopProps'] as Map<String, dynamic>?);
Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'profiles': instance.profiles,
@@ -72,8 +73,6 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'isAnimateToPage': instance.isAnimateToPage,
'isCompatible': instance.isCompatible,
'autoCheckUpdate': instance.autoCheckUpdate,
'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy,
'onlyProxy': instance.onlyProxy,
'prueBlack': instance.prueBlack,
'isCloseConnections': instance.isCloseConnections,
@@ -82,6 +81,8 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'test-url': instance.testUrl,
'isExclude': instance.isExclude,
'windowProps': instance.windowProps,
'vpnProps': instance.vpnProps,
'desktopProps': instance.desktopProps,
};
const _$ThemeModeEnumMap = {
@@ -157,6 +158,7 @@ _$CoreStateImpl _$$CoreStateImplFromJson(Map<String, dynamic> json) =>
: AccessControl.fromJson(
json['accessControl'] as Map<String, dynamic>),
currentProfileName: json['currentProfileName'] as String,
enable: json['enable'] as bool,
allowBypass: json['allowBypass'] as bool,
systemProxy: json['systemProxy'] as bool,
mixedPort: (json['mixedPort'] as num).toInt(),
@@ -167,6 +169,7 @@ Map<String, dynamic> _$$CoreStateImplToJson(_$CoreStateImpl instance) =>
<String, dynamic>{
'accessControl': instance.accessControl,
'currentProfileName': instance.currentProfileName,
'enable': instance.enable,
'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy,
'mixedPort': instance.mixedPort,
@@ -188,3 +191,27 @@ Map<String, dynamic> _$$WindowPropsImplToJson(_$WindowPropsImpl instance) =>
'top': instance.top,
'left': instance.left,
};
_$VpnPropsImpl _$$VpnPropsImplFromJson(Map<String, dynamic> json) =>
_$VpnPropsImpl(
enable: json['enable'] as bool? ?? true,
systemProxy: json['systemProxy'] as bool? ?? false,
allowBypass: json['allowBypass'] as bool? ?? true,
);
Map<String, dynamic> _$$VpnPropsImplToJson(_$VpnPropsImpl instance) =>
<String, dynamic>{
'enable': instance.enable,
'systemProxy': instance.systemProxy,
'allowBypass': instance.allowBypass,
};
_$DesktopPropsImpl _$$DesktopPropsImplFromJson(Map<String, dynamic> json) =>
_$DesktopPropsImpl(
systemProxy: json['systemProxy'] as bool? ?? true,
);
Map<String, dynamic> _$$DesktopPropsImplToJson(_$DesktopPropsImpl instance) =>
<String, dynamic>{
'systemProxy': instance.systemProxy,
};

View File

@@ -625,6 +625,147 @@ abstract class _ProfilesSelectorState implements ProfilesSelectorState {
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$NetworkDetectionState {
bool get isTesting => throw _privateConstructorUsedError;
IpInfo? get ipInfo => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$NetworkDetectionStateCopyWith<NetworkDetectionState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $NetworkDetectionStateCopyWith<$Res> {
factory $NetworkDetectionStateCopyWith(NetworkDetectionState value,
$Res Function(NetworkDetectionState) then) =
_$NetworkDetectionStateCopyWithImpl<$Res, NetworkDetectionState>;
@useResult
$Res call({bool isTesting, IpInfo? ipInfo});
}
/// @nodoc
class _$NetworkDetectionStateCopyWithImpl<$Res,
$Val extends NetworkDetectionState>
implements $NetworkDetectionStateCopyWith<$Res> {
_$NetworkDetectionStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isTesting = null,
Object? ipInfo = freezed,
}) {
return _then(_value.copyWith(
isTesting: null == isTesting
? _value.isTesting
: isTesting // ignore: cast_nullable_to_non_nullable
as bool,
ipInfo: freezed == ipInfo
? _value.ipInfo
: ipInfo // ignore: cast_nullable_to_non_nullable
as IpInfo?,
) as $Val);
}
}
/// @nodoc
abstract class _$$NetworkDetectionStateImplCopyWith<$Res>
implements $NetworkDetectionStateCopyWith<$Res> {
factory _$$NetworkDetectionStateImplCopyWith(
_$NetworkDetectionStateImpl value,
$Res Function(_$NetworkDetectionStateImpl) then) =
__$$NetworkDetectionStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool isTesting, IpInfo? ipInfo});
}
/// @nodoc
class __$$NetworkDetectionStateImplCopyWithImpl<$Res>
extends _$NetworkDetectionStateCopyWithImpl<$Res,
_$NetworkDetectionStateImpl>
implements _$$NetworkDetectionStateImplCopyWith<$Res> {
__$$NetworkDetectionStateImplCopyWithImpl(_$NetworkDetectionStateImpl _value,
$Res Function(_$NetworkDetectionStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isTesting = null,
Object? ipInfo = freezed,
}) {
return _then(_$NetworkDetectionStateImpl(
isTesting: null == isTesting
? _value.isTesting
: isTesting // ignore: cast_nullable_to_non_nullable
as bool,
ipInfo: freezed == ipInfo
? _value.ipInfo
: ipInfo // ignore: cast_nullable_to_non_nullable
as IpInfo?,
));
}
}
/// @nodoc
class _$NetworkDetectionStateImpl implements _NetworkDetectionState {
const _$NetworkDetectionStateImpl(
{required this.isTesting, required this.ipInfo});
@override
final bool isTesting;
@override
final IpInfo? ipInfo;
@override
String toString() {
return 'NetworkDetectionState(isTesting: $isTesting, ipInfo: $ipInfo)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$NetworkDetectionStateImpl &&
(identical(other.isTesting, isTesting) ||
other.isTesting == isTesting) &&
(identical(other.ipInfo, ipInfo) || other.ipInfo == ipInfo));
}
@override
int get hashCode => Object.hash(runtimeType, isTesting, ipInfo);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$NetworkDetectionStateImplCopyWith<_$NetworkDetectionStateImpl>
get copyWith => __$$NetworkDetectionStateImplCopyWithImpl<
_$NetworkDetectionStateImpl>(this, _$identity);
}
abstract class _NetworkDetectionState implements NetworkDetectionState {
const factory _NetworkDetectionState(
{required final bool isTesting,
required final IpInfo? ipInfo}) = _$NetworkDetectionStateImpl;
@override
bool get isTesting;
@override
IpInfo? get ipInfo;
@override
@JsonKey(ignore: true)
_$$NetworkDetectionStateImplCopyWith<_$NetworkDetectionStateImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ApplicationSelectorState {
String? get locale => throw _privateConstructorUsedError;
@@ -2845,3 +2986,772 @@ abstract class _ProxiesActionsState implements ProxiesActionsState {
_$$ProxiesActionsStateImplCopyWith<_$ProxiesActionsStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$AutoLaunchState {
bool get isAutoLaunch => throw _privateConstructorUsedError;
bool get isOpenTun => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$AutoLaunchStateCopyWith<AutoLaunchState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $AutoLaunchStateCopyWith<$Res> {
factory $AutoLaunchStateCopyWith(
AutoLaunchState value, $Res Function(AutoLaunchState) then) =
_$AutoLaunchStateCopyWithImpl<$Res, AutoLaunchState>;
@useResult
$Res call({bool isAutoLaunch, bool isOpenTun});
}
/// @nodoc
class _$AutoLaunchStateCopyWithImpl<$Res, $Val extends AutoLaunchState>
implements $AutoLaunchStateCopyWith<$Res> {
_$AutoLaunchStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isAutoLaunch = null,
Object? isOpenTun = null,
}) {
return _then(_value.copyWith(
isAutoLaunch: null == isAutoLaunch
? _value.isAutoLaunch
: isAutoLaunch // ignore: cast_nullable_to_non_nullable
as bool,
isOpenTun: null == isOpenTun
? _value.isOpenTun
: isOpenTun // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
/// @nodoc
abstract class _$$AutoLaunchStateImplCopyWith<$Res>
implements $AutoLaunchStateCopyWith<$Res> {
factory _$$AutoLaunchStateImplCopyWith(_$AutoLaunchStateImpl value,
$Res Function(_$AutoLaunchStateImpl) then) =
__$$AutoLaunchStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool isAutoLaunch, bool isOpenTun});
}
/// @nodoc
class __$$AutoLaunchStateImplCopyWithImpl<$Res>
extends _$AutoLaunchStateCopyWithImpl<$Res, _$AutoLaunchStateImpl>
implements _$$AutoLaunchStateImplCopyWith<$Res> {
__$$AutoLaunchStateImplCopyWithImpl(
_$AutoLaunchStateImpl _value, $Res Function(_$AutoLaunchStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isAutoLaunch = null,
Object? isOpenTun = null,
}) {
return _then(_$AutoLaunchStateImpl(
isAutoLaunch: null == isAutoLaunch
? _value.isAutoLaunch
: isAutoLaunch // ignore: cast_nullable_to_non_nullable
as bool,
isOpenTun: null == isOpenTun
? _value.isOpenTun
: isOpenTun // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
class _$AutoLaunchStateImpl implements _AutoLaunchState {
const _$AutoLaunchStateImpl(
{required this.isAutoLaunch, required this.isOpenTun});
@override
final bool isAutoLaunch;
@override
final bool isOpenTun;
@override
String toString() {
return 'AutoLaunchState(isAutoLaunch: $isAutoLaunch, isOpenTun: $isOpenTun)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AutoLaunchStateImpl &&
(identical(other.isAutoLaunch, isAutoLaunch) ||
other.isAutoLaunch == isAutoLaunch) &&
(identical(other.isOpenTun, isOpenTun) ||
other.isOpenTun == isOpenTun));
}
@override
int get hashCode => Object.hash(runtimeType, isAutoLaunch, isOpenTun);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$AutoLaunchStateImplCopyWith<_$AutoLaunchStateImpl> get copyWith =>
__$$AutoLaunchStateImplCopyWithImpl<_$AutoLaunchStateImpl>(
this, _$identity);
}
abstract class _AutoLaunchState implements AutoLaunchState {
const factory _AutoLaunchState(
{required final bool isAutoLaunch,
required final bool isOpenTun}) = _$AutoLaunchStateImpl;
@override
bool get isAutoLaunch;
@override
bool get isOpenTun;
@override
@JsonKey(ignore: true)
_$$AutoLaunchStateImplCopyWith<_$AutoLaunchStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ProxyState {
bool get isStart => throw _privateConstructorUsedError;
bool get systemProxy => throw _privateConstructorUsedError;
int get port => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ProxyStateCopyWith<ProxyState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProxyStateCopyWith<$Res> {
factory $ProxyStateCopyWith(
ProxyState value, $Res Function(ProxyState) then) =
_$ProxyStateCopyWithImpl<$Res, ProxyState>;
@useResult
$Res call({bool isStart, bool systemProxy, int port});
}
/// @nodoc
class _$ProxyStateCopyWithImpl<$Res, $Val extends ProxyState>
implements $ProxyStateCopyWith<$Res> {
_$ProxyStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isStart = null,
Object? systemProxy = null,
Object? port = null,
}) {
return _then(_value.copyWith(
isStart: null == isStart
? _value.isStart
: isStart // ignore: cast_nullable_to_non_nullable
as bool,
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
port: null == port
? _value.port
: port // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$ProxyStateImplCopyWith<$Res>
implements $ProxyStateCopyWith<$Res> {
factory _$$ProxyStateImplCopyWith(
_$ProxyStateImpl value, $Res Function(_$ProxyStateImpl) then) =
__$$ProxyStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool isStart, bool systemProxy, int port});
}
/// @nodoc
class __$$ProxyStateImplCopyWithImpl<$Res>
extends _$ProxyStateCopyWithImpl<$Res, _$ProxyStateImpl>
implements _$$ProxyStateImplCopyWith<$Res> {
__$$ProxyStateImplCopyWithImpl(
_$ProxyStateImpl _value, $Res Function(_$ProxyStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isStart = null,
Object? systemProxy = null,
Object? port = null,
}) {
return _then(_$ProxyStateImpl(
isStart: null == isStart
? _value.isStart
: isStart // ignore: cast_nullable_to_non_nullable
as bool,
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
port: null == port
? _value.port
: port // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
class _$ProxyStateImpl implements _ProxyState {
const _$ProxyStateImpl(
{required this.isStart, required this.systemProxy, required this.port});
@override
final bool isStart;
@override
final bool systemProxy;
@override
final int port;
@override
String toString() {
return 'ProxyState(isStart: $isStart, systemProxy: $systemProxy, port: $port)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProxyStateImpl &&
(identical(other.isStart, isStart) || other.isStart == isStart) &&
(identical(other.systemProxy, systemProxy) ||
other.systemProxy == systemProxy) &&
(identical(other.port, port) || other.port == port));
}
@override
int get hashCode => Object.hash(runtimeType, isStart, systemProxy, port);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ProxyStateImplCopyWith<_$ProxyStateImpl> get copyWith =>
__$$ProxyStateImplCopyWithImpl<_$ProxyStateImpl>(this, _$identity);
}
abstract class _ProxyState implements ProxyState {
const factory _ProxyState(
{required final bool isStart,
required final bool systemProxy,
required final int port}) = _$ProxyStateImpl;
@override
bool get isStart;
@override
bool get systemProxy;
@override
int get port;
@override
@JsonKey(ignore: true)
_$$ProxyStateImplCopyWith<_$ProxyStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ClashConfigState {
int get mixedPort => throw _privateConstructorUsedError;
bool get allowLan => throw _privateConstructorUsedError;
bool get ipv6 => throw _privateConstructorUsedError;
String get geodataLoader => throw _privateConstructorUsedError;
LogLevel get logLevel => throw _privateConstructorUsedError;
String get externalController => throw _privateConstructorUsedError;
Mode get mode => throw _privateConstructorUsedError;
FindProcessMode get findProcessMode => throw _privateConstructorUsedError;
int get keepAliveInterval => throw _privateConstructorUsedError;
bool get unifiedDelay => throw _privateConstructorUsedError;
bool get tcpConcurrent => throw _privateConstructorUsedError;
Tun get tun => throw _privateConstructorUsedError;
Dns get dns => throw _privateConstructorUsedError;
Map<String, String> get geoXUrl => throw _privateConstructorUsedError;
List<String> get rules => throw _privateConstructorUsedError;
String? get globalRealUa => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ClashConfigStateCopyWith<ClashConfigState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ClashConfigStateCopyWith<$Res> {
factory $ClashConfigStateCopyWith(
ClashConfigState value, $Res Function(ClashConfigState) then) =
_$ClashConfigStateCopyWithImpl<$Res, ClashConfigState>;
@useResult
$Res call(
{int mixedPort,
bool allowLan,
bool ipv6,
String geodataLoader,
LogLevel logLevel,
String externalController,
Mode mode,
FindProcessMode findProcessMode,
int keepAliveInterval,
bool unifiedDelay,
bool tcpConcurrent,
Tun tun,
Dns dns,
Map<String, String> geoXUrl,
List<String> rules,
String? globalRealUa});
$TunCopyWith<$Res> get tun;
}
/// @nodoc
class _$ClashConfigStateCopyWithImpl<$Res, $Val extends ClashConfigState>
implements $ClashConfigStateCopyWith<$Res> {
_$ClashConfigStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? mixedPort = null,
Object? allowLan = null,
Object? ipv6 = null,
Object? geodataLoader = null,
Object? logLevel = null,
Object? externalController = null,
Object? mode = null,
Object? findProcessMode = null,
Object? keepAliveInterval = null,
Object? unifiedDelay = null,
Object? tcpConcurrent = null,
Object? tun = null,
Object? dns = null,
Object? geoXUrl = null,
Object? rules = null,
Object? globalRealUa = freezed,
}) {
return _then(_value.copyWith(
mixedPort: null == mixedPort
? _value.mixedPort
: mixedPort // ignore: cast_nullable_to_non_nullable
as int,
allowLan: null == allowLan
? _value.allowLan
: allowLan // ignore: cast_nullable_to_non_nullable
as bool,
ipv6: null == ipv6
? _value.ipv6
: ipv6 // ignore: cast_nullable_to_non_nullable
as bool,
geodataLoader: null == geodataLoader
? _value.geodataLoader
: geodataLoader // ignore: cast_nullable_to_non_nullable
as String,
logLevel: null == logLevel
? _value.logLevel
: logLevel // ignore: cast_nullable_to_non_nullable
as LogLevel,
externalController: null == externalController
? _value.externalController
: externalController // ignore: cast_nullable_to_non_nullable
as String,
mode: null == mode
? _value.mode
: mode // ignore: cast_nullable_to_non_nullable
as Mode,
findProcessMode: null == findProcessMode
? _value.findProcessMode
: findProcessMode // ignore: cast_nullable_to_non_nullable
as FindProcessMode,
keepAliveInterval: null == keepAliveInterval
? _value.keepAliveInterval
: keepAliveInterval // ignore: cast_nullable_to_non_nullable
as int,
unifiedDelay: null == unifiedDelay
? _value.unifiedDelay
: unifiedDelay // ignore: cast_nullable_to_non_nullable
as bool,
tcpConcurrent: null == tcpConcurrent
? _value.tcpConcurrent
: tcpConcurrent // ignore: cast_nullable_to_non_nullable
as bool,
tun: null == tun
? _value.tun
: tun // ignore: cast_nullable_to_non_nullable
as Tun,
dns: null == dns
? _value.dns
: dns // ignore: cast_nullable_to_non_nullable
as Dns,
geoXUrl: null == geoXUrl
? _value.geoXUrl
: geoXUrl // ignore: cast_nullable_to_non_nullable
as Map<String, String>,
rules: null == rules
? _value.rules
: rules // ignore: cast_nullable_to_non_nullable
as List<String>,
globalRealUa: freezed == globalRealUa
? _value.globalRealUa
: globalRealUa // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$TunCopyWith<$Res> get tun {
return $TunCopyWith<$Res>(_value.tun, (value) {
return _then(_value.copyWith(tun: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$ClashConfigStateImplCopyWith<$Res>
implements $ClashConfigStateCopyWith<$Res> {
factory _$$ClashConfigStateImplCopyWith(_$ClashConfigStateImpl value,
$Res Function(_$ClashConfigStateImpl) then) =
__$$ClashConfigStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int mixedPort,
bool allowLan,
bool ipv6,
String geodataLoader,
LogLevel logLevel,
String externalController,
Mode mode,
FindProcessMode findProcessMode,
int keepAliveInterval,
bool unifiedDelay,
bool tcpConcurrent,
Tun tun,
Dns dns,
Map<String, String> geoXUrl,
List<String> rules,
String? globalRealUa});
@override
$TunCopyWith<$Res> get tun;
}
/// @nodoc
class __$$ClashConfigStateImplCopyWithImpl<$Res>
extends _$ClashConfigStateCopyWithImpl<$Res, _$ClashConfigStateImpl>
implements _$$ClashConfigStateImplCopyWith<$Res> {
__$$ClashConfigStateImplCopyWithImpl(_$ClashConfigStateImpl _value,
$Res Function(_$ClashConfigStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? mixedPort = null,
Object? allowLan = null,
Object? ipv6 = null,
Object? geodataLoader = null,
Object? logLevel = null,
Object? externalController = null,
Object? mode = null,
Object? findProcessMode = null,
Object? keepAliveInterval = null,
Object? unifiedDelay = null,
Object? tcpConcurrent = null,
Object? tun = null,
Object? dns = null,
Object? geoXUrl = null,
Object? rules = null,
Object? globalRealUa = freezed,
}) {
return _then(_$ClashConfigStateImpl(
mixedPort: null == mixedPort
? _value.mixedPort
: mixedPort // ignore: cast_nullable_to_non_nullable
as int,
allowLan: null == allowLan
? _value.allowLan
: allowLan // ignore: cast_nullable_to_non_nullable
as bool,
ipv6: null == ipv6
? _value.ipv6
: ipv6 // ignore: cast_nullable_to_non_nullable
as bool,
geodataLoader: null == geodataLoader
? _value.geodataLoader
: geodataLoader // ignore: cast_nullable_to_non_nullable
as String,
logLevel: null == logLevel
? _value.logLevel
: logLevel // ignore: cast_nullable_to_non_nullable
as LogLevel,
externalController: null == externalController
? _value.externalController
: externalController // ignore: cast_nullable_to_non_nullable
as String,
mode: null == mode
? _value.mode
: mode // ignore: cast_nullable_to_non_nullable
as Mode,
findProcessMode: null == findProcessMode
? _value.findProcessMode
: findProcessMode // ignore: cast_nullable_to_non_nullable
as FindProcessMode,
keepAliveInterval: null == keepAliveInterval
? _value.keepAliveInterval
: keepAliveInterval // ignore: cast_nullable_to_non_nullable
as int,
unifiedDelay: null == unifiedDelay
? _value.unifiedDelay
: unifiedDelay // ignore: cast_nullable_to_non_nullable
as bool,
tcpConcurrent: null == tcpConcurrent
? _value.tcpConcurrent
: tcpConcurrent // ignore: cast_nullable_to_non_nullable
as bool,
tun: null == tun
? _value.tun
: tun // ignore: cast_nullable_to_non_nullable
as Tun,
dns: null == dns
? _value.dns
: dns // ignore: cast_nullable_to_non_nullable
as Dns,
geoXUrl: null == geoXUrl
? _value._geoXUrl
: geoXUrl // ignore: cast_nullable_to_non_nullable
as Map<String, String>,
rules: null == rules
? _value._rules
: rules // ignore: cast_nullable_to_non_nullable
as List<String>,
globalRealUa: freezed == globalRealUa
? _value.globalRealUa
: globalRealUa // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
class _$ClashConfigStateImpl implements _ClashConfigState {
const _$ClashConfigStateImpl(
{required this.mixedPort,
required this.allowLan,
required this.ipv6,
required this.geodataLoader,
required this.logLevel,
required this.externalController,
required this.mode,
required this.findProcessMode,
required this.keepAliveInterval,
required this.unifiedDelay,
required this.tcpConcurrent,
required this.tun,
required this.dns,
required final Map<String, String> geoXUrl,
required final List<String> rules,
required this.globalRealUa})
: _geoXUrl = geoXUrl,
_rules = rules;
@override
final int mixedPort;
@override
final bool allowLan;
@override
final bool ipv6;
@override
final String geodataLoader;
@override
final LogLevel logLevel;
@override
final String externalController;
@override
final Mode mode;
@override
final FindProcessMode findProcessMode;
@override
final int keepAliveInterval;
@override
final bool unifiedDelay;
@override
final bool tcpConcurrent;
@override
final Tun tun;
@override
final Dns dns;
final Map<String, String> _geoXUrl;
@override
Map<String, String> get geoXUrl {
if (_geoXUrl is EqualUnmodifiableMapView) return _geoXUrl;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_geoXUrl);
}
final List<String> _rules;
@override
List<String> get rules {
if (_rules is EqualUnmodifiableListView) return _rules;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_rules);
}
@override
final String? globalRealUa;
@override
String toString() {
return 'ClashConfigState(mixedPort: $mixedPort, allowLan: $allowLan, ipv6: $ipv6, geodataLoader: $geodataLoader, logLevel: $logLevel, externalController: $externalController, mode: $mode, findProcessMode: $findProcessMode, keepAliveInterval: $keepAliveInterval, unifiedDelay: $unifiedDelay, tcpConcurrent: $tcpConcurrent, tun: $tun, dns: $dns, geoXUrl: $geoXUrl, rules: $rules, globalRealUa: $globalRealUa)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ClashConfigStateImpl &&
(identical(other.mixedPort, mixedPort) ||
other.mixedPort == mixedPort) &&
(identical(other.allowLan, allowLan) ||
other.allowLan == allowLan) &&
(identical(other.ipv6, ipv6) || other.ipv6 == ipv6) &&
(identical(other.geodataLoader, geodataLoader) ||
other.geodataLoader == geodataLoader) &&
(identical(other.logLevel, logLevel) ||
other.logLevel == logLevel) &&
(identical(other.externalController, externalController) ||
other.externalController == externalController) &&
(identical(other.mode, mode) || other.mode == mode) &&
(identical(other.findProcessMode, findProcessMode) ||
other.findProcessMode == findProcessMode) &&
(identical(other.keepAliveInterval, keepAliveInterval) ||
other.keepAliveInterval == keepAliveInterval) &&
(identical(other.unifiedDelay, unifiedDelay) ||
other.unifiedDelay == unifiedDelay) &&
(identical(other.tcpConcurrent, tcpConcurrent) ||
other.tcpConcurrent == tcpConcurrent) &&
(identical(other.tun, tun) || other.tun == tun) &&
(identical(other.dns, dns) || other.dns == dns) &&
const DeepCollectionEquality().equals(other._geoXUrl, _geoXUrl) &&
const DeepCollectionEquality().equals(other._rules, _rules) &&
(identical(other.globalRealUa, globalRealUa) ||
other.globalRealUa == globalRealUa));
}
@override
int get hashCode => Object.hash(
runtimeType,
mixedPort,
allowLan,
ipv6,
geodataLoader,
logLevel,
externalController,
mode,
findProcessMode,
keepAliveInterval,
unifiedDelay,
tcpConcurrent,
tun,
dns,
const DeepCollectionEquality().hash(_geoXUrl),
const DeepCollectionEquality().hash(_rules),
globalRealUa);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ClashConfigStateImplCopyWith<_$ClashConfigStateImpl> get copyWith =>
__$$ClashConfigStateImplCopyWithImpl<_$ClashConfigStateImpl>(
this, _$identity);
}
abstract class _ClashConfigState implements ClashConfigState {
const factory _ClashConfigState(
{required final int mixedPort,
required final bool allowLan,
required final bool ipv6,
required final String geodataLoader,
required final LogLevel logLevel,
required final String externalController,
required final Mode mode,
required final FindProcessMode findProcessMode,
required final int keepAliveInterval,
required final bool unifiedDelay,
required final bool tcpConcurrent,
required final Tun tun,
required final Dns dns,
required final Map<String, String> geoXUrl,
required final List<String> rules,
required final String? globalRealUa}) = _$ClashConfigStateImpl;
@override
int get mixedPort;
@override
bool get allowLan;
@override
bool get ipv6;
@override
String get geodataLoader;
@override
LogLevel get logLevel;
@override
String get externalController;
@override
Mode get mode;
@override
FindProcessMode get findProcessMode;
@override
int get keepAliveInterval;
@override
bool get unifiedDelay;
@override
bool get tcpConcurrent;
@override
Tun get tun;
@override
Dns get dns;
@override
Map<String, String> get geoXUrl;
@override
List<String> get rules;
@override
String? get globalRealUa;
@override
@JsonKey(ignore: true)
_$$ClashConfigStateImplCopyWith<_$ClashConfigStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -41,6 +41,14 @@ class ProfilesSelectorState with _$ProfilesSelectorState {
}) = _ProfilesSelectorState;
}
@freezed
class NetworkDetectionState with _$NetworkDetectionState {
const factory NetworkDetectionState({
required bool isTesting,
required IpInfo? ipInfo,
}) = _NetworkDetectionState;
}
@freezed
class ApplicationSelectorState with _$ApplicationSelectorState {
const factory ApplicationSelectorState({
@@ -148,19 +156,19 @@ extension PackageListSelectorStateExt on PackageListSelectorState {
return packages
.where((item) => isFilterSystemApp ? item.isSystem == false : true)
.sorted(
(a, b) {
(a, b) {
return switch (sort) {
AccessSortType.none => 0,
AccessSortType.name =>
other.sortByChar(
PinyinHelper.getPinyin(a.label),
PinyinHelper.getPinyin(b.label),
),
AccessSortType.time => a.firstInstallTime.compareTo(b.firstInstallTime),
AccessSortType.name => other.sortByChar(
PinyinHelper.getPinyin(a.label),
PinyinHelper.getPinyin(b.label),
),
AccessSortType.time =>
a.firstInstallTime.compareTo(b.firstInstallTime),
};
},
).sorted(
(a, b) {
(a, b) {
final isSelectA = selectedList.contains(a.packageName);
final isSelectB = selectedList.contains(b.packageName);
if (isSelectA && isSelectB) return 0;
@@ -187,3 +195,42 @@ class ProxiesActionsState with _$ProxiesActionsState {
required bool hasProvider,
}) = _ProxiesActionsState;
}
@freezed
class AutoLaunchState with _$AutoLaunchState {
const factory AutoLaunchState({
required bool isAutoLaunch,
required bool isOpenTun,
}) = _AutoLaunchState;
}
@freezed
class ProxyState with _$ProxyState {
const factory ProxyState({
required bool isStart,
required bool systemProxy,
required int port,
}) = _ProxyState;
}
@freezed
class ClashConfigState with _$ClashConfigState {
const factory ClashConfigState({
required int mixedPort,
required bool allowLan,
required bool ipv6,
required String geodataLoader,
required LogLevel logLevel,
required String externalController,
required Mode mode,
required FindProcessMode findProcessMode,
required int keepAliveInterval,
required bool unifiedDelay,
required bool tcpConcurrent,
required Tun tun,
required Dns dns,
required GeoXMap geoXUrl,
required List<String> rules,
required String? globalRealUa,
}) = _ClashConfigState;
}

View File

@@ -13,20 +13,6 @@ typedef OnSelected = void Function(int index);
class HomePage extends StatelessWidget {
const HomePage({super.key});
_navigationBarContainer({
required BuildContext context,
required Widget child,
}) {
// if (!system.isDesktop) return child;
return Container(
padding: const EdgeInsets.all(16).copyWith(
right: 0,
),
color: context.colorScheme.surface,
child: child,
);
}
_getNavigationBar({
required BuildContext context,
required ViewMode viewMode,
@@ -47,61 +33,78 @@ class HomePage extends StatelessWidget {
selectedIndex: currentIndex,
);
}
final extended = viewMode == ViewMode.desktop;
return _navigationBarContainer(
context: context,
child: NavigationRail(
groupAlignment: -0.8,
selectedIconTheme: IconThemeData(
color: context.colorScheme.onSurfaceVariant,
),
unselectedIconTheme: IconThemeData(
color: context.colorScheme.onSurfaceVariant,
),
selectedLabelTextStyle: context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
unselectedLabelTextStyle: context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
destinations: navigationItems
.map(
(e) => NavigationRailDestination(
icon: e.icon,
label: Text(
Intl.message(e.label),
),
),
)
.toList(),
onDestinationSelected: globalState.appController.toPage,
extended: extended,
minExtendedWidth: 200,
selectedIndex: currentIndex,
labelType: extended
? NavigationRailLabelType.none
: NavigationRailLabelType.selected,
),
);
return NavigationRail(
groupAlignment: -0.95,
destinations: navigationItems
.map(
(e) => NavigationRailDestination(
icon: e.icon,
label: Text(
Intl.message(e.label),
),
return LayoutBuilder(
builder: (_, container) {
return Material(
color: context.colorScheme.surfaceContainer,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 16,
),
)
.toList(),
onDestinationSelected: globalState.appController.toPage,
extended: extended,
minExtendedWidth: 172,
selectedIndex: currentIndex,
labelType: extended
? NavigationRailLabelType.none
: NavigationRailLabelType.selected,
height: container.maxHeight,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: IntrinsicHeight(
child: Selector<Config, bool>(
selector: (_, config) => config.showLabel,
builder: (_, showLabel, __) {
return NavigationRail(
backgroundColor:
context.colorScheme.surfaceContainer,
selectedIconTheme: IconThemeData(
color: context.colorScheme.onSurfaceVariant,
),
unselectedIconTheme: IconThemeData(
color: context.colorScheme.onSurfaceVariant,
),
selectedLabelTextStyle:
context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
unselectedLabelTextStyle:
context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
destinations: navigationItems
.map(
(e) => NavigationRailDestination(
icon: e.icon,
label: Text(
Intl.message(e.label),
),
),
)
.toList(),
onDestinationSelected:
globalState.appController.toPage,
extended: false,
selectedIndex: currentIndex,
labelType: showLabel
? NavigationRailLabelType.all
: NavigationRailLabelType.none,
);
},
),
),
),
),
const SizedBox(
height: 16,
),
IconButton(
onPressed: () {
final config = globalState.appController.config;
config.showLabel = !config.showLabel;
},
icon: const Icon(Icons.menu),
)
],
),
),
);
},
);
}

29
lib/plugins/service.dart Normal file
View File

@@ -0,0 +1,29 @@
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'package:flutter/services.dart';
class Service {
static Service? _instance;
late MethodChannel methodChannel;
ReceivePort? receiver;
Service._internal() {
methodChannel = const MethodChannel("service");
}
factory Service() {
_instance ??= Service._internal();
return _instance!;
}
Future<bool?> init() async {
return await methodChannel.invokeMethod<bool>("init");
}
Future<bool?> destroy() async {
return await methodChannel.invokeMethod<bool>("destroy");
}
}
final service = Platform.isAndroid ? Service() : null;

View File

@@ -4,22 +4,18 @@ import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:proxy/proxy_platform_interface.dart';
class Proxy extends ProxyPlatform {
static Proxy? _instance;
class Vpn {
static Vpn? _instance;
late MethodChannel methodChannel;
ReceivePort? receiver;
ServiceMessageListener? _serviceMessageHandler;
Proxy._internal() {
methodChannel = const MethodChannel("proxy");
Vpn._internal() {
methodChannel = const MethodChannel("vpn");
methodChannel.setMethodCallHandler((call) async {
switch (call.method) {
case "started":
@@ -32,36 +28,21 @@ class Proxy extends ProxyPlatform {
});
}
factory Proxy() {
_instance ??= Proxy._internal();
factory Vpn() {
_instance ??= Vpn._internal();
return _instance!;
}
Future<bool?> initService() async {
return await methodChannel.invokeMethod<bool>("initService");
}
handleStop() {
globalState.stopSystemProxy();
}
@override
Future<bool?> startProxy(port) async {
Future<bool?> startVpn(port) async {
final state = clashCore.getState();
return await methodChannel.invokeMethod<bool>("startProxy", {
return await methodChannel.invokeMethod<bool>("start", {
'port': state.mixedPort,
'args': json.encode(state),
});
}
@override
Future<bool?> stopProxy() async {
clashCore.stopTun();
final isStop = await methodChannel.invokeMethod<bool>("stopProxy");
if (isStop == true) {
startTime = null;
}
return isStop;
Future<bool?> stopVpn() async {
return await methodChannel.invokeMethod<bool>("stop");
}
Future<bool?> setProtect(int fd) async {
@@ -78,10 +59,7 @@ class Proxy extends ProxyPlatform {
});
}
bool get isStart => startTime != null && startTime!.isBeforeNow;
onStarted(int? fd) {
if (fd == null) return;
if (receiver != null) {
receiver!.close();
receiver == null;
@@ -90,11 +68,7 @@ class Proxy extends ProxyPlatform {
receiver!.listen((message) {
_handleServiceMessage(message);
});
clashCore.startTun(fd, receiver!.sendPort.nativePort);
}
updateStartTime() {
startTime = clashCore.getRunTime();
clashCore.startTun(fd ?? 0, receiver!.sendPort.nativePort);
}
setServiceMessageHandler(ServiceMessageListener serviceMessageListener) {
@@ -103,7 +77,6 @@ class Proxy extends ProxyPlatform {
_handleServiceMessage(String message) {
final m = ServiceMessage.fromJson(json.decode(message));
debugPrint(m.toString());
switch (m.type) {
case ServiceMessageType.protect:
_serviceMessageHandler?.onProtect(Fd.fromJson(m.data));
@@ -117,4 +90,4 @@ class Proxy extends ProxyPlatform {
}
}
final proxy = Platform.isAndroid ? Proxy() : null;
final vpn = Platform.isAndroid ? Vpn() : null;

View File

@@ -3,7 +3,8 @@ import 'dart:io';
import 'package:animations/animations.dart';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/plugins/proxy.dart';
import 'package:fl_clash/plugins/service.dart';
import 'package:fl_clash/plugins/vpn.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
@@ -21,11 +22,14 @@ class GlobalState {
late PackageInfo packageInfo;
Function? updateCurrentDelayDebounce;
PageController? pageController;
DateTime? startTime;
final navigatorKey = GlobalKey<NavigatorState>();
late AppController appController;
GlobalKey<CommonScaffoldState> homeScaffoldKey = GlobalKey();
List<Function> updateFunctionLists = [];
bool get isStart => startTime != null && startTime!.isBeforeNow;
startListenUpdate() {
if (timer != null && timer!.isActive == true) return;
timer = Timer.periodic(const Duration(seconds: 1), (Timer t) {
@@ -65,23 +69,32 @@ class GlobalState {
appState.versionInfo = clashCore.getVersionInfo();
}
Future<void> startSystemProxy({
required AppState appState,
handleStart({
required Config config,
required ClashConfig clashConfig,
}) async {
if (!globalState.isVpnService && Platform.isAndroid) {
await proxy?.initService();
} else {
await proxyManager.startProxy(
port: clashConfig.mixedPort,
);
clashCore.start();
if (globalState.isVpnService) {
await vpn?.startVpn(clashConfig.mixedPort);
startListenUpdate();
return;
}
startTime ??= DateTime.now();
await service?.init();
startListenUpdate();
}
Future<void> stopSystemProxy() async {
await proxyManager.stopProxy();
updateStartTime() {
startTime = clashCore.getRunTime();
}
handleStop() async {
clashCore.stop();
if (Platform.isAndroid) {
clashCore.stopTun();
}
await service?.destroy();
startTime = null;
stopListenUpdate();
}
@@ -116,12 +129,14 @@ class GlobalState {
);
clashCore.setState(
CoreState(
enable: config.vpnProps.enable,
accessControl: config.isAccessControl ? config.accessControl : null,
allowBypass: config.allowBypass,
systemProxy: config.systemProxy,
allowBypass: config.vpnProps.allowBypass,
systemProxy: config.vpnProps.systemProxy,
mixedPort: clashConfig.mixedPort,
onlyProxy: config.onlyProxy,
currentProfileName: config.currentProfile?.label ?? config.currentProfileId ?? "",
currentProfileName:
config.currentProfile?.label ?? config.currentProfileId ?? "",
),
);
}
@@ -207,7 +222,7 @@ class GlobalState {
}) {
final traffic = clashCore.getTraffic();
if (Platform.isAndroid && isVpnService == true) {
proxy?.startForeground(
vpn?.startForeground(
title: clashCore.getState().currentProfileName,
content: "$traffic",
);

View File

@@ -1,5 +1,6 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'text.dart';
@@ -29,12 +30,13 @@ class InfoHeader extends StatelessWidget {
return Container(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
Flexible(
flex: 1,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.max,
children: [
if (info.iconData != null) ...[
Icon(
@@ -46,6 +48,7 @@ class InfoHeader extends StatelessWidget {
),
],
Flexible(
flex: 1,
child: TooltipText(
text: Text(
info.label,
@@ -58,6 +61,9 @@ class InfoHeader extends StatelessWidget {
],
),
),
const SizedBox(
width: 8,
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
@@ -155,6 +161,18 @@ class CommonCard extends StatelessWidget {
],
);
}
if (selectWidget != null && isSelected) {
final List<Widget> children = [];
children.add(childWidget);
children.add(
Positioned.fill(
child: selectWidget!,
),
);
childWidget = Stack(
children: children,
);
}
return OutlinedButton(
clipBehavior: Clip.antiAlias,
style: ButtonStyle(
@@ -172,25 +190,7 @@ class CommonCard extends StatelessWidget {
),
),
onPressed: onPressed,
child: Builder(
builder: (_) {
if (selectWidget == null) {
return childWidget;
}
List<Widget> children = [];
children.add(childWidget);
if (isSelected) {
children.add(
Positioned.fill(
child: selectWidget!,
),
);
}
return Stack(
children: children,
);
},
),
child: childWidget,
);
}
}

View File

@@ -1,10 +1,11 @@
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/proxy.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../common/function.dart';
class ClashContainer extends StatefulWidget {
final Widget child;
@@ -19,12 +20,49 @@ class ClashContainer extends StatefulWidget {
class _ClashContainerState extends State<ClashContainer>
with AppMessageListener {
Function? updateClashConfigDebounce;
Widget _updateContainer(Widget child) {
return Selector<ClashConfig, ClashConfigState>(
selector: (_, clashConfig) => ClashConfigState(
mixedPort: clashConfig.mixedPort,
allowLan: clashConfig.allowLan,
ipv6: clashConfig.ipv6,
logLevel: clashConfig.logLevel,
geodataLoader: clashConfig.geodataLoader,
externalController: clashConfig.externalController,
mode: clashConfig.mode,
findProcessMode: clashConfig.findProcessMode,
keepAliveInterval: clashConfig.keepAliveInterval,
unifiedDelay: clashConfig.unifiedDelay,
tcpConcurrent: clashConfig.tcpConcurrent,
tun: clashConfig.tun,
dns: clashConfig.dns,
geoXUrl: clashConfig.geoXUrl,
rules: clashConfig.rules,
globalRealUa: clashConfig.globalRealUa,
),
builder: (__, state, child) {
if (updateClashConfigDebounce == null) {
updateClashConfigDebounce = debounce<Function()>(() async {
await globalState.appController.updateClashConfig();
});
} else {
updateClashConfigDebounce!();
}
return child!;
},
child: child,
);
}
Widget _updateCoreState(Widget child) {
return Selector2<Config, ClashConfig, CoreState>(
selector: (_, config, clashConfig) => CoreState(
accessControl: config.isAccessControl ? config.accessControl : null,
allowBypass: config.allowBypass,
systemProxy: config.systemProxy,
enable: config.vpnProps.enable,
allowBypass: config.vpnProps.allowBypass,
systemProxy: config.vpnProps.systemProxy,
mixedPort: clashConfig.mixedPort,
onlyProxy: config.onlyProxy,
currentProfileName:
@@ -61,7 +99,9 @@ class _ClashContainerState extends State<ClashContainer>
Widget build(BuildContext context) {
return _changeProfileContainer(
_updateCoreState(
widget.child,
_updateContainer(
widget.child,
),
),
);
}
@@ -89,6 +129,7 @@ class _ClashContainerState extends State<ClashContainer>
@override
void onLog(Log log) {
globalState.appController.appState.addLog(log);
debugPrint("$log");
super.onLog(log);
}
@@ -113,9 +154,7 @@ class _ClashContainerState extends State<ClashContainer>
@override
Future<void> onStarted(String runTime) async {
super.onStarted(runTime);
proxy?.updateStartTime();
final appController = globalState.appController;
await appController.applyProfile(isPrue: true);
appController.addCheckIpNumDebounce();
}
}

View File

@@ -0,0 +1,37 @@
import 'package:fl_clash/common/proxy.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ProxyContainer extends StatelessWidget {
final Widget child;
const ProxyContainer({super.key, required this.child});
_updateProxy(ProxyState proxyState) {
final isStart = proxyState.isStart;
final systemProxy = proxyState.systemProxy;
final port = proxyState.port;
if (isStart && systemProxy) {
proxy?.startProxy(port);
}else{
proxy?.stopProxy();
}
}
@override
Widget build(BuildContext context) {
return Selector3<AppState, Config, ClashConfig, ProxyState>(
selector: (_, appState, config, clashConfig) => ProxyState(
isStart: appState.isStart,
systemProxy: config.desktopProps.systemProxy,
port: clashConfig.mixedPort,
),
builder: (_, state, child) {
_updateProxy(state);
return child!;
},
child: child,
);
}
}

View File

@@ -109,7 +109,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
valueListenable: _actions,
builder: (_, actions, __) {
final realActions =
actions.isNotEmpty ? actions : widget.actions;
actions.isNotEmpty ? actions : widget.actions;
return AppBar(
centerTitle: false,
automaticallyImplyLeading: widget.automaticallyImplyLeading,

View File

@@ -24,13 +24,13 @@ class _TileContainerState extends State<TileContainer> with TileListener {
@override
void onStart() {
globalState.appController.updateSystemProxy(true);
globalState.appController.updateStatus(true);
super.onStart();
}
@override
void onStop() {
globalState.appController.updateSystemProxy(false);
globalState.appController.updateStatus(false);
super.onStop();
}

View File

@@ -32,12 +32,12 @@ class _TrayContainerState extends State<TrayContainer> with TrayListener {
_updateOtherTray() async {
if (isTrayInit == false) {
await trayManager.setIcon(
other.getTrayIconPath(),
);
await trayManager.setToolTip(
appName,
);
await trayManager.setIcon(
other.getTrayIconPath(),
);
isTrayInit = true;
}
}
@@ -110,7 +110,7 @@ class _TrayContainerState extends State<TrayContainer> with TrayListener {
final proxyMenuItem = MenuItem.checkbox(
label: appLocalizations.systemProxy,
onClick: (_) async {
globalState.appController.updateSystemProxy(!state.isRun);
globalState.appController.updateStatus(!state.isRun);
},
checked: state.isRun,
);

View File

@@ -23,8 +23,8 @@ class _WindowContainerState extends State<WindowContainer> with WindowListener {
_autoLaunchContainer(Widget child) {
return Selector<Config, bool>(
selector: (_, config) => config.autoLaunch,
builder: (_, isAutoLaunch, child) {
autoLaunch?.updateStatus(isAutoLaunch);
builder: (_, state, child) {
autoLaunch?.updateStatus(state);
return child!;
},
child: child,
@@ -33,22 +33,7 @@ class _WindowContainerState extends State<WindowContainer> with WindowListener {
@override
Widget build(BuildContext context) {
return Stack(
children: [
Column(
children: [
SizedBox(
height: kHeaderHeight,
),
Expanded(
flex: 1,
child: _autoLaunchContainer(widget.child),
),
],
),
const WindowHeader(),
],
);
return _autoLaunchContainer(widget.child);
}
@override
@@ -98,6 +83,35 @@ class _WindowContainerState extends State<WindowContainer> with WindowListener {
}
}
class WindowHeaderContainer extends StatelessWidget {
final Widget child;
const WindowHeaderContainer({
super.key,
required this.child,
});
@override
Widget build(BuildContext context) {
return Stack(
children: [
Column(
children: [
SizedBox(
height: kHeaderHeight,
),
Expanded(
flex: 1,
child: child,
),
],
),
const WindowHeader(),
],
);
}
}
class WindowHeader extends StatefulWidget {
const WindowHeader({super.key});
@@ -188,7 +202,7 @@ class _WindowHeaderState extends State<WindowHeader> {
),
IconButton(
onPressed: () {
windowManager.close();
globalState.appController.handleBackOrExit();
},
icon: const Icon(Icons.close),
),
@@ -214,7 +228,7 @@ class _WindowHeaderState extends State<WindowHeader> {
_updateMaximized();
},
child: Container(
color: context.colorScheme.surface,
color: context.colorScheme.secondary.toSoft(),
alignment: Alignment.centerLeft,
height: kHeaderHeight,
),