Compare commits

..

2 Commits

Author SHA1 Message Date
chen08209
30dc3f4d13 Fix android system dns issues
Optimize dns default option

Fix some issues
2024-09-20 11:20:31 +08:00
chen08209
2c5f8525a7 Update readme 2024-09-18 00:41:46 +08:00
55 changed files with 30449 additions and 35129 deletions

View File

@@ -34,22 +34,22 @@ def isRelease = defStoreFile.exists() && defStorePassword != null && defKeyAlias
android {
namespace "com.follow.clash"
compileSdkVersion 34
ndkVersion "27.1.12297006"
ndkVersion "25.1.8937393"
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '17'
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
signingConfigs {
if (isRelease) {
if (isRelease){
release {
storeFile defStoreFile
storePassword defStorePassword
@@ -74,9 +74,10 @@ android {
applicationIdSuffix '.debug'
}
release {
if (isRelease) {
minifyEnabled true
if(isRelease){
signingConfig signingConfigs.release
} else {
}else{
signingConfig signingConfigs.debug
}
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"

View File

@@ -10,22 +10,25 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
tools:ignore="SystemPermissionTypo" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
tools:ignore="SystemPermissionTypo" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application
android:name="${applicationName}"
android:enableOnBackInvokedCallback="true"
android:extractNativeLibs="true"
android:icon="@mipmap/ic_launcher"
android:hardwareAccelerated="true"
android:label="FlClash">
android:label="FlClash"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="tiramisu">
<activity
android:name="com.follow.clash.MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
@@ -61,16 +64,6 @@
<data android:host="install-config" />
</intent-filter>
</activity>
<!-- <meta-data-->
<!-- android:name="io.flutter.embedding.android.EnableImpeller"-->
<!-- android:value="true" />-->
<activity
android:name=".TempActivity"
android:exported="true"
android:theme="@style/TransparentTheme">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="com.follow.clash.action.START" />
@@ -81,6 +74,14 @@
</intent-filter>
</activity>
<!-- <meta-data-->
<!-- android:name="io.flutter.embedding.android.EnableImpeller"-->
<!-- android:value="true" />-->
<activity
android:name=".TempActivity"
android:theme="@style/TransparentTheme" />
<service
android:name=".services.FlClashTileService"
android:exported="true"
@@ -126,19 +127,12 @@
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="vpn" />
</service>
<service
android:name=".services.FlClashService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="service" />
</service>
android:foregroundServiceType="specialUse" />
<meta-data
android:name="flutterEmbedding"

View File

@@ -11,6 +11,19 @@ import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity : FlutterActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
when (intent.action) {
"com.follow.clash.action.START" -> {
GlobalState.getCurrentTilePlugin()?.handleStart()
}
"com.follow.clash.action.STOP" -> {
GlobalState.getCurrentTilePlugin()?.handleStop()
}
}
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine.plugins.add(AppPlugin())

View File

@@ -6,15 +6,6 @@ import android.os.Bundle
class TempActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
when (intent.action) {
"com.follow.clash.action.START" -> {
GlobalState.getCurrentTilePlugin()?.handleStart()
}
"com.follow.clash.action.STOP" -> {
GlobalState.getCurrentTilePlugin()?.handleStop()
}
}
finishAndRemoveTask()
}
}

View File

@@ -0,0 +1,26 @@
package com.follow.clash.models
import android.net.NetworkCapabilities
import android.os.Build
val TRANSPORT_PRIORITY = sequence {
yield(NetworkCapabilities.TRANSPORT_CELLULAR)
if (Build.VERSION.SDK_INT >= 27) {
yield(NetworkCapabilities.TRANSPORT_LOWPAN)
}
yield(NetworkCapabilities.TRANSPORT_BLUETOOTH)
if (Build.VERSION.SDK_INT >= 26) {
yield(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
}
yield(NetworkCapabilities.TRANSPORT_WIFI)
if (Build.VERSION.SDK_INT >= 31) {
yield(NetworkCapabilities.TRANSPORT_USB)
}
yield(NetworkCapabilities.TRANSPORT_ETHERNET)
}.toList()

View File

@@ -16,7 +16,6 @@ data class Props(
val accessControl: AccessControl?,
val allowBypass: Boolean?,
val systemProxy: Boolean?,
val ipv6: Boolean?,
)
data class TunProps(

View File

@@ -1,5 +1,6 @@
package com.follow.clash.plugins
import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -162,7 +163,8 @@ class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
}
}
private fun handleStartVpn() {
@SuppressLint("ForegroundServiceType")
fun handleStartVpn() {
GlobalState.getCurrentAppPlugin()?.requestVpnPermission(context) {
start()
}
@@ -226,6 +228,7 @@ class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
onUpdateNetwork()
}
@SuppressLint("ForegroundServiceType")
private fun startForeground(title: String, content: String) {
GlobalState.runLock.withLock {
if (GlobalState.runState.value != RunState.START) return

View File

@@ -11,13 +11,14 @@ import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import com.follow.clash.BaseServiceInterface
import com.follow.clash.MainActivity
import com.follow.clash.models.Props
@SuppressLint("WrongConstant")
class FlClashService : Service(), BaseServiceInterface {
private val binder = LocalBinder()

View File

@@ -1,6 +1,5 @@
package com.follow.clash.services
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
@@ -38,7 +37,6 @@ class FlClashTileService : TileService() {
GlobalState.runState.observeForever(observer)
}
@SuppressLint("StartActivityAndCollapseDeprecated")
private fun activityTransfer() {
val intent = Intent(this, TempActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)

View File

@@ -15,12 +15,12 @@ import android.os.Build
import android.os.IBinder
import android.os.Parcel
import android.os.RemoteException
import android.util.Log
import androidx.core.app.NotificationCompat
import com.follow.clash.BaseServiceInterface
import com.follow.clash.GlobalState
import com.follow.clash.MainActivity
import com.follow.clash.R
import com.follow.clash.TempActivity
import com.follow.clash.models.AccessControlMode
import com.follow.clash.models.Props
import com.follow.clash.models.TunProps
@@ -29,6 +29,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@SuppressLint("WrongConstant")
class FlClashVpnService : VpnService(), BaseServiceInterface {
companion object {
@@ -72,19 +73,11 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
override fun start(port: Int, props: Props?): TunProps {
return with(Builder()) {
addAddress(TUN_GATEWAY, TUN_SUBNET_PREFIX)
addAddress(TUN_GATEWAY6, TUN_SUBNET_PREFIX6)
addRoute(NET_ANY, 0)
addRoute(NET_ANY6, 0)
addDnsServer(TUN_DNS)
if (props?.ipv6 == true) {
try {
addAddress(TUN_GATEWAY6, TUN_SUBNET_PREFIX6)
addRoute(NET_ANY6, 0)
addDnsServer(TUN_DNS6)
} catch (_: Exception) {
}
}
addDnsServer(TUN_DNS6)
setMtu(TUN_MTU)
props?.accessControl?.let { accessControl ->
when (accessControl.mode) {
@@ -122,16 +115,16 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
fd = establish()?.detachFd()
?: throw NullPointerException("Establish VPN rejected by system"),
gateway = "$TUN_GATEWAY/$TUN_SUBNET_PREFIX",
gateway6 = if (props?.ipv6 == true) "$TUN_GATEWAY6/$TUN_SUBNET_PREFIX6" else "",
gateway6 = "$TUN_GATEWAY6/$TUN_SUBNET_PREFIX6",
portal = TUN_PORTAL,
portal6 = if (props?.ipv6 == true) TUN_PORTAL6 else "",
portal6 = TUN_PORTAL6,
dns = TUN_DNS,
dns6 = if (props?.ipv6 == true) TUN_DNS6 else ""
dns6 = TUN_DNS6
)
}
}
fun updateUnderlyingNetworks(networks: Array<Network>) {
fun updateUnderlyingNetworks( networks: Array<Network>){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
this.setUnderlyingNetworks(networks)
}
@@ -167,23 +160,18 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
)
}
val stopIntent = Intent(this, TempActivity::class.java)
stopIntent.action = "com.follow.clash.action.STOP"
stopIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
val stopPendingIntent = if (Build.VERSION.SDK_INT >= 31) {
PendingIntent.getActivity(
this,
0,
stopIntent,
Intent("com.follow.clash.action.STOP"),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
} else {
PendingIntent.getActivity(
this,
0,
stopIntent,
Intent("com.follow.clash.action.STOP"),
PendingIntent.FLAG_UPDATE_CURRENT
)
}

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -18,7 +18,6 @@ type AndroidProps struct {
AccessControl *AccessControl `json:"accessControl"`
AllowBypass bool `json:"allowBypass"`
SystemProxy bool `json:"systemProxy"`
Ipv6 bool `json:"ipv6"`
}
type State struct {

View File

@@ -31,21 +31,18 @@ func Start(tunProps Props) (*sing_tun.Listener, error) {
return nil, err
}
prefix4 = append(prefix4, tempPrefix4)
var prefix6 []netip.Prefix
if tunProps.Gateway6 != "" {
tempPrefix6, err := netip.ParsePrefix(tunProps.Gateway6)
if err != nil {
log.Errorln("startTUN error:", err)
return nil, err
}
prefix6 = append(prefix6, tempPrefix6)
tempPrefix6, err := netip.ParsePrefix(tunProps.Gateway6)
if err != nil {
log.Errorln("startTUN error:", err)
return nil, err
}
prefix6 = append(prefix6, tempPrefix6)
var dnsHijack []string
dnsHijack = append(dnsHijack, net.JoinHostPort(tunProps.Dns, "53"))
if tunProps.Dns6 != "" {
dnsHijack = append(dnsHijack, net.JoinHostPort(tunProps.Dns6, "53"))
}
dnsHijack = append(dnsHijack, net.JoinHostPort(tunProps.Dns6, "53"))
options := LC.Tun{
Enable: true,

View File

@@ -1,13 +1,10 @@
import 'dart:io';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
class Android {
init() async {
app?.onExit = () async {
await globalState.appController.savePreferences();
};
app?.onExit = () {};
}
}

View File

@@ -28,5 +28,4 @@ export 'iterable.dart';
export 'scroll.dart';
export 'icons.dart';
export 'http.dart';
export 'keyboard.dart';
export 'network.dart';
export 'keyboard.dart';

View File

@@ -26,9 +26,9 @@ const GeoXMap defaultGeoXMap = {
"mmdb":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
"asn":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb",
"https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb",
"geoip":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat",
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoIP.dat",
"geosite":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat"
};

View File

@@ -15,6 +15,9 @@ class FlClashHttpOverrides extends HttpOverrides {
final port = appController.clashConfig.mixedPort;
final isStart = appController.appFlowingState.isStart;
if (!isStart) return "DIRECT";
if (appController.appState.groups.isEmpty) {
return "DIRECT";
}
return "PROXY localhost:$port";
};
return client;

View File

@@ -15,10 +15,4 @@ extension ListExtension<T> on List<T> {
}
return res;
}
List<T> safeSublist(int start) {
if(start <= 0) return this;
if(start > length) return [];
return sublist(start);
}
}

View File

@@ -1,25 +0,0 @@
import 'dart:io';
extension NetworkInterfaceExt on NetworkInterface {
bool get isWifi {
final nameLowCase = name.toLowerCase();
if (nameLowCase.contains('wlan') ||
nameLowCase.contains('wi-fi') ||
nameLowCase == 'en0' ||
nameLowCase == 'eth0') {
return true;
}
return false;
}
bool get includesIPv4 {
return addresses.any((addr) => addr.isIPv4);
}
}
extension InternetAddressExt on InternetAddress {
bool get isIPv4 {
return type == InternetAddressType.IPv4;
}
}

View File

@@ -171,18 +171,14 @@ class Other {
if (disposition == null) return null;
final parseValue = HeaderValue.parse(disposition);
final parameters = parseValue.parameters;
final fileNamePointKey = parameters.keys
.firstWhere((key) => key == "filename*", orElse: () => "");
if (fileNamePointKey.isNotEmpty) {
final res = parameters[fileNamePointKey]?.split("''") ?? [];
if (res.length >= 2) {
return Uri.decodeComponent(res[1]);
}
final key = parameters.keys
.firstWhere((key) => key.startsWith("filename"), orElse: () => '');
if (key.isEmpty) return null;
if (key == "filename*") {
return Uri.decodeComponent((parameters[key] ?? "").split("'").last);
} else {
return parameters[key];
}
final fileNameKey = parameters.keys
.firstWhere((key) => key == "filename", orElse: () => "");
if (fileNameKey.isEmpty) return null;
return parameters[fileNameKey];
}
double getViewWidth() {
@@ -225,14 +221,11 @@ class Other {
return "${appName}_backup_${DateTime.now().show}.zip";
}
String get logFile {
return "${appName}_${DateTime.now().show}.log";
}
Size getScreenSize() {
final view = WidgetsBinding.instance.platformDispatcher.views.first;
return view.physicalSize / view.devicePixelRatio;
}
}
final other = Other();

View File

@@ -2,7 +2,6 @@ import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/services.dart';
import 'window.dart';

View File

@@ -2,12 +2,10 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:archive/archive.dart';
import 'package:fl_clash/common/archive.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart';
@@ -60,9 +58,7 @@ class AppController {
updateRunTime,
updateTraffic,
];
if (!Platform.isAndroid) {
applyProfileDebounce();
}
applyProfileDebounce();
} else {
await globalState.handleStop();
clashCore.resetTraffic();
@@ -122,15 +118,11 @@ class AppController {
}
Future<void> updateClashConfig({bool isPatch = true}) async {
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return;
await commonScaffoldState?.loadingRun(() async {
await globalState.updateClashConfig(
clashConfig: clashConfig,
config: config,
isPatch: isPatch,
);
});
await globalState.updateClashConfig(
clashConfig: clashConfig,
config: config,
isPatch: isPatch,
);
}
Future applyProfile({bool isPrue = false}) async {
@@ -307,7 +299,7 @@ class AppController {
init() async {
final isDisclaimerAccepted = await handlerDisclaimer();
if (!isDisclaimerAccepted) {
handleExit();
system.exit();
}
updateLogStatus();
if (!config.silentLaunch) {
@@ -528,6 +520,21 @@ class AppController {
'';
}
Future<List<int>> backupData() async {
final homeDirPath = await appPath.getHomeDirPath();
final profilesPath = await appPath.getProfilesPath();
final configJson = config.toJson();
final clashConfigJson = clashConfig.toJson();
return Isolate.run<List<int>>(() async {
final archive = Archive();
archive.add("config.json", configJson);
archive.add("clashConfig.json", clashConfigJson);
await archive.addDirectoryToArchive(profilesPath, homeDirPath);
final zipEncoder = ZipEncoder();
return zipEncoder.encode(archive) ?? [];
});
}
updateTun() {
clashConfig.tun = clashConfig.tun.copyWith(
enable: !clashConfig.tun.enable,
@@ -566,36 +573,6 @@ class AppController {
clashConfig.mode = Mode.values[nextIndex];
}
Future<bool> exportLogs() async {
final logsRaw = appFlowingState.logs.map(
(item) => item.toString(),
);
final data = await Isolate.run<List<int>>(() async {
final logsRawString = logsRaw.join("\n");
return utf8.encode(logsRawString);
});
return await picker.saveFile(
other.logFile,
Uint8List.fromList(data),
) !=
null;
}
Future<List<int>> backupData() async {
final homeDirPath = await appPath.getHomeDirPath();
final profilesPath = await appPath.getProfilesPath();
final configJson = config.toJson();
final clashConfigJson = clashConfig.toJson();
return Isolate.run<List<int>>(() async {
final archive = Archive();
archive.add("config.json", configJson);
archive.add("clashConfig.json", clashConfigJson);
await archive.addDirectoryToArchive(profilesPath, homeDirPath);
final zipEncoder = ZipEncoder();
return zipEncoder.encode(archive) ?? [];
});
}
recoveryData(
List<int> data,
RecoveryOption recoveryOption,

View File

@@ -15,10 +15,6 @@ extension GroupTypeExtension on GroupType {
)
.toList();
bool get isURLTestOrFallback {
return [GroupType.URLTest, GroupType.Fallback].contains(this);
}
static GroupType? getGroupType(String value) {
final index = GroupTypeExtension.valueList.indexOf(value);
if (index == -1) return null;

View File

@@ -67,9 +67,11 @@ class _ConfigFragmentState extends State<ConfigFragment> {
title: const Text("DNS"),
subtitle: Text(appLocalizations.dnsDesc),
leading: const Icon(Icons.dns),
delegate: const OpenDelegate(
delegate: OpenDelegate(
title: "DNS",
widget: DnsListView(),
widget: generateListView(
dnsItems,
),
isScaffold: true,
isBlur: false,
extendPageWidth: 360,

View File

@@ -10,8 +10,35 @@ import 'package:provider/provider.dart';
class OverrideItem extends StatelessWidget {
const OverrideItem({super.key});
_initActions(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [
IconButton(
onPressed: () {
globalState.showMessage(
title: appLocalizations.resetDns,
message: TextSpan(
text: appLocalizations.dnsResetTip,
),
onTab: () {
globalState.appController.clashConfig.dns = const Dns();
Navigator.of(context).pop();
});
},
tooltip: appLocalizations.resetDns,
icon: const Icon(
Icons.replay,
),
)
];
});
}
@override
Widget build(BuildContext context) {
_initActions(context);
return Selector<Config, bool>(
selector: (_, config) => config.overrideDns,
builder: (_, override, __) {
@@ -31,6 +58,35 @@ class OverrideItem extends StatelessWidget {
}
}
class DnsDisabledContainer extends StatelessWidget {
final Widget child;
const DnsDisabledContainer(
this.child, {
super.key,
});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.overrideDns,
builder: (_, enable, child) {
return AbsorbPointer(
absorbing: !enable,
child: DisabledMask(
status: !enable,
child: Container(
color: context.colorScheme.surface,
child: child!,
),
),
);
},
child: child,
);
}
}
class StatusItem extends StatelessWidget {
const StatusItem({super.key});
@@ -413,6 +469,7 @@ class NameserverPolicyItem extends StatelessWidget {
items: nameserverPolicy.entries,
titleBuilder: (item) => Text(item.key),
subtitleBuilder: (item) => Text(item.value),
isMap: true,
onRemove: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
@@ -738,25 +795,27 @@ class DnsOptions extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: generateSection(
title: appLocalizations.options,
items: [
const StatusItem(),
const UseHostsItem(),
const UseSystemHostsItem(),
const IPv6Item(),
const RespectRulesItem(),
const PreferH3Item(),
const DnsModeItem(),
const FakeIpRangeItem(),
const FakeIpFilterItem(),
const DefaultNameserverItem(),
const NameserverPolicyItem(),
const NameserverItem(),
const FallbackItem(),
const ProxyServerNameserverItem(),
],
return DnsDisabledContainer(
Column(
children: generateSection(
title: appLocalizations.options,
items: [
const StatusItem(),
const UseHostsItem(),
const UseSystemHostsItem(),
const IPv6Item(),
const RespectRulesItem(),
const PreferH3Item(),
const DnsModeItem(),
const FakeIpRangeItem(),
const FakeIpFilterItem(),
const DefaultNameserverItem(),
const NameserverPolicyItem(),
const NameserverItem(),
const FallbackItem(),
const ProxyServerNameserverItem(),
],
),
),
);
}
@@ -767,16 +826,18 @@ class FallbackFilterOptions extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: generateSection(
title: appLocalizations.fallbackFilter,
items: [
const GeoipItem(),
const GeoipCodeItem(),
const GeositeItem(),
const IpcidrItem(),
const DomainItem(),
],
return DnsDisabledContainer(
Column(
children: generateSection(
title: appLocalizations.fallbackFilter,
items: [
const GeoipItem(),
const GeoipCodeItem(),
const GeositeItem(),
const IpcidrItem(),
const DomainItem(),
],
),
),
);
}
@@ -787,41 +848,3 @@ const dnsItems = <Widget>[
DnsOptions(),
FallbackFilterOptions(),
];
class DnsListView extends StatelessWidget {
const DnsListView({super.key});
_initActions(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [
IconButton(
onPressed: () {
globalState.showMessage(
title: appLocalizations.resetDns,
message: TextSpan(
text: appLocalizations.dnsResetTip,
),
onTab: () {
globalState.appController.clashConfig.dns = const Dns();
Navigator.of(context).pop();
});
},
tooltip: appLocalizations.resetDns,
icon: const Icon(
Icons.replay,
),
)
];
});
}
@override
Widget build(BuildContext context) {
_initActions(context);
return generateListView(
dnsItems,
);
}
}

View File

@@ -226,6 +226,7 @@ class HostsItem extends StatelessWidget {
clashConfig.hosts = Map.from(clashConfig.hosts)
..addEntries([value]);
},
isMap: true,
);
},
),

View File

@@ -115,34 +115,6 @@ class SystemProxySwitch extends StatelessWidget {
}
}
class Ipv6Switch extends StatelessWidget {
const Ipv6Switch({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.vpnProps.ipv6,
builder: (_, ipv6, __) {
return ListItem.switchItem(
leading: const Icon(Icons.water_outlined),
title: const Text("IPv6"),
subtitle: Text(appLocalizations.ipv6InboundDesc),
delegate: SwitchDelegate(
value: ipv6,
onChanged: (bool value) async {
final config = globalState.appController.config;
final vpnProps = config.vpnProps;
config.vpnProps = vpnProps.copyWith(
ipv6: value,
);
},
),
);
},
);
}
}
class VpnOptions extends StatelessWidget {
const VpnOptions({super.key});
@@ -155,7 +127,6 @@ class VpnOptions extends StatelessWidget {
items: [
const SystemProxySwitch(),
const AllowBypassSwitch(),
const Ipv6Switch(),
],
),
),

View File

@@ -13,55 +13,16 @@ class IntranetIP extends StatefulWidget {
}
class _IntranetIPState extends State<IntranetIP> {
final ipNotifier = ValueNotifier<String?>("");
Future<String> getNetworkType() async {
try {
List<NetworkInterface> interfaces = await NetworkInterface.list(
includeLoopback: false,
type: InternetAddressType.any,
);
for (var interface in interfaces) {
if (interface.name.toLowerCase().contains('wlan') ||
interface.name.toLowerCase().contains('wi-fi')) {
return 'WiFi';
}
if (interface.name.toLowerCase().contains('rmnet') ||
interface.name.toLowerCase().contains('ccmni') ||
interface.name.toLowerCase().contains('cellular')) {
return 'Mobile Data';
}
}
return 'Unknown';
} catch (e) {
return 'Error';
}
}
final ipNotifier = ValueNotifier<String>("");
Future<String?> getLocalIpAddress() async {
List<NetworkInterface> interfaces = await NetworkInterface.list(
includeLoopback: false,
)
..sort((a, b) {
if (a.isWifi && !b.isWifi) return -1;
if (!a.isWifi && b.isWifi) return 1;
if (a.includesIPv4 && !b.includesIPv4) return -1;
if (!a.includesIPv4 && b.includesIPv4) return 1;
return 0;
});
List<NetworkInterface> interfaces = await NetworkInterface.list();
for (final interface in interfaces) {
final addresses = interface.addresses;
if (addresses.isEmpty) {
continue;
for (final address in interface.addresses) {
if (!address.isLoopback) {
return address.address;
}
}
addresses.sort((a, b) {
if (a.isIPv4 && !b.isIPv4) return -1;
if (!a.isIPv4 && b.isIPv4) return 1;
return 0;
});
return addresses.first.address;
}
return null;
}
@@ -87,15 +48,17 @@ class _IntranetIPState extends State<IntranetIP> {
label: appLocalizations.intranetIP,
iconData: Icons.devices,
),
onPressed: () {},
onPressed: (){
},
child: Container(
padding: const EdgeInsets.all(16).copyWith(top: 0),
height: globalState.measure.titleMediumHeight + 24 - 2,
height: globalState.measure.titleLargeHeight + 24 - 2,
child: ValueListenableBuilder(
valueListenable: ipNotifier,
builder: (_, value, __) {
return FadeBox(
child: value != null
child: value.isNotEmpty
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
@@ -104,9 +67,8 @@ class _IntranetIPState extends State<IntranetIP> {
flex: 1,
child: TooltipText(
text: Text(
value.isNotEmpty ? value : appLocalizations.noNetwork,
style: context
.textTheme.titleLarge?.toSoftBold.toMinus,
value,
style: context.textTheme.titleLarge?.toSoftBold.toMinus,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),

View File

@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
@@ -24,7 +22,6 @@ class _NetworkDetectionState extends State<NetworkDetection> {
);
bool? _preIsStart;
Function? _checkIpDebounce;
Timer? _setTimeoutTimer;
CancelToken? cancelToken;
_checkIp() async {
@@ -34,7 +31,6 @@ class _NetworkDetectionState extends State<NetworkDetection> {
if (!isInit) return;
final isStart = appFlowingState.isStart;
if (_preIsStart == false && _preIsStart == isStart) return;
_clearSetTimeoutTimer();
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: true,
ipInfo: null,
@@ -47,32 +43,11 @@ class _NetworkDetectionState extends State<NetworkDetection> {
cancelToken = CancelToken();
try {
final ipInfo = await request.checkIp(cancelToken: cancelToken);
if (ipInfo != null) {
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: false,
ipInfo: ipInfo,
);
return;
}
_setTimeoutTimer = Timer(const Duration(milliseconds: 2000), () {
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: false,
ipInfo: null,
);
});
} catch (_) {
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: true,
ipInfo: null,
isTesting: false,
ipInfo: ipInfo,
);
}
}
_clearSetTimeoutTimer() {
if(_setTimeoutTimer != null){
_setTimeoutTimer?.cancel();
_setTimeoutTimer = null;
}
} catch (_) {}
}
_checkIpContainer(Widget child) {

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
@@ -31,24 +30,18 @@ class _LogsFragmentState extends State<LogsFragment> {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final appFlowingState = globalState.appController.appFlowingState;
logsNotifier.value =
logsNotifier.value.copyWith(logs: appFlowingState.logs);
logsNotifier.value = logsNotifier.value.copyWith(logs: appFlowingState.logs);
if (timer != null) {
timer?.cancel();
timer = null;
}
timer = Timer.periodic(const Duration(milliseconds: 200), (timer) {
final maxLength = Platform.isAndroid ? 1000 : 60;
final logs = appFlowingState.logs.safeSublist(
appFlowingState.logs.length - maxLength,
);
final logs = appFlowingState.logs;
if (!const ListEquality<Log>().equals(
logsNotifier.value.logs,
logs,
)) {
logsNotifier.value = logsNotifier.value.copyWith(
logs: logs,
);
logsNotifier.value = logsNotifier.value.copyWith(logs: logs);
}
});
});
@@ -63,21 +56,6 @@ class _LogsFragmentState extends State<LogsFragment> {
timer = null;
}
_handleExport() async {
final commonScaffoldState = context.commonScaffoldState;
final res = await commonScaffoldState?.loadingRun<bool>(
() async {
return await globalState.appController.exportLogs();
},
title: appLocalizations.exportLogs,
);
if (res != true) return;
globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(text: appLocalizations.exportSuccess),
);
}
_initActions() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final commonScaffoldState =
@@ -94,17 +72,6 @@ class _LogsFragmentState extends State<LogsFragment> {
},
icon: const Icon(Icons.search),
),
const SizedBox(
width: 8,
),
IconButton(
onPressed: () {
_handleExport();
},
icon: const Icon(
Icons.file_download_outlined,
),
),
];
});
}
@@ -269,8 +236,7 @@ class LogsSearchDelegate extends SearchDelegate {
_addKeyword(String keyword) {
final isContains = logsNotifier.value.keywords.contains(keyword);
if (isContains) return;
final keywords = List<String>.from(logsNotifier.value.keywords)
..add(keyword);
final keywords = List<String>.from(logsNotifier.value.keywords)..add(keyword);
logsNotifier.value = logsNotifier.value.copyWith(
keywords: keywords,
);
@@ -279,8 +245,7 @@ class LogsSearchDelegate extends SearchDelegate {
_deleteKeyword(String keyword) {
final isContains = logsNotifier.value.keywords.contains(keyword);
if (!isContains) return;
final keywords = List<String>.from(logsNotifier.value.keywords)
..remove(keyword);
final keywords = List<String>.from(logsNotifier.value.keywords)..remove(keyword);
logsNotifier.value = logsNotifier.value.copyWith(
keywords: keywords,
);
@@ -374,9 +339,7 @@ class _LogItemState extends State<LogItem> {
style: context.textTheme.bodySmall
?.copyWith(color: context.colorScheme.primary),
),
const SizedBox(
height: 8,
),
const SizedBox(height: 8,),
Container(
alignment: Alignment.centerLeft,
child: CommonChip(

View File

@@ -102,12 +102,12 @@ class ProxyCard extends StatelessWidget {
_changeProxy(BuildContext context) async {
final appController = globalState.appController;
final isURLTestOrFallback = groupType.isURLTestOrFallback;
final isUrlTest = groupType == GroupType.URLTest;
final isSelector = groupType == GroupType.Selector;
if (isURLTestOrFallback || isSelector) {
if (isUrlTest || isSelector) {
final currentProxyName =
appController.config.currentSelectedMap[groupName];
final nextProxyName = switch (isURLTestOrFallback) {
final nextProxyName = switch (isUrlTest) {
true => currentProxyName == proxy.name ? "" : proxy.name,
false => proxy.name,
};
@@ -207,7 +207,7 @@ class ProxyCard extends StatelessWidget {
),
),
),
if (groupType.isURLTestOrFallback)
if (groupType == GroupType.URLTest)
Selector<Config, String>(
selector: (_, config) {
final selectedProxyName =

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
@@ -38,10 +37,7 @@ class _RequestsFragmentState extends State<RequestsFragment> {
timer = null;
}
timer = Timer.periodic(const Duration(milliseconds: 200), (timer) {
final maxLength = Platform.isAndroid ? 1000 : 60;
final requests = appState.requests.safeSublist(
appState.requests.length - maxLength,
);
final requests = appState.requests;
if (!const ListEquality<Connection>().equals(
requestsNotifier.value.connections,
requests,

View File

@@ -70,7 +70,7 @@ class _ToolboxFragmentState extends State<ToolsFragment> {
final isDisclaimerAccepted =
await globalState.appController.showDisclaimer();
if (!isDisclaimerAccepted) {
globalState.appController.handleExit();
system.exit();
}
},
),

View File

@@ -123,7 +123,7 @@
"tabAnimation": "Tab animation",
"tabAnimationDesc": "When enabled, the home tab will add a toggle animation",
"desc": "A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.",
"startVpn": "Starting VPN...",
"startVpn": "Staring VPN...",
"stopVpn": "Stopping VPN...",
"discovery": "Discovery a new version",
"compatible": "Compatibility mode",
@@ -304,9 +304,5 @@
"hotkeyConflict": "Hotkey conflict",
"remove": "Remove",
"noHotKey": "No HotKey",
"dnsResetTip": "Make sure to reset the DNS",
"noNetwork": "No network",
"ipv6InboundDesc": "Allow IPv6 inbound",
"exportLogs": "Export logs",
"exportSuccess": "Export Success"
"dnsResetTip": "Make sure to reset the DNS"
}

View File

@@ -304,9 +304,5 @@
"hotkeyConflict": "快捷键冲突",
"remove": "移除",
"noHotKey": "暂无快捷键",
"dnsResetTip": "确定重置DNS",
"noNetwork": "无网络",
"ipv6InboundDesc": "允许IPv6入站",
"exportLogs": "导出日志",
"exportSuccess": "导出成功"
"dnsResetTip": "确定重置DNS"
}

View File

@@ -163,8 +163,6 @@ class MessageLookup extends MessageLookupByLibrary {
"expand": MessageLookupByLibrary.simpleMessage("Standard"),
"expirationTime":
MessageLookupByLibrary.simpleMessage("Expiration time"),
"exportLogs": MessageLookupByLibrary.simpleMessage("Export logs"),
"exportSuccess": MessageLookupByLibrary.simpleMessage("Export Success"),
"externalController":
MessageLookupByLibrary.simpleMessage("ExternalController"),
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
@@ -221,8 +219,6 @@ class MessageLookup extends MessageLookupByLibrary {
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
"When turned on it will be able to receive IPv6 traffic"),
"ipv6InboundDesc":
MessageLookupByLibrary.simpleMessage("Allow IPv6 inbound"),
"just": MessageLookupByLibrary.simpleMessage("Just"),
"keepAliveIntervalDesc":
MessageLookupByLibrary.simpleMessage("Tcp keep alive interval"),
@@ -273,7 +269,6 @@ class MessageLookup extends MessageLookupByLibrary {
"noHotKey": MessageLookupByLibrary.simpleMessage("No HotKey"),
"noInfo": MessageLookupByLibrary.simpleMessage("No info"),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("No more info"),
"noNetwork": MessageLookupByLibrary.simpleMessage("No network"),
"noProxy": MessageLookupByLibrary.simpleMessage("No proxy"),
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
"Please create a profile or add a valid profile"),
@@ -400,7 +395,7 @@ class MessageLookup extends MessageLookupByLibrary {
"source": MessageLookupByLibrary.simpleMessage("Source"),
"standard": MessageLookupByLibrary.simpleMessage("Standard"),
"start": MessageLookupByLibrary.simpleMessage("Start"),
"startVpn": MessageLookupByLibrary.simpleMessage("Starting VPN..."),
"startVpn": MessageLookupByLibrary.simpleMessage("Staring VPN..."),
"status": MessageLookupByLibrary.simpleMessage("Status"),
"statusDesc": MessageLookupByLibrary.simpleMessage(
"System DNS will be used when turned off"),

View File

@@ -133,8 +133,6 @@ class MessageLookup extends MessageLookupByLibrary {
"exit": MessageLookupByLibrary.simpleMessage("退出"),
"expand": MessageLookupByLibrary.simpleMessage("标准"),
"expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"),
"exportLogs": MessageLookupByLibrary.simpleMessage("导出日志"),
"exportSuccess": MessageLookupByLibrary.simpleMessage("导出成功"),
"externalController": MessageLookupByLibrary.simpleMessage("外部控制器"),
"externalControllerDesc":
MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制Clash内核"),
@@ -176,7 +174,6 @@ class MessageLookup extends MessageLookupByLibrary {
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"),
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
"keepAliveIntervalDesc":
MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
@@ -217,7 +214,6 @@ class MessageLookup extends MessageLookupByLibrary {
"noHotKey": MessageLookupByLibrary.simpleMessage("暂无快捷键"),
"noInfo": MessageLookupByLibrary.simpleMessage("暂无信息"),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("暂无更多信息"),
"noNetwork": MessageLookupByLibrary.simpleMessage("无网络"),
"noProxy": MessageLookupByLibrary.simpleMessage("暂无代理"),
"noProxyDesc":
MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"),

View File

@@ -1290,10 +1290,10 @@ class AppLocalizations {
);
}
/// `Starting VPN...`
/// `Staring VPN...`
String get startVpn {
return Intl.message(
'Starting VPN...',
'Staring VPN...',
name: 'startVpn',
desc: '',
args: [],
@@ -3109,46 +3109,6 @@ class AppLocalizations {
args: [],
);
}
/// `No network`
String get noNetwork {
return Intl.message(
'No network',
name: 'noNetwork',
desc: '',
args: [],
);
}
/// `Allow IPv6 inbound`
String get ipv6InboundDesc {
return Intl.message(
'Allow IPv6 inbound',
name: 'ipv6InboundDesc',
desc: '',
args: [],
);
}
/// `Export logs`
String get exportLogs {
return Intl.message(
'Export logs',
name: 'exportLogs',
desc: '',
args: [],
);
}
/// `Export Success`
String get exportSuccess {
return Intl.message(
'Export Success',
name: 'exportSuccess',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/plugins/app.dart';

View File

@@ -18,7 +18,6 @@ class AppStateManager extends StatefulWidget {
class _AppStateManagerState extends State<AppStateManager>
with WidgetsBindingObserver {
_updateNavigationsContainer(Widget child) {
return Selector2<AppState, Config, UpdateNavigationsSelector>(
selector: (_, appState, config) {

View File

@@ -19,9 +19,9 @@ class ClashManager extends StatefulWidget {
State<ClashManager> createState() => _ClashContainerState();
}
class _ClashContainerState extends State<ClashManager> with AppMessageListener {
class _ClashContainerState extends State<ClashManager>
with AppMessageListener {
Function? updateClashConfigDebounce;
Function? updateDelayDebounce;
Widget _updateContainer(Widget child) {
return Selector2<Config, ClashConfig, ClashConfigState>(
@@ -66,7 +66,6 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
selector: (_, config, clashConfig) => CoreState(
accessControl: config.isAccessControl ? config.accessControl : null,
enable: config.vpnProps.enable,
ipv6: config.vpnProps.ipv6,
allowBypass: config.vpnProps.allowBypass,
systemProxy: config.vpnProps.systemProxy,
mixedPort: clashConfig.mixedPort,
@@ -133,11 +132,7 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
final appController = globalState.appController;
appController.setDelay(delay);
super.onDelay(delay);
updateDelayDebounce ??= debounce(() async {
await appController.updateGroupDebounce();
await appController.addCheckIpNumDebounce();
});
updateDelayDebounce!();
await globalState.appController.updateGroupDebounce();
}
@override
@@ -150,12 +145,6 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
super.onLog(log);
}
@override
void onStarted(String runTime) {
super.onStarted(runTime);
globalState.appController.applyProfileDebounce();
}
@override
void onRequest(Connection connection) async {
globalState.appController.appState.addRequest(connection);
@@ -170,7 +159,7 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
providerName,
),
);
// appController.addCheckIpNumDebounce();
appController.addCheckIpNumDebounce();
super.onLoaded(providerName);
}
}

View File

@@ -151,8 +151,10 @@ class AppState with ChangeNotifier {
addRequest(Connection value) {
_requests = List.from(_requests)..add(value);
const maxLength = 1000;
_requests = _requests.safeSublist(_requests.length - maxLength);
final maxLength = Platform.isAndroid ? 1000 : 60;
if (_requests.length > maxLength) {
_requests = _requests.sublist(_requests.length - maxLength);
}
notifyListeners();
}
@@ -352,8 +354,10 @@ class AppFlowingState with ChangeNotifier {
addLog(Log log) {
_logs = List.from(_logs)..add(log);
const maxLength = 1000;
_logs = _logs.safeSublist(_logs.length - maxLength);
final maxLength = Platform.isAndroid ? 1000 : 60;
if (_logs.length > maxLength) {
_logs = _logs.sublist(_logs.length - maxLength);
}
notifyListeners();
}
@@ -369,7 +373,9 @@ class AppFlowingState with ChangeNotifier {
addTraffic(Traffic traffic) {
_traffics = List.from(_traffics)..add(traffic);
const maxLength = 60;
_traffics = _traffics.safeSublist(_traffics.length - maxLength);
if (_traffics.length > maxLength) {
_traffics = _traffics.sublist(_traffics.length - maxLength);
}
notifyListeners();
}

View File

@@ -303,7 +303,7 @@ extension GroupExt on Group {
String get realNow => now ?? "";
String getCurrentSelectedName(String proxyName) {
if (type.isURLTestOrFallback) {
if (type == GroupType.URLTest) {
return realNow.isNotEmpty ? realNow : proxyName;
}
return proxyName.isNotEmpty ? proxyName : realNow;

View File

@@ -42,7 +42,6 @@ class CoreState with _$CoreState {
required bool allowBypass,
required bool systemProxy,
required int mixedPort,
required bool ipv6,
required bool onlyProxy,
}) = _CoreState;
@@ -78,8 +77,7 @@ class WindowProps with _$WindowProps {
class VpnProps with _$VpnProps {
const factory VpnProps({
@Default(true) bool enable,
@Default(true) bool systemProxy,
@Default(false) bool ipv6,
@Default(false) bool systemProxy,
@Default(true) bool allowBypass,
}) = _VpnProps;

View File

@@ -32,9 +32,9 @@ ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig()
'mmdb':
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb',
'asn':
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb',
'https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb',
'geoip':
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat',
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoIP.dat',
'geosite':
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat'
}

View File

@@ -274,7 +274,6 @@ mixin _$CoreState {
bool get allowBypass => throw _privateConstructorUsedError;
bool get systemProxy => throw _privateConstructorUsedError;
int get mixedPort => throw _privateConstructorUsedError;
bool get ipv6 => throw _privateConstructorUsedError;
bool get onlyProxy => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -295,7 +294,6 @@ abstract class $CoreStateCopyWith<$Res> {
bool allowBypass,
bool systemProxy,
int mixedPort,
bool ipv6,
bool onlyProxy});
$AccessControlCopyWith<$Res>? get accessControl;
@@ -320,7 +318,6 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
Object? allowBypass = null,
Object? systemProxy = null,
Object? mixedPort = null,
Object? ipv6 = null,
Object? onlyProxy = null,
}) {
return _then(_value.copyWith(
@@ -348,10 +345,6 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
? _value.mixedPort
: mixedPort // ignore: cast_nullable_to_non_nullable
as int,
ipv6: null == ipv6
? _value.ipv6
: ipv6 // ignore: cast_nullable_to_non_nullable
as bool,
onlyProxy: null == onlyProxy
? _value.onlyProxy
: onlyProxy // ignore: cast_nullable_to_non_nullable
@@ -387,7 +380,6 @@ abstract class _$$CoreStateImplCopyWith<$Res>
bool allowBypass,
bool systemProxy,
int mixedPort,
bool ipv6,
bool onlyProxy});
@override
@@ -411,7 +403,6 @@ class __$$CoreStateImplCopyWithImpl<$Res>
Object? allowBypass = null,
Object? systemProxy = null,
Object? mixedPort = null,
Object? ipv6 = null,
Object? onlyProxy = null,
}) {
return _then(_$CoreStateImpl(
@@ -439,10 +430,6 @@ class __$$CoreStateImplCopyWithImpl<$Res>
? _value.mixedPort
: mixedPort // ignore: cast_nullable_to_non_nullable
as int,
ipv6: null == ipv6
? _value.ipv6
: ipv6 // ignore: cast_nullable_to_non_nullable
as bool,
onlyProxy: null == onlyProxy
? _value.onlyProxy
: onlyProxy // ignore: cast_nullable_to_non_nullable
@@ -461,7 +448,6 @@ class _$CoreStateImpl implements _CoreState {
required this.allowBypass,
required this.systemProxy,
required this.mixedPort,
required this.ipv6,
required this.onlyProxy});
factory _$CoreStateImpl.fromJson(Map<String, dynamic> json) =>
@@ -480,13 +466,11 @@ class _$CoreStateImpl implements _CoreState {
@override
final int mixedPort;
@override
final bool ipv6;
@override
final bool onlyProxy;
@override
String toString() {
return 'CoreState(accessControl: $accessControl, currentProfileName: $currentProfileName, enable: $enable, allowBypass: $allowBypass, systemProxy: $systemProxy, mixedPort: $mixedPort, ipv6: $ipv6, onlyProxy: $onlyProxy)';
return 'CoreState(accessControl: $accessControl, currentProfileName: $currentProfileName, enable: $enable, allowBypass: $allowBypass, systemProxy: $systemProxy, mixedPort: $mixedPort, onlyProxy: $onlyProxy)';
}
@override
@@ -505,7 +489,6 @@ class _$CoreStateImpl implements _CoreState {
other.systemProxy == systemProxy) &&
(identical(other.mixedPort, mixedPort) ||
other.mixedPort == mixedPort) &&
(identical(other.ipv6, ipv6) || other.ipv6 == ipv6) &&
(identical(other.onlyProxy, onlyProxy) ||
other.onlyProxy == onlyProxy));
}
@@ -520,7 +503,6 @@ class _$CoreStateImpl implements _CoreState {
allowBypass,
systemProxy,
mixedPort,
ipv6,
onlyProxy);
@JsonKey(ignore: true)
@@ -545,7 +527,6 @@ abstract class _CoreState implements CoreState {
required final bool allowBypass,
required final bool systemProxy,
required final int mixedPort,
required final bool ipv6,
required final bool onlyProxy}) = _$CoreStateImpl;
factory _CoreState.fromJson(Map<String, dynamic> json) =
@@ -564,8 +545,6 @@ abstract class _CoreState implements CoreState {
@override
int get mixedPort;
@override
bool get ipv6;
@override
bool get onlyProxy;
@override
@JsonKey(ignore: true)
@@ -955,7 +934,6 @@ VpnProps _$VpnPropsFromJson(Map<String, dynamic> json) {
mixin _$VpnProps {
bool get enable => throw _privateConstructorUsedError;
bool get systemProxy => throw _privateConstructorUsedError;
bool get ipv6 => throw _privateConstructorUsedError;
bool get allowBypass => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -969,7 +947,7 @@ abstract class $VpnPropsCopyWith<$Res> {
factory $VpnPropsCopyWith(VpnProps value, $Res Function(VpnProps) then) =
_$VpnPropsCopyWithImpl<$Res, VpnProps>;
@useResult
$Res call({bool enable, bool systemProxy, bool ipv6, bool allowBypass});
$Res call({bool enable, bool systemProxy, bool allowBypass});
}
/// @nodoc
@@ -987,7 +965,6 @@ class _$VpnPropsCopyWithImpl<$Res, $Val extends VpnProps>
$Res call({
Object? enable = null,
Object? systemProxy = null,
Object? ipv6 = null,
Object? allowBypass = null,
}) {
return _then(_value.copyWith(
@@ -999,10 +976,6 @@ class _$VpnPropsCopyWithImpl<$Res, $Val extends VpnProps>
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
ipv6: null == ipv6
? _value.ipv6
: ipv6 // ignore: cast_nullable_to_non_nullable
as bool,
allowBypass: null == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
@@ -1019,7 +992,7 @@ abstract class _$$VpnPropsImplCopyWith<$Res>
__$$VpnPropsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool enable, bool systemProxy, bool ipv6, bool allowBypass});
$Res call({bool enable, bool systemProxy, bool allowBypass});
}
/// @nodoc
@@ -1035,7 +1008,6 @@ class __$$VpnPropsImplCopyWithImpl<$Res>
$Res call({
Object? enable = null,
Object? systemProxy = null,
Object? ipv6 = null,
Object? allowBypass = null,
}) {
return _then(_$VpnPropsImpl(
@@ -1047,10 +1019,6 @@ class __$$VpnPropsImplCopyWithImpl<$Res>
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
ipv6: null == ipv6
? _value.ipv6
: ipv6 // ignore: cast_nullable_to_non_nullable
as bool,
allowBypass: null == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
@@ -1063,10 +1031,7 @@ class __$$VpnPropsImplCopyWithImpl<$Res>
@JsonSerializable()
class _$VpnPropsImpl implements _VpnProps {
const _$VpnPropsImpl(
{this.enable = true,
this.systemProxy = true,
this.ipv6 = false,
this.allowBypass = true});
{this.enable = true, this.systemProxy = false, this.allowBypass = true});
factory _$VpnPropsImpl.fromJson(Map<String, dynamic> json) =>
_$$VpnPropsImplFromJson(json);
@@ -1079,14 +1044,11 @@ class _$VpnPropsImpl implements _VpnProps {
final bool systemProxy;
@override
@JsonKey()
final bool ipv6;
@override
@JsonKey()
final bool allowBypass;
@override
String toString() {
return 'VpnProps(enable: $enable, systemProxy: $systemProxy, ipv6: $ipv6, allowBypass: $allowBypass)';
return 'VpnProps(enable: $enable, systemProxy: $systemProxy, allowBypass: $allowBypass)';
}
@override
@@ -1097,7 +1059,6 @@ class _$VpnPropsImpl implements _VpnProps {
(identical(other.enable, enable) || other.enable == enable) &&
(identical(other.systemProxy, systemProxy) ||
other.systemProxy == systemProxy) &&
(identical(other.ipv6, ipv6) || other.ipv6 == ipv6) &&
(identical(other.allowBypass, allowBypass) ||
other.allowBypass == allowBypass));
}
@@ -1105,7 +1066,7 @@ class _$VpnPropsImpl implements _VpnProps {
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, enable, systemProxy, ipv6, allowBypass);
Object.hash(runtimeType, enable, systemProxy, allowBypass);
@JsonKey(ignore: true)
@override
@@ -1125,7 +1086,6 @@ abstract class _VpnProps implements VpnProps {
const factory _VpnProps(
{final bool enable,
final bool systemProxy,
final bool ipv6,
final bool allowBypass}) = _$VpnPropsImpl;
factory _VpnProps.fromJson(Map<String, dynamic> json) =
@@ -1136,8 +1096,6 @@ abstract class _VpnProps implements VpnProps {
@override
bool get systemProxy;
@override
bool get ipv6;
@override
bool get allowBypass;
@override
@JsonKey(ignore: true)

View File

@@ -176,7 +176,6 @@ _$CoreStateImpl _$$CoreStateImplFromJson(Map<String, dynamic> json) =>
allowBypass: json['allowBypass'] as bool,
systemProxy: json['systemProxy'] as bool,
mixedPort: (json['mixedPort'] as num).toInt(),
ipv6: json['ipv6'] as bool,
onlyProxy: json['onlyProxy'] as bool,
);
@@ -188,7 +187,6 @@ Map<String, dynamic> _$$CoreStateImplToJson(_$CoreStateImpl instance) =>
'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy,
'mixedPort': instance.mixedPort,
'ipv6': instance.ipv6,
'onlyProxy': instance.onlyProxy,
};
@@ -226,8 +224,7 @@ Map<String, dynamic> _$$WindowPropsImplToJson(_$WindowPropsImpl instance) =>
_$VpnPropsImpl _$$VpnPropsImplFromJson(Map<String, dynamic> json) =>
_$VpnPropsImpl(
enable: json['enable'] as bool? ?? true,
systemProxy: json['systemProxy'] as bool? ?? true,
ipv6: json['ipv6'] as bool? ?? false,
systemProxy: json['systemProxy'] as bool? ?? false,
allowBypass: json['allowBypass'] as bool? ?? true,
);
@@ -235,7 +232,6 @@ Map<String, dynamic> _$$VpnPropsImplToJson(_$VpnPropsImpl instance) =>
<String, dynamic>{
'enable': instance.enable,
'systemProxy': instance.systemProxy,
'ipv6': instance.ipv6,
'allowBypass': instance.allowBypass,
};

View File

@@ -90,7 +90,7 @@ class GlobalState {
startTime = clashCore.getRunTime();
}
Future handleStop() async {
handleStop() async {
clashCore.stop();
if (Platform.isAndroid) {
clashCore.stopTun();
@@ -133,7 +133,6 @@ class GlobalState {
CoreState(
enable: config.vpnProps.enable,
accessControl: config.isAccessControl ? config.accessControl : null,
ipv6: config.vpnProps.ipv6,
allowBypass: config.vpnProps.allowBypass,
systemProxy: config.vpnProps.systemProxy,
mixedPort: clashConfig.mixedPort,

View File

@@ -149,6 +149,7 @@ class UpdatePage<T> extends StatelessWidget {
final Widget Function(T item)? subtitleBuilder;
final Function(T item) onAdd;
final Function(T item) onRemove;
final bool isMap;
const UpdatePage({
super.key,
@@ -157,37 +158,10 @@ class UpdatePage<T> extends StatelessWidget {
required this.titleBuilder,
required this.onRemove,
required this.onAdd,
this.isMap = false,
this.subtitleBuilder,
});
bool get isMap => items is Iterable<MapEntry>;
_handleEdit(T item) async {
if (isMap) {
item as MapEntry<String, String>;
final value = await globalState.showCommonDialog<T>(
child: AddDialog(
defaultKey: item.key,
defaultValue: item.value,
title: title,
),
);
if (value == null) return;
onAdd(value);
} else {
item as String;
final value = await globalState.showCommonDialog<T>(
child: AddDialog(
defaultKey: null,
defaultValue: item,
title: title,
),
);
if (value == null) return;
onAdd(value);
}
}
@override
Widget build(BuildContext context) {
return FloatLayout(
@@ -196,8 +170,7 @@ class UpdatePage<T> extends StatelessWidget {
onPressed: () async {
final value = await globalState.showCommonDialog<T>(
child: AddDialog(
defaultKey: isMap ? "" : null,
defaultValue: "",
isMap: isMap,
title: title,
),
);
@@ -229,9 +202,7 @@ class UpdatePage<T> extends StatelessWidget {
},
),
),
onPressed: () {
_handleEdit(e);
},
onPressed: () {},
),
);
},
@@ -242,14 +213,12 @@ class UpdatePage<T> extends StatelessWidget {
class AddDialog extends StatefulWidget {
final String title;
final String? defaultKey;
final String defaultValue;
final bool isMap;
const AddDialog({
super.key,
required this.title,
this.defaultKey,
required this.defaultValue,
required this.isMap,
});
@override
@@ -264,19 +233,13 @@ class _AddDialogState extends State<AddDialog> {
@override
void initState() {
super.initState();
keyController = TextEditingController(
text: widget.defaultKey,
);
valueController = TextEditingController(
text: widget.defaultValue,
);
keyController = TextEditingController();
valueController = TextEditingController();
}
bool get hasKey => widget.defaultKey != null;
_submit() {
if (!_formKey.currentState!.validate()) return;
if (hasKey) {
if (widget.isMap) {
Navigator.of(context).pop<MapEntry<String, String>>(
MapEntry(
keyController.text,
@@ -301,7 +264,7 @@ class _AddDialogState extends State<AddDialog> {
child: Wrap(
runSpacing: 16,
children: [
if (hasKey)
if (widget.isMap)
TextFormField(
maxLines: 2,
minLines: 1,

View File

@@ -1,7 +1,7 @@
name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none'
version: 0.8.62+202409261
version: 0.8.61+202409201
environment:
sdk: '>=3.1.0 <4.0.0'
flutter: 3.22.0

View File

@@ -55,8 +55,7 @@ class BuildLibItem {
}
class Build {
static List<BuildLibItem> get buildItems =>
[
static List<BuildLibItem> get buildItems => [
BuildLibItem(
platform: PlatformType.macos,
arch: Arch.amd64,
@@ -115,7 +114,7 @@ class Build {
final ndk = environment["ANDROID_NDK"];
assert(ndk != null);
final prebuiltDir =
Directory(join(ndk!, "toolchains", "llvm", "prebuilt"));
Directory(join(ndk!, "toolchains", "llvm", "prebuilt"));
final prebuiltDirList = prebuiltDir.listSync();
final map = {
"armeabi-v7a": "armv7a-linux-androideabi21-clang",
@@ -134,7 +133,8 @@ class Build {
static get tags => "with_gvisor";
static Future<void> exec(List<String> executable, {
static Future<void> exec(
List<String> executable, {
String? name,
Map<String, String>? environment,
String? workingDirectory,
@@ -163,7 +163,7 @@ class Build {
Arch? arch,
}) async {
final items = buildItems.where(
(element) {
(element) {
return element.platform == platform &&
(arch == null ? true : element.arch == arch);
},
@@ -187,7 +187,6 @@ class Build {
env["GOARCH"] = item.arch.name;
env["CGO_ENABLED"] = "1";
env["CC"] = _getCc(item);
env["CFLAGS"] = "-O3 -Werror";
await exec(
[
@@ -276,11 +275,10 @@ class BuildCommand extends Command {
@override
String get name => platform.name;
List<Arch> get arches =>
Build.buildItems
.where((element) => element.platform == platform)
.map((e) => e.arch)
.toList();
List<Arch> get arches => Build.buildItems
.where((element) => element.platform == platform)
.map((e) => e.arch)
.toList();
Future<void> _buildLib(Arch? arch) async {
await Build.buildLib(platform: platform, arch: arch);
@@ -340,8 +338,7 @@ class BuildCommand extends Command {
await Build.exec(
name: name,
Build.getExecutable(
"flutter_distributor package --skip-clean --platform ${platform
.name} --targets $targets --flutter-build-args=verbose $args",
"flutter_distributor package --skip-clean --platform ${platform.name} --targets $targets --flutter-build-args=verbose $args",
),
);
}
@@ -351,7 +348,7 @@ class BuildCommand extends Command {
final String build = argResults?['build'] ?? 'all';
final archName = argResults?['arch'];
final currentArches =
arches.where((element) => element.name == archName).toList();
arches.where((element) => element.name == archName).toList();
final arch = currentArches.isEmpty ? null : currentArches.first;
if (arch == null && platform == PlatformType.windows) {
throw "Invalid arch";
@@ -389,8 +386,7 @@ class BuildCommand extends Command {
platform: platform,
targets: "apk",
args:
"--flutter-build-args split-per-abi --build-target-platform ${defaultTargets
.join(",")}",
"--flutter-build-args split-per-abi --build-target-platform ${defaultTargets.join(",")}",
);
case PlatformType.macos:
await _getMacosDependencies();

View File

@@ -2,15 +2,24 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/other.dart';
import 'package:lpinyin/lpinyin.dart';
void main() {
startService();
final res = [1, 2, 3, 4, 5, 6,7,8,9,10,11];
print(res.batch(5));
}
startService() async {
// 定义服务器将要监听的地址和端口
final host = InternetAddress.anyIPv4; // 监听所有网络接口
const port = 8080; // 使用 8080 端口
try {
// 创建服务器
final server = await HttpServer.bind("127.0.0.1", 10001);
final server = await HttpServer.bind(host, port);
print('服务器正在监听 ${server.address.address}:${server.port}');
// 监听请求