Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e766d9407 | ||
|
|
80f8aa22ee | ||
|
|
97714e8b25 | ||
|
|
50bf4170d9 | ||
|
|
79efa67df3 | ||
|
|
ac397393a0 | ||
|
|
b685165230 | ||
|
|
402221aaa2 | ||
|
|
f6d9ed11d9 | ||
|
|
c38a671d57 | ||
|
|
75af47aead | ||
|
|
8dafe3b0ec | ||
|
|
813198a21d | ||
|
|
68dd262fef | ||
|
|
5ef020db73 | ||
|
|
e3c9035903 |
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@@ -87,7 +87,7 @@ jobs:
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.x'
|
||||
flutter-version: 3.22.x
|
||||
channel: 'stable'
|
||||
cache: true
|
||||
|
||||
@@ -136,18 +136,25 @@ jobs:
|
||||
gitchangelog "${pre}.." >> release.md 2>&1 || echo "Error in gitchangelog"
|
||||
echo -e "\n\n</details>" >> release.md
|
||||
fi
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ./dist/*
|
||||
body_path: './release.md'
|
||||
|
||||
- name: Create Fdroid Source Dir
|
||||
run: |
|
||||
mkdir -p ./tmp
|
||||
cp ./dist/*android-arm64-v8a* ./tmp/ || true
|
||||
echo "Files copied successfully"
|
||||
|
||||
- name: Push to fdroid repo
|
||||
uses: cpina/github-action-push-to-another-repository@v1.7.2
|
||||
env:
|
||||
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
|
||||
with:
|
||||
source-directory: ./dist/
|
||||
source-directory: ./tmp/
|
||||
destination-github-username: chen08209
|
||||
destination-repository-name: FlClash-fdroid-repo
|
||||
user-name: 'github-actions[bot]'
|
||||
|
||||
@@ -102,6 +102,9 @@ flutter {
|
||||
dependencies {
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation 'com.google.code.gson:gson:2.10'
|
||||
implementation("com.android.tools.smali:smali-dexlib2:3.0.7") {
|
||||
exclude group: "com.google.guava", module: "guava"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
@@ -14,18 +14,20 @@
|
||||
<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.FOREGROUND_SERVICE_SPECIAL_USE"
|
||||
<uses-permission
|
||||
android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
|
||||
tools:ignore="SystemPermissionTypo" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<application
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:extractNativeLibs="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="FlClash"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
tools:targetApi="tiramisu">
|
||||
<activity
|
||||
android:name="com.follow.clash.MainActivity"
|
||||
@@ -56,17 +58,17 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="clash"/>
|
||||
<data android:scheme="clashmeta"/>
|
||||
<data android:scheme="flclash"/>
|
||||
<data android:scheme="clash" />
|
||||
<data android:scheme="clashmeta" />
|
||||
<data android:scheme="flclash" />
|
||||
|
||||
<data android:host="install-config"/>
|
||||
<data android:host="install-config" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- <meta-data-->
|
||||
<!-- android:name="io.flutter.embedding.android.EnableImpeller"-->
|
||||
<!-- android:value="true" />-->
|
||||
<!-- <meta-data-->
|
||||
<!-- android:name="io.flutter.embedding.android.EnableImpeller"-->
|
||||
<!-- android:value="true" />-->
|
||||
|
||||
<activity
|
||||
android:name=".TempActivity"
|
||||
@@ -75,11 +77,10 @@
|
||||
<service
|
||||
android:name=".services.FlClashTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_stat_name"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:icon="@drawable/ic_stat_name"
|
||||
android:label="FlClash"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
>
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
@@ -114,13 +115,17 @@
|
||||
android:name=".services.FlClashVpnService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
>
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".services.FlClashService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="specialUse" />
|
||||
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.follow.clash
|
||||
|
||||
import com.follow.clash.models.Props
|
||||
|
||||
interface BaseServiceInterface {
|
||||
fun start(port: Int, props: Props?): Int?
|
||||
fun stop()
|
||||
fun startForeground(title: String, content: String)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.follow.clash
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.follow.clash.plugins.AppPlugin
|
||||
import com.follow.clash.plugins.ProxyPlugin
|
||||
import com.follow.clash.plugins.ServicePlugin
|
||||
import com.follow.clash.plugins.VpnPlugin
|
||||
import com.follow.clash.plugins.TilePlugin
|
||||
import io.flutter.FlutterInjector
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
@@ -22,6 +22,7 @@ enum class RunState {
|
||||
object GlobalState {
|
||||
|
||||
private val lock = ReentrantLock()
|
||||
val runLock = ReentrantLock()
|
||||
|
||||
val runState: MutableLiveData<RunState> = MutableLiveData<RunState>(RunState.STOP)
|
||||
var flutterEngine: FlutterEngine? = null
|
||||
@@ -37,6 +38,11 @@ object GlobalState {
|
||||
return currentEngine?.plugins?.get(TilePlugin::class.java) as TilePlugin?
|
||||
}
|
||||
|
||||
fun getCurrentVPNPlugin(): VpnPlugin? {
|
||||
val currentEngine = if (serviceEngine != null) serviceEngine else flutterEngine
|
||||
return currentEngine?.plugins?.get(VpnPlugin::class.java) as VpnPlugin?
|
||||
}
|
||||
|
||||
fun destroyServiceEngine() {
|
||||
serviceEngine?.destroy()
|
||||
serviceEngine = null
|
||||
@@ -47,9 +53,10 @@ object GlobalState {
|
||||
lock.withLock {
|
||||
destroyServiceEngine()
|
||||
serviceEngine = FlutterEngine(context)
|
||||
serviceEngine?.plugins?.add(ProxyPlugin())
|
||||
serviceEngine?.plugins?.add(VpnPlugin())
|
||||
serviceEngine?.plugins?.add(AppPlugin())
|
||||
serviceEngine?.plugins?.add(TilePlugin())
|
||||
serviceEngine?.plugins?.add(ServicePlugin())
|
||||
val vpnService = DartExecutor.DartEntrypoint(
|
||||
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
|
||||
"vpnService"
|
||||
|
||||
@@ -2,7 +2,8 @@ package com.follow.clash
|
||||
|
||||
|
||||
import com.follow.clash.plugins.AppPlugin
|
||||
import com.follow.clash.plugins.ProxyPlugin
|
||||
import com.follow.clash.plugins.ServicePlugin
|
||||
import com.follow.clash.plugins.VpnPlugin
|
||||
import com.follow.clash.plugins.TilePlugin
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
@@ -12,7 +13,8 @@ class MainActivity : FlutterActivity() {
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
flutterEngine.plugins.add(AppPlugin())
|
||||
flutterEngine.plugins.add(ProxyPlugin())
|
||||
flutterEngine.plugins.add(VpnPlugin())
|
||||
flutterEngine.plugins.add(ServicePlugin())
|
||||
flutterEngine.plugins.add(TilePlugin())
|
||||
GlobalState.flutterEngine = flutterEngine
|
||||
}
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
package com.follow.clash.extensions
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.system.OsConstants.IPPROTO_TCP
|
||||
import android.system.OsConstants.IPPROTO_UDP
|
||||
import android.util.Base64
|
||||
import java.net.URL
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.follow.clash.MainActivity
|
||||
import com.follow.clash.R
|
||||
import com.follow.clash.models.Metadata
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
|
||||
suspend fun Drawable.getBase64(): String {
|
||||
@@ -31,7 +41,6 @@ fun Metadata.getProtocol(): Int? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun String.getInetSocketAddress(): InetSocketAddress {
|
||||
val url = URL("https://$this")
|
||||
return InetSocketAddress(InetAddress.getByName(url.host), url.port)
|
||||
}
|
||||
private val CHANNEL = "FlClash"
|
||||
|
||||
private val notificationId: Int = 1
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.follow.clash.models
|
||||
|
||||
import java.util.Date
|
||||
|
||||
data class Package(
|
||||
val packageName: String,
|
||||
val label: String,
|
||||
val isSystem:Boolean
|
||||
val isSystem: Boolean,
|
||||
val firstInstallTime: Long,
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ data class AccessControl(
|
||||
)
|
||||
|
||||
data class Props(
|
||||
val enable: Boolean?,
|
||||
val accessControl: AccessControl?,
|
||||
val allowBypass: Boolean?,
|
||||
val systemProxy: Boolean?,
|
||||
|
||||
@@ -6,11 +6,16 @@ import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.ComponentInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import com.android.tools.smali.dexlib2.dexbacked.DexBackedDexFile
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.content.getSystemService
|
||||
import com.follow.clash.GlobalState
|
||||
@@ -19,6 +24,7 @@ import com.follow.clash.extensions.getProtocol
|
||||
import com.follow.clash.models.Package
|
||||
import com.follow.clash.models.Process
|
||||
import com.google.gson.Gson
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
@@ -32,7 +38,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {
|
||||
|
||||
@@ -40,7 +46,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
|
||||
private var toast: Toast? = null
|
||||
|
||||
private var context: Context? = null
|
||||
private lateinit var context: Context
|
||||
|
||||
private lateinit var channel: MethodChannel
|
||||
|
||||
@@ -48,14 +54,78 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
|
||||
private var connectivity: ConnectivityManager? = null
|
||||
|
||||
private var vpnCallBack: (() -> Unit)? = null
|
||||
|
||||
private val iconMap = mutableMapOf<String, String?>()
|
||||
private val packages = mutableListOf<Package>()
|
||||
|
||||
private val skipPrefixList = listOf(
|
||||
"com.google",
|
||||
"com.android.chrome",
|
||||
"com.android.vending",
|
||||
"com.microsoft",
|
||||
"com.apple",
|
||||
"com.zhiliaoapp.musically", // Banned by China
|
||||
)
|
||||
|
||||
private val chinaAppPrefixList = listOf(
|
||||
"com.tencent",
|
||||
"com.alibaba",
|
||||
"com.umeng",
|
||||
"com.qihoo",
|
||||
"com.ali",
|
||||
"com.alipay",
|
||||
"com.amap",
|
||||
"com.sina",
|
||||
"com.weibo",
|
||||
"com.vivo",
|
||||
"com.xiaomi",
|
||||
"com.huawei",
|
||||
"com.taobao",
|
||||
"com.secneo",
|
||||
"s.h.e.l.l",
|
||||
"com.stub",
|
||||
"com.kiwisec",
|
||||
"com.secshell",
|
||||
"com.wrapper",
|
||||
"cn.securitystack",
|
||||
"com.mogosec",
|
||||
"com.secoen",
|
||||
"com.netease",
|
||||
"com.mx",
|
||||
"com.qq.e",
|
||||
"com.baidu",
|
||||
"com.bytedance",
|
||||
"com.bugly",
|
||||
"com.miui",
|
||||
"com.oppo",
|
||||
"com.coloros",
|
||||
"com.iqoo",
|
||||
"com.meizu",
|
||||
"com.gionee",
|
||||
"cn.nubia",
|
||||
"com.oplus",
|
||||
"andes.oplus",
|
||||
"com.unionpay",
|
||||
"cn.wps"
|
||||
)
|
||||
|
||||
private val chinaAppRegex by lazy {
|
||||
("(" + chinaAppPrefixList.joinToString("|").replace(".", "\\.") + ").*").toRegex()
|
||||
}
|
||||
|
||||
|
||||
val VPN_PERMISSION_REQUEST_CODE = 1001
|
||||
|
||||
val NOTIFICATION_PERMISSION_REQUEST_CODE = 1002
|
||||
|
||||
private var isBlockNotification: Boolean = false
|
||||
|
||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
scope = CoroutineScope(Dispatchers.Default)
|
||||
context = flutterPluginBinding.applicationContext;
|
||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "app")
|
||||
channel.setMethodCallHandler(this)
|
||||
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
@@ -88,7 +158,13 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
|
||||
"getPackages" -> {
|
||||
scope.launch {
|
||||
result.success(getPackages())
|
||||
result.success(getPackagesToJson())
|
||||
}
|
||||
}
|
||||
|
||||
"getChinaPackageNames" -> {
|
||||
scope.launch {
|
||||
result.success(getChinaPackageNames())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +183,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
}
|
||||
if (iconMap["default"] == null) {
|
||||
iconMap["default"] =
|
||||
context?.packageManager?.defaultActivityIcon?.getBase64()
|
||||
context.packageManager?.defaultActivityIcon?.getBase64()
|
||||
}
|
||||
result.success(iconMap["default"])
|
||||
return@launch
|
||||
@@ -134,12 +210,8 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
result.success(null)
|
||||
return@withContext
|
||||
}
|
||||
if (context == null) {
|
||||
result.success(null)
|
||||
return@withContext
|
||||
}
|
||||
if (connectivity == null) {
|
||||
connectivity = context!!.getSystemService<ConnectivityManager>()
|
||||
connectivity = context.getSystemService<ConnectivityManager>()
|
||||
}
|
||||
val src = InetSocketAddress(metadata.sourceIP, metadata.sourcePort)
|
||||
val dst = InetSocketAddress(
|
||||
@@ -155,7 +227,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
result.success(null)
|
||||
return@withContext
|
||||
}
|
||||
val packages = context?.packageManager?.getPackagesForUid(uid)
|
||||
val packages = context.packageManager?.getPackagesForUid(uid)
|
||||
result.success(packages?.first())
|
||||
}
|
||||
}
|
||||
@@ -180,46 +252,43 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
}
|
||||
|
||||
private fun openFile(path: String) {
|
||||
context?.let {
|
||||
val file = File(path)
|
||||
val uri = FileProvider.getUriForFile(
|
||||
it,
|
||||
"${it.packageName}.fileProvider",
|
||||
file
|
||||
)
|
||||
val file = File(path)
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
"${context.packageName}.fileProvider",
|
||||
file
|
||||
)
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW).setDataAndType(
|
||||
val intent = Intent(Intent.ACTION_VIEW).setDataAndType(
|
||||
uri,
|
||||
"text/plain"
|
||||
)
|
||||
|
||||
val flags =
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
|
||||
val resInfoList = context.packageManager.queryIntentActivities(
|
||||
intent, PackageManager.MATCH_DEFAULT_ONLY
|
||||
)
|
||||
|
||||
for (resolveInfo in resInfoList) {
|
||||
val packageName = resolveInfo.activityInfo.packageName
|
||||
context.grantUriPermission(
|
||||
packageName,
|
||||
uri,
|
||||
"text/plain"
|
||||
flags
|
||||
)
|
||||
}
|
||||
|
||||
val flags =
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
|
||||
val resInfoList = it.packageManager.queryIntentActivities(
|
||||
intent, PackageManager.MATCH_DEFAULT_ONLY
|
||||
)
|
||||
|
||||
for (resolveInfo in resInfoList) {
|
||||
val packageName = resolveInfo.activityInfo.packageName
|
||||
it.grantUriPermission(
|
||||
packageName,
|
||||
uri,
|
||||
flags
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
activity?.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
println(e)
|
||||
}
|
||||
try {
|
||||
activity?.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
println(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateExcludeFromRecents(value: Boolean?) {
|
||||
if (context == null) return
|
||||
val am = getSystemService(context!!, ActivityManager::class.java)
|
||||
val am = getSystemService(context, ActivityManager::class.java)
|
||||
val task = am?.appTasks?.firstOrNull {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
it.taskInfo.taskId == activity?.taskId
|
||||
@@ -236,7 +305,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
}
|
||||
|
||||
private suspend fun getPackageIcon(packageName: String): String? {
|
||||
val packageManager = context?.packageManager
|
||||
val packageManager = context.packageManager
|
||||
if (iconMap[packageName] == null) {
|
||||
iconMap[packageName] = try {
|
||||
packageManager?.getApplicationIcon(packageName)?.getBase64()
|
||||
@@ -248,32 +317,144 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
return iconMap[packageName]
|
||||
}
|
||||
|
||||
private suspend fun getPackages(): String {
|
||||
return withContext(Dispatchers.Default) {
|
||||
val packageManager = context?.packageManager
|
||||
val packages: List<Package>? =
|
||||
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter {
|
||||
it.packageName != context?.packageName
|
||||
|| it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
||||
|| it.packageName == "android"
|
||||
private fun getPackages(): List<Package> {
|
||||
val packageManager = context.packageManager
|
||||
if (packages.isNotEmpty()) return packages;
|
||||
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter {
|
||||
it.packageName != context.packageName
|
||||
|| it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
||||
|| it.packageName == "android"
|
||||
|
||||
}?.map {
|
||||
Package(
|
||||
packageName = it.packageName,
|
||||
label = it.applicationInfo.loadLabel(packageManager).toString(),
|
||||
isSystem = (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 1
|
||||
)
|
||||
}
|
||||
}?.map {
|
||||
Package(
|
||||
packageName = it.packageName,
|
||||
label = it.applicationInfo.loadLabel(packageManager).toString(),
|
||||
isSystem = (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 1,
|
||||
firstInstallTime = it.firstInstallTime
|
||||
)
|
||||
}?.let { packages.addAll(it) }
|
||||
return packages;
|
||||
}
|
||||
|
||||
private suspend fun getPackagesToJson(): String {
|
||||
return withContext(Dispatchers.Default) {
|
||||
Gson().toJson(getPackages())
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getChinaPackageNames(): String {
|
||||
return withContext(Dispatchers.Default) {
|
||||
val packages: List<String> =
|
||||
getPackages().map { it.packageName }.filter { isChinaPackage(it) }
|
||||
Gson().toJson(packages)
|
||||
}
|
||||
}
|
||||
|
||||
fun requestVpnPermission(context: Context, callBack: () -> Unit) {
|
||||
vpnCallBack = callBack
|
||||
val intent = VpnService.prepare(context)
|
||||
if (intent != null) {
|
||||
activity?.startActivityForResult(intent, VPN_PERMISSION_REQUEST_CODE)
|
||||
return;
|
||||
}
|
||||
vpnCallBack?.invoke()
|
||||
}
|
||||
|
||||
fun requestNotificationsPermission(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val permission = ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
)
|
||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
if (isBlockNotification) return
|
||||
if (activity == null) return
|
||||
ActivityCompat.requestPermissions(
|
||||
activity!!,
|
||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||
NOTIFICATION_PERMISSION_REQUEST_CODE
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun isChinaPackage(packageName: String): Boolean {
|
||||
val packageManager = context.packageManager ?: return false
|
||||
skipPrefixList.forEach {
|
||||
if (packageName == it || packageName.startsWith("$it.")) return false
|
||||
}
|
||||
val packageManagerFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
PackageManager.MATCH_UNINSTALLED_PACKAGES or PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
PackageManager.GET_UNINSTALLED_PACKAGES or PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
|
||||
}
|
||||
if (packageName.matches(chinaAppRegex)) {
|
||||
return true
|
||||
}
|
||||
try {
|
||||
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.PackageInfoFlags.of(packageManagerFlags.toLong())
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION") packageManager.getPackageInfo(
|
||||
packageName, packageManagerFlags
|
||||
)
|
||||
}
|
||||
mutableListOf<ComponentInfo>().apply {
|
||||
packageInfo.services?.let { addAll(it) }
|
||||
packageInfo.activities?.let { addAll(it) }
|
||||
packageInfo.receivers?.let { addAll(it) }
|
||||
packageInfo.providers?.let { addAll(it) }
|
||||
}.forEach {
|
||||
if (it.name.matches(chinaAppRegex)) return true
|
||||
}
|
||||
ZipFile(File(packageInfo.applicationInfo.publicSourceDir)).use {
|
||||
for (packageEntry in it.entries()) {
|
||||
if (packageEntry.name.startsWith("firebase-")) return false
|
||||
}
|
||||
for (packageEntry in it.entries()) {
|
||||
if (!(packageEntry.name.startsWith("classes") && packageEntry.name.endsWith(
|
||||
".dex"
|
||||
))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if (packageEntry.size > 15000000) {
|
||||
return true
|
||||
}
|
||||
val input = it.getInputStream(packageEntry).buffered()
|
||||
val dexFile = try {
|
||||
DexBackedDexFile.fromInputStream(null, input)
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
for (clazz in dexFile.classes) {
|
||||
val clazzName =
|
||||
clazz.type.substring(1, clazz.type.length - 1).replace("/", ".")
|
||||
.replace("$", ".")
|
||||
if (clazzName.matches(chinaAppRegex)) return true
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun requestGc() {
|
||||
channel.invokeMethod("gc", null)
|
||||
}
|
||||
|
||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||
activity = binding.activity;
|
||||
binding.addActivityResultListener(::onActivityResult)
|
||||
binding.addRequestPermissionsResultListener(::onRequestPermissionsResultListener)
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivityForConfigChanges() {
|
||||
@@ -288,4 +469,25 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
channel.invokeMethod("exit", null)
|
||||
activity = null
|
||||
}
|
||||
|
||||
private fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
|
||||
if (requestCode == VPN_PERMISSION_REQUEST_CODE) {
|
||||
if (resultCode == FlutterActivity.RESULT_OK) {
|
||||
GlobalState.initServiceEngine(context)
|
||||
vpnCallBack?.invoke()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun onRequestPermissionsResultListener(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
): Boolean {
|
||||
if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
|
||||
isBlockNotification = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
package com.follow.clash.plugins
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.follow.clash.GlobalState
|
||||
import com.follow.clash.RunState
|
||||
import com.follow.clash.models.Props
|
||||
import com.follow.clash.services.FlClashVpnService
|
||||
import com.google.gson.Gson
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
|
||||
class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {
|
||||
|
||||
private lateinit var flutterMethodChannel: MethodChannel
|
||||
|
||||
val VPN_PERMISSION_REQUEST_CODE = 1001
|
||||
val NOTIFICATION_PERMISSION_REQUEST_CODE = 1002
|
||||
|
||||
private var activity: Activity? = null
|
||||
private var context: Context? = null
|
||||
private var flClashVpnService: FlClashVpnService? = null
|
||||
private var port: Int = 7890
|
||||
private var props: Props? = null
|
||||
private var isBlockNotification: Boolean = false
|
||||
private var isStart: Boolean = false
|
||||
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
val binder = service as FlClashVpnService.LocalBinder
|
||||
flClashVpnService = binder.getService()
|
||||
if (isStart) {
|
||||
startVpn()
|
||||
} else {
|
||||
flClashVpnService?.initServiceEngine()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(arg: ComponentName) {
|
||||
flClashVpnService = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
context = flutterPluginBinding.applicationContext
|
||||
flutterMethodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "proxy")
|
||||
flutterMethodChannel.setMethodCallHandler(this)
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
flutterMethodChannel.setMethodCallHandler(null)
|
||||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) {
|
||||
"initService" -> {
|
||||
isStart = false
|
||||
initService()
|
||||
requestNotificationsPermission()
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
"startProxy" -> {
|
||||
isStart = true
|
||||
port = call.argument<Int>("port")!!
|
||||
val args = call.argument<String>("args")
|
||||
props =
|
||||
if (args != null) Gson().fromJson(args, Props::class.java) else null
|
||||
startVpn()
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
"stopProxy" -> {
|
||||
stopVpn()
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
"setProtect" -> {
|
||||
val fd = call.argument<Int>("fd")
|
||||
if (fd != null) {
|
||||
flClashVpnService?.protect(fd)
|
||||
result.success(true)
|
||||
} else {
|
||||
result.success(false)
|
||||
}
|
||||
}
|
||||
|
||||
"startForeground" -> {
|
||||
val title = call.argument<String>("title") as String
|
||||
val content = call.argument<String>("content") as String
|
||||
startForeground(title, content)
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initService() {
|
||||
val intent = VpnService.prepare(context)
|
||||
if (intent != null) {
|
||||
activity?.startActivityForResult(intent, VPN_PERMISSION_REQUEST_CODE)
|
||||
} else {
|
||||
if (flClashVpnService != null) {
|
||||
flClashVpnService!!.initServiceEngine()
|
||||
} else {
|
||||
bindService()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startVpn() {
|
||||
if (flClashVpnService == null) {
|
||||
bindService()
|
||||
return
|
||||
}
|
||||
if (GlobalState.runState.value == RunState.START) return
|
||||
GlobalState.runState.value = RunState.START
|
||||
val intent = VpnService.prepare(context)
|
||||
if (intent != null) {
|
||||
stopVpn()
|
||||
return
|
||||
}
|
||||
val fd = flClashVpnService?.start(port, props)
|
||||
flutterMethodChannel.invokeMethod("started", fd)
|
||||
}
|
||||
|
||||
private fun stopVpn() {
|
||||
if (GlobalState.runState.value == RunState.STOP) return
|
||||
GlobalState.runState.value = RunState.STOP
|
||||
flClashVpnService?.stop()
|
||||
GlobalState.destroyServiceEngine()
|
||||
}
|
||||
|
||||
private fun startForeground(title: String, content: String) {
|
||||
if (GlobalState.runState.value != RunState.START) return
|
||||
flClashVpnService?.startForeground(title, content)
|
||||
}
|
||||
|
||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||
activity = binding.activity
|
||||
binding.addActivityResultListener(::onActivityResult)
|
||||
binding.addRequestPermissionsResultListener(::onRequestPermissionsResultListener)
|
||||
}
|
||||
|
||||
private fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
|
||||
if (requestCode == VPN_PERMISSION_REQUEST_CODE) {
|
||||
if (resultCode == FlutterActivity.RESULT_OK) {
|
||||
bindService()
|
||||
} else {
|
||||
stopVpn()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun onRequestPermissionsResultListener(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
): Boolean {
|
||||
if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
|
||||
isBlockNotification = true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun requestNotificationsPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val permission = context?.let {
|
||||
ContextCompat.checkSelfPermission(
|
||||
it,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
)
|
||||
}
|
||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
if (isBlockNotification) return
|
||||
if (activity == null) return
|
||||
ActivityCompat.requestPermissions(
|
||||
activity!!,
|
||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||
NOTIFICATION_PERMISSION_REQUEST_CODE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivityForConfigChanges() {
|
||||
activity = null
|
||||
}
|
||||
|
||||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
||||
activity = binding.activity
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivity() {
|
||||
activity = null
|
||||
}
|
||||
|
||||
private fun bindService() {
|
||||
val intent = Intent(context, FlClashVpnService::class.java)
|
||||
context?.bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.follow.clash.plugins
|
||||
|
||||
import android.content.Context
|
||||
import com.follow.clash.GlobalState
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
|
||||
class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||
|
||||
private lateinit var flutterMethodChannel: MethodChannel
|
||||
|
||||
private lateinit var context: Context
|
||||
|
||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
context = flutterPluginBinding.applicationContext
|
||||
flutterMethodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "service")
|
||||
flutterMethodChannel.setMethodCallHandler(this)
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
flutterMethodChannel.setMethodCallHandler(null)
|
||||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) {
|
||||
"init" -> {
|
||||
GlobalState.getCurrentAppPlugin()?.requestNotificationsPermission(context)
|
||||
GlobalState.initServiceEngine(context)
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
"destroy" -> {
|
||||
handleDestroy()
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDestroy() {
|
||||
GlobalState.getCurrentVPNPlugin()?.stop()
|
||||
GlobalState.destroyServiceEngine()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.follow.clash.plugins
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import com.follow.clash.BaseServiceInterface
|
||||
import com.follow.clash.GlobalState
|
||||
import com.follow.clash.RunState
|
||||
import com.follow.clash.models.Props
|
||||
import com.follow.clash.services.FlClashService
|
||||
import com.follow.clash.services.FlClashVpnService
|
||||
import com.google.gson.Gson
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
|
||||
class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||
private lateinit var flutterMethodChannel: MethodChannel
|
||||
private lateinit var context: Context
|
||||
private var flClashService: BaseServiceInterface? = null
|
||||
private var port: Int = 7890
|
||||
private var props: Props? = null
|
||||
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
flClashService = when (service) {
|
||||
is FlClashVpnService.LocalBinder -> service.getService()
|
||||
is FlClashService.LocalBinder -> service.getService()
|
||||
else -> throw Exception("invalid binder")
|
||||
}
|
||||
start()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(arg: ComponentName) {
|
||||
flClashService = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
context = flutterPluginBinding.applicationContext
|
||||
flutterMethodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "vpn")
|
||||
flutterMethodChannel.setMethodCallHandler(this)
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
flutterMethodChannel.setMethodCallHandler(null)
|
||||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) {
|
||||
|
||||
"start" -> {
|
||||
port = call.argument<Int>("port")!!
|
||||
val args = call.argument<String>("args")
|
||||
props =
|
||||
if (args != null) Gson().fromJson(args, Props::class.java) else null
|
||||
when (props?.enable == true) {
|
||||
true -> handleStartVpn()
|
||||
false -> start()
|
||||
}
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
"stop" -> {
|
||||
stop()
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
"setProtect" -> {
|
||||
val fd = call.argument<Int>("fd")
|
||||
if (fd != null) {
|
||||
if (flClashService is FlClashVpnService) {
|
||||
(flClashService as FlClashVpnService).protect(fd)
|
||||
}
|
||||
result.success(true)
|
||||
} else {
|
||||
result.success(false)
|
||||
}
|
||||
}
|
||||
|
||||
"startForeground" -> {
|
||||
val title = call.argument<String>("title") as String
|
||||
val content = call.argument<String>("content") as String
|
||||
startForeground(title, content)
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ForegroundServiceType")
|
||||
fun handleStartVpn() {
|
||||
GlobalState.getCurrentAppPlugin()?.requestVpnPermission(context) {
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ForegroundServiceType")
|
||||
private fun startForeground(title: String, content: String) {
|
||||
GlobalState.runLock.withLock {
|
||||
if (GlobalState.runState.value != RunState.START) return
|
||||
flClashService?.startForeground(title, content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun start() {
|
||||
if (flClashService == null) {
|
||||
bindService()
|
||||
return
|
||||
}
|
||||
GlobalState.runLock.withLock {
|
||||
if (GlobalState.runState.value == RunState.START) return
|
||||
GlobalState.runState.value = RunState.START
|
||||
val fd = flClashService?.start(port, props)
|
||||
flutterMethodChannel.invokeMethod("started", fd)
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
GlobalState.runLock.withLock {
|
||||
if (GlobalState.runState.value == RunState.STOP) return
|
||||
GlobalState.runState.value = RunState.STOP
|
||||
flClashService?.stop()
|
||||
}
|
||||
GlobalState.destroyServiceEngine()
|
||||
}
|
||||
|
||||
private fun bindService() {
|
||||
val intent = when (props?.enable == true) {
|
||||
true -> Intent(context, FlClashVpnService::class.java)
|
||||
false -> Intent(context, FlClashService::class.java)
|
||||
}
|
||||
context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.follow.clash.services
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
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()
|
||||
|
||||
inner class LocalBinder : Binder() {
|
||||
fun getService(): FlClashService = this@FlClashService
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
private val CHANNEL = "FlClash"
|
||||
|
||||
private val notificationId: Int = 1
|
||||
|
||||
private val notificationBuilder: NotificationCompat.Builder by lazy {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
|
||||
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
|
||||
PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
} else {
|
||||
PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
}
|
||||
with(NotificationCompat.Builder(this, CHANNEL)) {
|
||||
setSmallIcon(com.follow.clash.R.drawable.ic_stat_name)
|
||||
setContentTitle("FlClash")
|
||||
setContentIntent(pendingIntent)
|
||||
setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
priority = NotificationCompat.PRIORITY_MIN
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
|
||||
}
|
||||
setOngoing(true)
|
||||
setShowWhen(false)
|
||||
setOnlyAlertOnce(true)
|
||||
setAutoCancel(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun start(port: Int, props: Props?): Int? = null
|
||||
|
||||
override fun stop() {
|
||||
stopSelf()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ForegroundServiceType", "WrongConstant")
|
||||
override fun startForeground(title: String, content: String) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val manager = getSystemService(NotificationManager::class.java)
|
||||
var channel = manager?.getNotificationChannel(CHANNEL)
|
||||
if (channel == null) {
|
||||
channel =
|
||||
NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
|
||||
manager?.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
val notification =
|
||||
notificationBuilder.setContentTitle(title).setContentText(content).build()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||
} else {
|
||||
startForeground(notificationId, notification)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.follow.clash.services
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
@@ -15,6 +16,7 @@ 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
|
||||
@@ -25,10 +27,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class FlClashVpnService : VpnService() {
|
||||
private val CHANNEL = "FlClash"
|
||||
|
||||
private val notificationId: Int = 1
|
||||
@SuppressLint("WrongConstant")
|
||||
class FlClashVpnService : VpnService(), BaseServiceInterface {
|
||||
|
||||
private val passList = listOf(
|
||||
"*zhihu.com",
|
||||
@@ -52,10 +52,10 @@ class FlClashVpnService : VpnService() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
initServiceEngine()
|
||||
GlobalState.initServiceEngine(applicationContext)
|
||||
}
|
||||
|
||||
fun start(port: Int, props: Props?): Int? {
|
||||
override fun start(port: Int, props: Props?): Int? {
|
||||
return with(Builder()) {
|
||||
addAddress("172.16.0.1", 30)
|
||||
setMtu(9000)
|
||||
@@ -97,11 +97,18 @@ class FlClashVpnService : VpnService() {
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
|
||||
override fun stop() {
|
||||
stopSelf()
|
||||
stopForeground()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
}
|
||||
|
||||
private val CHANNEL = "FlClash"
|
||||
|
||||
private val notificationId: Int = 1
|
||||
|
||||
private val notificationBuilder: NotificationCompat.Builder by lazy {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
|
||||
@@ -136,16 +143,8 @@ class FlClashVpnService : VpnService() {
|
||||
}
|
||||
}
|
||||
|
||||
fun initServiceEngine() {
|
||||
GlobalState.initServiceEngine(applicationContext)
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
GlobalState.getCurrentAppPlugin()?.requestGc()
|
||||
}
|
||||
|
||||
fun startForeground(title: String, content: String) {
|
||||
@SuppressLint("ForegroundServiceType", "WrongConstant")
|
||||
override fun startForeground(title: String, content: String) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val manager = getSystemService(NotificationManager::class.java)
|
||||
var channel = manager?.getNotificationChannel(CHANNEL)
|
||||
@@ -157,17 +156,16 @@ class FlClashVpnService : VpnService() {
|
||||
}
|
||||
val notification =
|
||||
notificationBuilder.setContentTitle(title).setContentText(content).build()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||
} else {
|
||||
startForeground(notificationId, notification)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopForeground() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
GlobalState.getCurrentAppPlugin()?.requestGc()
|
||||
}
|
||||
|
||||
private val binder = LocalBinder()
|
||||
@@ -190,7 +188,6 @@ class FlClashVpnService : VpnService() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
BIN
assets/fonts/Icons.ttf
Normal file
BIN
assets/fonts/Icons.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/Twemoji.Mozilla.ttf
Normal file
BIN
assets/fonts/Twemoji.Mozilla.ttf
Normal file
Binary file not shown.
Submodule core/Clash.Meta updated: fffdf84493...a61c926a17
237
core/common.go
237
core/common.go
@@ -3,6 +3,18 @@ package main
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
route "github.com/metacubex/mihomo/hub/route"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
@@ -15,61 +27,18 @@ import (
|
||||
cp "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/hub"
|
||||
"github.com/metacubex/mihomo/hub/executor"
|
||||
"github.com/metacubex/mihomo/hub/route"
|
||||
"github.com/metacubex/mihomo/listener"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
rp "github.com/metacubex/mihomo/rules/provider"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
//type healthCheckSchema struct {
|
||||
// Enable bool `provider:"enable"`
|
||||
// URL string `provider:"url"`
|
||||
// Interval int `provider:"interval"`
|
||||
// TestTimeout int `provider:"timeout,omitempty"`
|
||||
// Lazy bool `provider:"lazy,omitempty"`
|
||||
// ExpectedStatus string `provider:"expected-status,omitempty"`
|
||||
//}
|
||||
|
||||
//type proxyProviderSchema struct {
|
||||
// Type string `provider:"type"`
|
||||
// Path string `provider:"path,omitempty"`
|
||||
// URL string `provider:"url,omitempty"`
|
||||
// Proxy string `provider:"proxy,omitempty"`
|
||||
// Interval int `provider:"interval,omitempty"`
|
||||
// Filter string `provider:"filter,omitempty"`
|
||||
// ExcludeFilter string `provider:"exclude-filter,omitempty"`
|
||||
// ExcludeType string `provider:"exclude-type,omitempty"`
|
||||
// DialerProxy string `provider:"dialer-proxy,omitempty"`
|
||||
//
|
||||
// HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
// Override ap.OverrideSchema `provider:"override,omitempty"`
|
||||
// Header map[string][]string `provider:"header,omitempty"`
|
||||
//}
|
||||
//
|
||||
//type ruleProviderSchema struct {
|
||||
// Type string `provider:"type"`
|
||||
// Behavior string `provider:"behavior"`
|
||||
// Path string `provider:"path,omitempty"`
|
||||
// URL string `provider:"url,omitempty"`
|
||||
// Proxy string `provider:"proxy,omitempty"`
|
||||
// Format string `provider:"format,omitempty"`
|
||||
// Interval int `provider:"interval,omitempty"`
|
||||
//}
|
||||
|
||||
type ConfigExtendedParams struct {
|
||||
IsPatch bool `json:"is-patch"`
|
||||
IsCompatible bool `json:"is-compatible"`
|
||||
SelectedMap map[string]string `json:"selected-map"`
|
||||
TestURL *string `json:"test-url"`
|
||||
OverrideDns bool `json:"override-dns"`
|
||||
}
|
||||
|
||||
type GenerateConfigParams struct {
|
||||
@@ -102,6 +71,12 @@ type ExternalProvider struct {
|
||||
UpdateAt time.Time `json:"update-at"`
|
||||
}
|
||||
|
||||
type ExternalProviders []ExternalProvider
|
||||
|
||||
func (a ExternalProviders) Len() int { return len(a) }
|
||||
func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
||||
func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
var b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
||||
|
||||
func restartExecutable(execPath string) {
|
||||
@@ -190,35 +165,67 @@ func getRawConfigWithId(id string) *config.RawConfig {
|
||||
return prof
|
||||
}
|
||||
|
||||
func getExternalProvidersRaw() map[string]ExternalProvider {
|
||||
externalProviders := make(map[string]ExternalProvider)
|
||||
func getExternalProvidersRaw() map[string]cp.Provider {
|
||||
eps := make(map[string]cp.Provider)
|
||||
for n, p := range tunnel.Providers() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*provider.ProxySetProvider)
|
||||
externalProviders[n] = ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Count: p.Count(),
|
||||
Path: p.Vehicle().Path(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
}
|
||||
eps[n] = p
|
||||
}
|
||||
}
|
||||
for n, p := range tunnel.RuleProviders() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*rp.RuleSetProvider)
|
||||
externalProviders[n] = ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Count: p.Count(),
|
||||
Path: p.Vehicle().Path(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
}
|
||||
eps[n] = p
|
||||
}
|
||||
}
|
||||
return externalProviders
|
||||
return eps
|
||||
}
|
||||
|
||||
func toExternalProvider(p cp.Provider) (*ExternalProvider, error) {
|
||||
switch p.(type) {
|
||||
case *provider.ProxySetProvider:
|
||||
psp := p.(*provider.ProxySetProvider)
|
||||
return &ExternalProvider{
|
||||
Name: psp.Name(),
|
||||
Type: psp.Type().String(),
|
||||
VehicleType: psp.VehicleType().String(),
|
||||
Count: psp.Count(),
|
||||
Path: psp.Vehicle().Path(),
|
||||
UpdateAt: psp.UpdatedAt(),
|
||||
}, nil
|
||||
case *rp.RuleSetProvider:
|
||||
rsp := p.(*rp.RuleSetProvider)
|
||||
return &ExternalProvider{
|
||||
Name: rsp.Name(),
|
||||
Type: rsp.Type().String(),
|
||||
VehicleType: rsp.VehicleType().String(),
|
||||
Count: rsp.Count(),
|
||||
Path: rsp.Vehicle().Path(),
|
||||
UpdateAt: rsp.UpdatedAt(),
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.New("not external provider")
|
||||
}
|
||||
}
|
||||
|
||||
func sideUpdateExternalProvider(p cp.Provider, bytes []byte) error {
|
||||
switch p.(type) {
|
||||
case *provider.ProxySetProvider:
|
||||
psp := p.(*provider.ProxySetProvider)
|
||||
elm, same, err := psp.SideUpdate(bytes)
|
||||
if err == nil && !same {
|
||||
psp.OnUpdate(elm)
|
||||
}
|
||||
return nil
|
||||
case rp.RuleSetProvider:
|
||||
rsp := p.(*rp.RuleSetProvider)
|
||||
elm, same, err := rsp.SideUpdate(bytes)
|
||||
if err == nil && !same {
|
||||
rsp.OnUpdate(elm)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.New("not external provider")
|
||||
}
|
||||
}
|
||||
|
||||
func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig {
|
||||
@@ -373,6 +380,12 @@ func generateProxyGroupAndRule(proxyGroup *[]map[string]any, rule *[]string) {
|
||||
*rule = computedRule
|
||||
}
|
||||
|
||||
func genHosts(hosts, patchHosts map[string]any) {
|
||||
for k, v := range patchHosts {
|
||||
hosts[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) {
|
||||
targetConfig.ExternalController = patchConfig.ExternalController
|
||||
targetConfig.ExternalUI = ""
|
||||
@@ -380,7 +393,6 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
|
||||
targetConfig.ExternalUIURL = ""
|
||||
targetConfig.TCPConcurrent = patchConfig.TCPConcurrent
|
||||
targetConfig.UnifiedDelay = patchConfig.UnifiedDelay
|
||||
//targetConfig.GeodataMode = false
|
||||
targetConfig.IPv6 = patchConfig.IPv6
|
||||
targetConfig.LogLevel = patchConfig.LogLevel
|
||||
targetConfig.Port = 0
|
||||
@@ -398,48 +410,90 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
|
||||
targetConfig.Profile.StoreSelected = false
|
||||
targetConfig.GeoXUrl = patchConfig.GeoXUrl
|
||||
targetConfig.GlobalUA = patchConfig.GlobalUA
|
||||
if targetConfig.DNS.Enable == false {
|
||||
genHosts(targetConfig.Hosts, patchConfig.Hosts)
|
||||
if configParams.OverrideDns {
|
||||
targetConfig.DNS = patchConfig.DNS
|
||||
} else {
|
||||
if targetConfig.DNS.Enable == false {
|
||||
targetConfig.DNS.Enable = true
|
||||
}
|
||||
}
|
||||
//if runtime.GOOS == "android" {
|
||||
// targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, "dhcp://"+dns.SystemDNSPlaceholder)
|
||||
//} else if runtime.GOOS == "windows" {
|
||||
// targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder)
|
||||
//}
|
||||
if configParams.IsCompatible == false {
|
||||
targetConfig.ProxyProvider = make(map[string]map[string]any)
|
||||
targetConfig.RuleProvider = make(map[string]map[string]any)
|
||||
generateProxyGroupAndRule(&targetConfig.ProxyGroup, &targetConfig.Rule)
|
||||
}
|
||||
//if configParams.IsCompatible == false {
|
||||
// targetConfig.ProxyProvider = make(map[string]map[string]any)
|
||||
// targetConfig.RuleProvider = make(map[string]map[string]any)
|
||||
// generateProxyGroupAndRule(&targetConfig.ProxyGroup, &targetConfig.Rule)
|
||||
//}
|
||||
}
|
||||
|
||||
func patchConfig(general *config.General) {
|
||||
func patchConfig(general *config.General, controller *config.Controller) {
|
||||
log.Infoln("[Apply] patch")
|
||||
route.ReStartServer(general.ExternalController)
|
||||
listener.SetAllowLan(general.AllowLan)
|
||||
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
|
||||
inbound.SetAllowedIPs(general.LanAllowedIPs)
|
||||
inbound.SetDisAllowedIPs(general.LanDisAllowedIPs)
|
||||
listener.SetBindAddress(general.BindAddress)
|
||||
route.ReStartServer(controller.ExternalController)
|
||||
tunnel.SetSniffing(general.Sniffing)
|
||||
tunnel.SetFindProcessMode(general.FindProcessMode)
|
||||
dialer.SetTcpConcurrent(general.TCPConcurrent)
|
||||
dialer.DefaultInterface.Store(general.Interface)
|
||||
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
||||
tunnel.SetMode(general.Mode)
|
||||
log.SetLevel(general.LogLevel)
|
||||
resolver.DisableIPv6 = !general.IPv6
|
||||
}
|
||||
|
||||
var isRunning = false
|
||||
|
||||
var runLock sync.Mutex
|
||||
|
||||
func updateListeners(general *config.General, listeners map[string]constant.InboundListener) {
|
||||
if !isRunning {
|
||||
return
|
||||
}
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
|
||||
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
|
||||
listener.SetAllowLan(general.AllowLan)
|
||||
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
|
||||
inbound.SetAllowedIPs(general.LanAllowedIPs)
|
||||
inbound.SetDisAllowedIPs(general.LanDisAllowedIPs)
|
||||
listener.SetBindAddress(general.BindAddress)
|
||||
listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
|
||||
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
|
||||
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
|
||||
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
|
||||
listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel)
|
||||
listener.ReCreateTun(general.Tun, tunnel.Tunnel)
|
||||
listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel)
|
||||
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
|
||||
listener.ReCreateVmess(general.VmessConfig, tunnel.Tunnel)
|
||||
listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
|
||||
tunnel.SetMode(general.Mode)
|
||||
log.SetLevel(general.LogLevel)
|
||||
listener.ReCreateTun(general.Tun, tunnel.Tunnel)
|
||||
}
|
||||
|
||||
func stopListeners() {
|
||||
listener.StopListener()
|
||||
}
|
||||
|
||||
func hcCompatibleProvider(proxyProviders map[string]cp.ProxyProvider) {
|
||||
wg := sync.WaitGroup{}
|
||||
ch := make(chan struct{}, math.MaxInt)
|
||||
for _, proxyProvider := range proxyProviders {
|
||||
proxyProvider := proxyProvider
|
||||
if proxyProvider.VehicleType() == cp.Compatible {
|
||||
log.Infoln("Start initial Compatible provider %s", proxyProvider.Name())
|
||||
wg.Add(1)
|
||||
ch <- struct{}{}
|
||||
go func() {
|
||||
defer func() { <-ch; wg.Done() }()
|
||||
if err := proxyProvider.Initial(); err != nil {
|
||||
log.Errorln("initial Compatible provider %s error: %v", proxyProvider.Name(), err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resolver.DisableIPv6 = !general.IPv6
|
||||
}
|
||||
|
||||
func patchSelectGroup() {
|
||||
@@ -467,12 +521,8 @@ func patchSelectGroup() {
|
||||
}
|
||||
}
|
||||
|
||||
var applyLock sync.Mutex
|
||||
|
||||
func applyConfig() error {
|
||||
applyLock.Lock()
|
||||
defer applyLock.Unlock()
|
||||
cfg, err := config.ParseRawConfig(currentConfig)
|
||||
cfg, err := config.ParseRawConfig(currentRawConfig)
|
||||
if err != nil {
|
||||
cfg, _ = config.ParseRawConfig(config.DefaultRawConfig())
|
||||
}
|
||||
@@ -480,12 +530,17 @@ func applyConfig() error {
|
||||
constant.DefaultTestURL = *configParams.TestURL
|
||||
}
|
||||
if configParams.IsPatch {
|
||||
patchConfig(cfg.General)
|
||||
patchConfig(cfg.General, cfg.Controller)
|
||||
} else {
|
||||
closeConnections()
|
||||
runtime.GC()
|
||||
hub.UltraApplyConfig(cfg, true)
|
||||
hub.UltraApplyConfig(cfg)
|
||||
patchSelectGroup()
|
||||
}
|
||||
updateListeners(cfg.General, cfg.Listeners)
|
||||
if isRunning {
|
||||
hcCompatibleProvider(cfg.Providers)
|
||||
}
|
||||
externalProviders = getExternalProvidersRaw()
|
||||
return err
|
||||
}
|
||||
|
||||
47
core/go.mod
47
core/go.mod
@@ -7,9 +7,8 @@ replace github.com/metacubex/mihomo => ./Clash.Meta
|
||||
require (
|
||||
github.com/Kr328/tun2socket v0.0.0-20220414050025-d07c78d06d34
|
||||
github.com/metacubex/mihomo v1.17.1
|
||||
github.com/miekg/dns v1.1.61
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/sync v0.7.0
|
||||
github.com/miekg/dns v1.1.62
|
||||
golang.org/x/sync v0.8.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -20,17 +19,16 @@ require (
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cilium/ebpf v0.12.3 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/coreos/go-iptables v0.7.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.14 // indirect
|
||||
github.com/go-chi/chi/v5 v5.1.0 // indirect
|
||||
github.com/go-chi/cors v1.2.1 // indirect
|
||||
github.com/go-chi/render v1.0.3 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
@@ -38,12 +36,12 @@ require (
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.4.0 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.2.0 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.3.0 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6 // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
@@ -52,18 +50,19 @@ require (
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // indirect
|
||||
github.com/metacubex/chacha v0.1.0 // indirect
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect
|
||||
github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e // indirect
|
||||
github.com/metacubex/quic-go v0.46.1-0.20240807232329-1c6cb2d67f58 // indirect
|
||||
github.com/metacubex/randv2 v0.2.0 // indirect
|
||||
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.7 // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.1 // indirect
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d // indirect
|
||||
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8 // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 // indirect
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 // indirect
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a // indirect
|
||||
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 // indirect
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd // indirect
|
||||
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785 // indirect
|
||||
github.com/metacubex/utls v1.6.6 // indirect
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
@@ -72,10 +71,9 @@ require (
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.2.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
|
||||
github.com/sagernet/fswatch v0.1.1 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
@@ -84,7 +82,7 @@ require (
|
||||
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e // indirect
|
||||
github.com/samber/lo v1.39.0 // indirect
|
||||
github.com/samber/lo v1.47.0 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
||||
@@ -101,13 +99,14 @@ require (
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.3.0 // indirect
|
||||
|
||||
106
core/go.sum
106
core/go.sum
@@ -19,8 +19,6 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
|
||||
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||
@@ -28,8 +26,8 @@ github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFE
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||
@@ -40,14 +38,12 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBE
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-chi/chi/v5 v5.0.14 h1:PyEwo2Vudraa0x/Wl6eDRRW2NXBvekgfxyydcM0WGE0=
|
||||
github.com/go-chi/chi/v5 v5.0.14/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
@@ -65,8 +61,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||
github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM=
|
||||
github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
|
||||
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
@@ -82,8 +78,8 @@ github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7s
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6 h1:dh8D8FksyMhD64mRMbUhZHWYJfNoNMCxfVq6eexleMw=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5 h1:GkMacU5ftc+IEg1449N3UEy2XLDz58W4fkrRu2fibb8=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
@@ -92,10 +88,6 @@ github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||
@@ -106,34 +98,36 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
||||
github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc=
|
||||
github.com/metacubex/chacha v0.1.0/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
|
||||
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
|
||||
github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e h1:bLYn3GuRvWDcBDAkIv5kUYIhzHwafDVq635BuybnKqI=
|
||||
github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e/go.mod h1:Yza2H7Ax1rxWPUcJx0vW+oAt9EsPuSiyQFhFabUPzwU=
|
||||
github.com/metacubex/quic-go v0.46.1-0.20240807232329-1c6cb2d67f58 h1:T6OxROLZBr9SOQxN5TzUslv81hEREy/dEgaUKVjaG7U=
|
||||
github.com/metacubex/quic-go v0.46.1-0.20240807232329-1c6cb2d67f58/go.mod h1:Yza2H7Ax1rxWPUcJx0vW+oAt9EsPuSiyQFhFabUPzwU=
|
||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew=
|
||||
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.7 h1:9f3Dt2+71TNp0e202llA2ug5h/rkWs2EZxQ5IMpf+9g=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.7/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.1 h1:XIZBXlazp8EEoPp1S0DViAhLkJakjQ2f+AOwwdKKNYg=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.1/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d h1:iYlepjRCYlPXtELupDL+pQjGqkCnQz4KQOfKImP9sog=
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
|
||||
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 h1:HobpULaPK6OoxrHMmgcwLkwwIduXVmwdcznwUfH1GQM=
|
||||
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 h1:ypfofGDZbP8p3Y4P/m74JYu7sQViesi3c8nbmT6cS0Y=
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a h1:NpSGclHJUYndUwBmyIpFBSoBVg8PoVX7QQKhYg0DjM0=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
|
||||
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c=
|
||||
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd h1:r7alry8u4qlUFLNMwGvG1A8ZcfPM6AMSmrm6E2yKdB4=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
|
||||
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785 h1:NNmI+ZV0DzNuqaAInRQuZFLHlWVuyHeow8jYpdKjHjo=
|
||||
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
|
||||
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
|
||||
github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo=
|
||||
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
|
||||
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
|
||||
@@ -156,16 +150,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.2.0 h1:9AzuUeF88YC5bK8u2vEG1Fpvu4wgpM1wfPIExfaaDxQ=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.2.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||
@@ -183,8 +173,8 @@ github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxe
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2FzELOFNFQtvsxH7NPmlo7X5JizEK51UCojo=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE=
|
||||
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
||||
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
@@ -230,21 +220,21 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -261,18 +251,18 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
|
||||
227
core/hub.go
227
core/hub.go
@@ -5,35 +5,56 @@ package main
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
bridge "core/dart-bridge"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
"github.com/metacubex/mihomo/adapter/provider"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/updater"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
cp "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/hub/executor"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
rp "github.com/metacubex/mihomo/rules/provider"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||
"golang.org/x/net/context"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var currentConfig = config.DefaultRawConfig()
|
||||
var currentRawConfig = config.DefaultRawConfig()
|
||||
|
||||
var configParams = ConfigExtendedParams{}
|
||||
|
||||
var externalProviders = map[string]cp.Provider{}
|
||||
|
||||
var isInit = false
|
||||
|
||||
//export start
|
||||
func start() {
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
isRunning = true
|
||||
}
|
||||
|
||||
//export stop
|
||||
func stop() {
|
||||
runLock.Lock()
|
||||
go func() {
|
||||
defer runLock.Unlock()
|
||||
isRunning = false
|
||||
stopListeners()
|
||||
}()
|
||||
}
|
||||
|
||||
//export initClash
|
||||
func initClash(homeDirStr *C.char) bool {
|
||||
if !isInit {
|
||||
@@ -57,10 +78,10 @@ func restartClash() bool {
|
||||
|
||||
//export shutdownClash
|
||||
func shutdownClash() bool {
|
||||
stopListeners()
|
||||
executor.Shutdown()
|
||||
runtime.GC()
|
||||
isInit = false
|
||||
currentConfig = nil
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -86,11 +107,15 @@ func validateConfig(s *C.char, port C.longlong) {
|
||||
}()
|
||||
}
|
||||
|
||||
var updateLock sync.Mutex
|
||||
|
||||
//export updateConfig
|
||||
func updateConfig(s *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
paramsString := C.GoString(s)
|
||||
go func() {
|
||||
updateLock.Lock()
|
||||
defer updateLock.Unlock()
|
||||
var params = &GenerateConfigParams{}
|
||||
err := json.Unmarshal([]byte(paramsString), params)
|
||||
if err != nil {
|
||||
@@ -99,7 +124,7 @@ func updateConfig(s *C.char, port C.longlong) {
|
||||
}
|
||||
configParams = params.Params
|
||||
prof := decorationConfig(params.ProfileId, params.Config)
|
||||
currentConfig = prof
|
||||
currentRawConfig = prof
|
||||
err = applyConfig()
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
@@ -200,11 +225,13 @@ func asyncTestDelay(s *C.char, port C.longlong) {
|
||||
var params = &TestDelayParams{}
|
||||
err := json.Unmarshal([]byte(paramsString), params)
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, "")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
expectedStatus, err := utils.NewUnsignedRanges[uint16]("")
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, "")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -311,34 +338,16 @@ func getProvider(name *C.char) *C.char {
|
||||
|
||||
//export getExternalProviders
|
||||
func getExternalProviders() *C.char {
|
||||
externalProviders := make(map[string]ExternalProvider)
|
||||
for n, p := range tunnel.Providers() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*provider.ProxySetProvider)
|
||||
externalProviders[n] = ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Count: p.Count(),
|
||||
Path: p.Vehicle().Path(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
}
|
||||
eps := make([]ExternalProvider, 0)
|
||||
for _, p := range externalProviders {
|
||||
externalProvider, err := toExternalProvider(p)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
eps = append(eps, *externalProvider)
|
||||
}
|
||||
for n, p := range tunnel.RuleProviders() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*rp.RuleSetProvider)
|
||||
externalProviders[n] = ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Count: p.Count(),
|
||||
Path: p.Vehicle().Path(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
data, err := json.Marshal(externalProviders)
|
||||
sort.Sort(ExternalProviders(eps))
|
||||
data, err := json.Marshal(eps)
|
||||
if err != nil {
|
||||
return C.CString("")
|
||||
}
|
||||
@@ -348,69 +357,48 @@ func getExternalProviders() *C.char {
|
||||
//export getExternalProvider
|
||||
func getExternalProvider(name *C.char) *C.char {
|
||||
externalProviderName := C.GoString(name)
|
||||
externalProviders := getExternalProvidersRaw()
|
||||
externalProvider, exist := externalProviders[externalProviderName]
|
||||
if !exist {
|
||||
return C.CString("")
|
||||
}
|
||||
data, err := json.Marshal(externalProvider)
|
||||
e, err := toExternalProvider(externalProvider)
|
||||
if err != nil {
|
||||
return C.CString("")
|
||||
}
|
||||
data, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return C.CString("")
|
||||
}
|
||||
return C.CString(string(data))
|
||||
}
|
||||
|
||||
//export updateExternalProvider
|
||||
func updateExternalProvider(providerName *C.char, providerType *C.char, port C.longlong) {
|
||||
//export updateGeoData
|
||||
func updateGeoData(geoType *C.char, geoName *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
providerNameString := C.GoString(providerName)
|
||||
providerTypeString := C.GoString(providerType)
|
||||
geoTypeString := C.GoString(geoType)
|
||||
geoNameString := C.GoString(geoName)
|
||||
go func() {
|
||||
switch providerTypeString {
|
||||
case "Proxy":
|
||||
providers := tunnel.Providers()
|
||||
proxyProvider, exist := providers[providerNameString].(*provider.ProxySetProvider)
|
||||
if !exist {
|
||||
bridge.SendToPort(i, "proxy provider is not exist")
|
||||
return
|
||||
}
|
||||
err := proxyProvider.Update()
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "Rule":
|
||||
providers := tunnel.RuleProviders()
|
||||
ruleProvider, exist := providers[providerNameString].(*rp.RuleSetProvider)
|
||||
if !exist {
|
||||
bridge.SendToPort(i, "rule provider is not exist")
|
||||
return
|
||||
}
|
||||
err := ruleProvider.Update()
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
switch geoTypeString {
|
||||
case "MMDB":
|
||||
err := updater.UpdateMMDB(constant.Path.Resolve(providerNameString))
|
||||
err := updater.UpdateMMDB(constant.Path.Resolve(geoNameString))
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "ASN":
|
||||
err := updater.UpdateASN(constant.Path.Resolve(providerNameString))
|
||||
err := updater.UpdateASN(constant.Path.Resolve(geoNameString))
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "GeoIp":
|
||||
err := updater.UpdateGeoIp(constant.Path.Resolve(providerNameString))
|
||||
err := updater.UpdateGeoIp(constant.Path.Resolve(geoNameString))
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "GeoSite":
|
||||
err := updater.UpdateGeoSite(constant.Path.Resolve(providerNameString))
|
||||
err := updater.UpdateGeoSite(constant.Path.Resolve(geoNameString))
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
@@ -420,65 +408,44 @@ func updateExternalProvider(providerName *C.char, providerType *C.char, port C.l
|
||||
}()
|
||||
}
|
||||
|
||||
//func sideLoadExternalProvider(providerName *C.char, providerType *C.char, data *C.char, port C.longlong) {
|
||||
// i := int64(port)
|
||||
// bytes := []byte(C.GoString(data))
|
||||
// providerNameString := C.GoString(providerName)
|
||||
// providerTypeString := C.GoString(providerType)
|
||||
// go func() {
|
||||
// switch providerTypeString {
|
||||
// case "Proxy":
|
||||
// providers := tunnel.Providers()
|
||||
// proxyProvider, exist := providers[providerNameString].(*provider.ProxySetProvider)
|
||||
// if exist {
|
||||
// bridge.SendToPort(i, "proxy provider is not exist")
|
||||
// return
|
||||
// }
|
||||
// err := proxyProvider.Update()
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "Rule":
|
||||
// providers := tunnel.RuleProviders()
|
||||
// ruleProvider, exist := providers[providerNameString].(*rp.RuleSetProvider)
|
||||
// if exist {
|
||||
// bridge.SendToPort(i, "proxy provider is not exist")
|
||||
// return
|
||||
// }
|
||||
// err := ruleProvider.Update()
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "MMDB":
|
||||
// err := updater.UpdateMMDB(constant.Path.Resolve(providerNameString))
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "ASN":
|
||||
// err := updater.UpdateASN(constant.Path.Resolve(providerNameString))
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "GeoIp":
|
||||
// err := updater.UpdateGeoIp(constant.Path.Resolve(providerNameString))
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "GeoSite":
|
||||
// err := updater.UpdateGeoSite(constant.Path.Resolve(providerNameString))
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// bridge.SendToPort(i, "")
|
||||
// }()
|
||||
//}
|
||||
//export updateExternalProvider
|
||||
func updateExternalProvider(providerName *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
providerNameString := C.GoString(providerName)
|
||||
go func() {
|
||||
externalProvider, exist := externalProviders[providerNameString]
|
||||
if !exist {
|
||||
bridge.SendToPort(i, "external provider is not exist")
|
||||
return
|
||||
}
|
||||
err := externalProvider.Update()
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
bridge.SendToPort(i, "")
|
||||
}()
|
||||
}
|
||||
|
||||
//export sideLoadExternalProvider
|
||||
func sideLoadExternalProvider(providerName *C.char, data *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
bytes := []byte(C.GoString(data))
|
||||
providerNameString := C.GoString(providerName)
|
||||
go func() {
|
||||
externalProvider, exist := externalProviders[providerNameString]
|
||||
if !exist {
|
||||
bridge.SendToPort(i, "external provider is not exist")
|
||||
return
|
||||
}
|
||||
err := sideUpdateExternalProvider(externalProvider, bytes)
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
bridge.SendToPort(i, "")
|
||||
}()
|
||||
}
|
||||
|
||||
//export initNativeApiBridge
|
||||
func initNativeApiBridge(api unsafe.Pointer) {
|
||||
|
||||
@@ -14,6 +14,7 @@ type AccessControl struct {
|
||||
}
|
||||
|
||||
type AndroidProps struct {
|
||||
Enable bool `json:"enable"`
|
||||
AccessControl *AccessControl `json:"accessControl"`
|
||||
AllowBypass bool `json:"allowBypass"`
|
||||
SystemProxy bool `json:"systemProxy"`
|
||||
|
||||
26
core/tun.go
26
core/tun.go
@@ -7,14 +7,15 @@ import (
|
||||
"core/platform"
|
||||
t "core/tun"
|
||||
"errors"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"golang.org/x/sync/semaphore"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
var tunLock sync.Mutex
|
||||
@@ -40,6 +41,18 @@ var fdMap FdMap
|
||||
func startTUN(fd C.int, port C.longlong) {
|
||||
i := int64(port)
|
||||
ServicePort = i
|
||||
if fd == 0 {
|
||||
tunLock.Lock()
|
||||
defer tunLock.Unlock()
|
||||
now := time.Now()
|
||||
runTime = &now
|
||||
SendMessage(Message{
|
||||
Type: StartedMessage,
|
||||
Data: strconv.FormatInt(runTime.UnixMilli(), 10),
|
||||
})
|
||||
return
|
||||
}
|
||||
initSocketHook()
|
||||
go func() {
|
||||
tunLock.Lock()
|
||||
defer tunLock.Unlock()
|
||||
@@ -88,6 +101,7 @@ func getRunTime() *C.char {
|
||||
|
||||
//export stopTun
|
||||
func stopTun() {
|
||||
removeSocketHook()
|
||||
go func() {
|
||||
tunLock.Lock()
|
||||
defer tunLock.Unlock()
|
||||
@@ -125,7 +139,7 @@ func markSocket(fd Fd) {
|
||||
|
||||
var fdCounter int64 = 0
|
||||
|
||||
func init() {
|
||||
func initSocketHook() {
|
||||
dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error {
|
||||
if platform.ShouldBlockConnection() {
|
||||
return errBlocked
|
||||
@@ -159,3 +173,7 @@ func init() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func removeSocketHook() {
|
||||
dialer.DefaultSocketHook = nil
|
||||
}
|
||||
|
||||
@@ -67,7 +67,6 @@ func Start(fd int, gateway, portal, dns string) (io.Closer, error) {
|
||||
for stack.TCP().SetDeadline(time.Time{}) == nil {
|
||||
conn, err := stack.TCP().Accept()
|
||||
if err != nil {
|
||||
log.Errorln("Accept connection: %v", err)
|
||||
continue
|
||||
}
|
||||
lAddr := conn.LocalAddr().(*net.TCPAddr)
|
||||
|
||||
@@ -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';
|
||||
@@ -88,7 +89,6 @@ class ApplicationState extends State<Application> {
|
||||
}
|
||||
await globalState.appController.init();
|
||||
globalState.appController.initLink();
|
||||
_updateGroups();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -96,7 +96,9 @@ class ApplicationState extends State<Application> {
|
||||
if (system.isDesktop) {
|
||||
return WindowContainer(
|
||||
child: TrayContainer(
|
||||
child: app,
|
||||
child: ProxyContainer(
|
||||
child: app,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -107,6 +109,17 @@ class ApplicationState extends State<Application> {
|
||||
);
|
||||
}
|
||||
|
||||
_buildPage(Widget page) {
|
||||
if (system.isDesktop) {
|
||||
return WindowHeaderContainer(
|
||||
child: page,
|
||||
);
|
||||
}
|
||||
return VpnContainer(
|
||||
child: page,
|
||||
);
|
||||
}
|
||||
|
||||
_updateSystemColorSchemes(
|
||||
ColorScheme? lightDynamic,
|
||||
ColorScheme? darkDynamic,
|
||||
@@ -120,75 +133,66 @@ class ApplicationState extends State<Application> {
|
||||
});
|
||||
}
|
||||
|
||||
_updateGroups() {
|
||||
if (globalState.groupsUpdateTimer != null) {
|
||||
globalState.groupsUpdateTimer?.cancel();
|
||||
globalState.groupsUpdateTimer = null;
|
||||
}
|
||||
globalState.groupsUpdateTimer ??= Timer.periodic(
|
||||
httpTimeoutDuration,
|
||||
(timer) async {
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@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(
|
||||
debugShowCheckedModeBanner: false,
|
||||
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) {
|
||||
return MediaContainer(
|
||||
child: _buildPage(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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -140,8 +140,7 @@ class ClashCore {
|
||||
clashFFI.freeCString(externalProvidersRaw);
|
||||
return Isolate.run<List<ExternalProvider>>(() {
|
||||
final externalProviders =
|
||||
(json.decode(externalProvidersRawString) as Map<String, dynamic>)
|
||||
.values
|
||||
(json.decode(externalProvidersRawString) as List<dynamic>)
|
||||
.map(
|
||||
(item) => ExternalProvider.fromJson(item),
|
||||
)
|
||||
@@ -150,7 +149,7 @@ class ClashCore {
|
||||
});
|
||||
}
|
||||
|
||||
ExternalProvider getExternalProvider(String externalProviderName) {
|
||||
ExternalProvider? getExternalProvider(String externalProviderName) {
|
||||
final externalProviderNameChar =
|
||||
externalProviderName.toNativeUtf8().cast<Char>();
|
||||
final externalProviderRaw =
|
||||
@@ -159,12 +158,37 @@ class ClashCore {
|
||||
final externalProviderRawString =
|
||||
externalProviderRaw.cast<Utf8>().toDartString();
|
||||
clashFFI.freeCString(externalProviderRaw);
|
||||
if(externalProviderRawString.isEmpty) return null;
|
||||
return ExternalProvider.fromJson(json.decode(externalProviderRawString));
|
||||
}
|
||||
|
||||
Future<String> updateExternalProvider({
|
||||
Future<String> updateGeoData({
|
||||
required String geoType,
|
||||
required String geoName,
|
||||
}) {
|
||||
final completer = Completer<String>();
|
||||
final receiver = ReceivePort();
|
||||
receiver.listen((message) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(message);
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final geoTypeChar = geoType.toNativeUtf8().cast<Char>();
|
||||
final geoNameChar = geoName.toNativeUtf8().cast<Char>();
|
||||
clashFFI.updateGeoData(
|
||||
geoTypeChar,
|
||||
geoNameChar,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(geoTypeChar);
|
||||
malloc.free(geoNameChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<String> sideLoadExternalProvider({
|
||||
required String providerName,
|
||||
required String providerType,
|
||||
required String data,
|
||||
}) {
|
||||
final completer = Completer<String>();
|
||||
final receiver = ReceivePort();
|
||||
@@ -175,14 +199,34 @@ class ClashCore {
|
||||
}
|
||||
});
|
||||
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
|
||||
final providerTypeChar = providerType.toNativeUtf8().cast<Char>();
|
||||
clashFFI.updateExternalProvider(
|
||||
final dataChar = data.toNativeUtf8().cast<Char>();
|
||||
clashFFI.sideLoadExternalProvider(
|
||||
providerNameChar,
|
||||
dataChar,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(providerNameChar);
|
||||
malloc.free(dataChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<String> updateExternalProvider({
|
||||
required String providerName,
|
||||
}) {
|
||||
final completer = Completer<String>();
|
||||
final receiver = ReceivePort();
|
||||
receiver.listen((message) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(message);
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
|
||||
clashFFI.updateExternalProvider(
|
||||
providerNameChar,
|
||||
providerTypeChar,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(providerNameChar);
|
||||
malloc.free(providerTypeChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@@ -193,6 +237,14 @@ class ClashCore {
|
||||
malloc.free(paramsChar);
|
||||
}
|
||||
|
||||
start() {
|
||||
clashFFI.start();
|
||||
}
|
||||
|
||||
stop() {
|
||||
clashFFI.stop();
|
||||
}
|
||||
|
||||
Future<Delay> getDelay(String proxyName) {
|
||||
final delayParams = {
|
||||
"proxy-name": proxyName,
|
||||
|
||||
@@ -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,
|
||||
) {
|
||||
@@ -5400,24 +5416,61 @@ class ClashFFI {
|
||||
late final _getExternalProvider = _getExternalProviderPtr
|
||||
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
|
||||
|
||||
void updateGeoData(
|
||||
ffi.Pointer<ffi.Char> geoType,
|
||||
ffi.Pointer<ffi.Char> geoName,
|
||||
int port,
|
||||
) {
|
||||
return _updateGeoData(
|
||||
geoType,
|
||||
geoName,
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _updateGeoDataPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
|
||||
ffi.LongLong)>>('updateGeoData');
|
||||
late final _updateGeoData = _updateGeoDataPtr.asFunction<
|
||||
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
||||
|
||||
void updateExternalProvider(
|
||||
ffi.Pointer<ffi.Char> providerName,
|
||||
ffi.Pointer<ffi.Char> providerType,
|
||||
int port,
|
||||
) {
|
||||
return _updateExternalProvider(
|
||||
providerName,
|
||||
providerType,
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _updateExternalProviderPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(
|
||||
ffi.Pointer<ffi.Char>, ffi.LongLong)>>('updateExternalProvider');
|
||||
late final _updateExternalProvider = _updateExternalProviderPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
|
||||
|
||||
void sideLoadExternalProvider(
|
||||
ffi.Pointer<ffi.Char> providerName,
|
||||
ffi.Pointer<ffi.Char> data,
|
||||
int port,
|
||||
) {
|
||||
return _sideLoadExternalProvider(
|
||||
providerName,
|
||||
data,
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _sideLoadExternalProviderPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
|
||||
ffi.LongLong)>>('updateExternalProvider');
|
||||
late final _updateExternalProvider = _updateExternalProviderPtr.asFunction<
|
||||
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
||||
ffi.LongLong)>>('sideLoadExternalProvider');
|
||||
late final _sideLoadExternalProvider =
|
||||
_sideLoadExternalProviderPtr.asFunction<
|
||||
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
||||
|
||||
void initNativeApiBridge(
|
||||
ffi.Pointer<ffi.Void> api,
|
||||
|
||||
@@ -23,6 +23,8 @@ 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';
|
||||
export 'scroll.dart';
|
||||
export 'icons.dart';
|
||||
export 'http.dart';
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
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:path/path.dart';
|
||||
import 'package:webdav_client/webdav_client.dart';
|
||||
|
||||
class DAVClient {
|
||||
late Client client;
|
||||
Completer<bool> pingCompleter = Completer();
|
||||
late String fileName;
|
||||
|
||||
DAVClient(DAV dav) {
|
||||
client = newClient(
|
||||
@@ -19,6 +16,7 @@ class DAVClient {
|
||||
user: dav.user,
|
||||
password: dav.password,
|
||||
);
|
||||
fileName = dav.fileName;
|
||||
client.setHeaders(
|
||||
{
|
||||
'accept-charset': 'utf-8',
|
||||
@@ -34,8 +32,6 @@ class DAVClient {
|
||||
Future<bool> _ping() async {
|
||||
try {
|
||||
await client.ping();
|
||||
await client.mkdir("/$appName");
|
||||
await client.mkdir("/$appName/$profilesDirectoryName");
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
@@ -44,7 +40,7 @@ class DAVClient {
|
||||
|
||||
get root => "/$appName";
|
||||
|
||||
get backupFile => "$root/backup.zip";
|
||||
get backupFile => "$root/$fileName";
|
||||
|
||||
backup(Uint8List data) async {
|
||||
await client.mkdir("$root");
|
||||
@@ -53,6 +49,7 @@ class DAVClient {
|
||||
}
|
||||
|
||||
Future<List<int>> recovery() async {
|
||||
await client.mkdir("$root");
|
||||
final data = await client.read(backupFile);
|
||||
return data;
|
||||
}
|
||||
|
||||
21
lib/common/http.dart
Normal file
21
lib/common/http.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import '../state.dart';
|
||||
|
||||
class FlClashHttpOverrides extends HttpOverrides {
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext? context) {
|
||||
final client = super.createHttpClient(context);
|
||||
client.badCertificateCallback = (_, __, ___) => true;
|
||||
client.findProxy = (url) {
|
||||
debugPrint("find $url");
|
||||
final port = globalState.appController.clashConfig.mixedPort;
|
||||
final isStart = globalState.appController.appState.isStart;
|
||||
if (!isStart) return "DIRECT";
|
||||
return "PROXY localhost:$port";
|
||||
};
|
||||
return client;
|
||||
}
|
||||
}
|
||||
6
lib/common/icons.dart
Normal file
6
lib/common/icons.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IconsExt{
|
||||
static const IconData target =
|
||||
IconData(0xe900, fontFamily: "Icons");
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:fl_clash/models/models.dart' hide Process;
|
||||
import 'package:launch_at_startup/launch_at_startup.dart';
|
||||
|
||||
import 'constant.dart';
|
||||
import 'system.dart';
|
||||
import 'windows.dart';
|
||||
|
||||
class AutoLaunch {
|
||||
static AutoLaunch? _instance;
|
||||
@@ -24,18 +26,77 @@ 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 {
|
||||
if (Platform.isWindows) {
|
||||
await windowsDisable();
|
||||
}
|
||||
return await launchAtStartup.enable();
|
||||
}
|
||||
|
||||
windowsDisable() async {
|
||||
final res = await Process.run(
|
||||
'schtasks',
|
||||
[
|
||||
'/Delete',
|
||||
'/TN',
|
||||
appName,
|
||||
'/F',
|
||||
],
|
||||
runInShell: true,
|
||||
);
|
||||
return res.exitCode == 0;
|
||||
}
|
||||
|
||||
Future<bool> windowsEnable() async {
|
||||
await disable();
|
||||
return windows?.runas(
|
||||
'schtasks',
|
||||
[
|
||||
'/Create',
|
||||
'/SC',
|
||||
'ONLOGON',
|
||||
'/TN',
|
||||
appName,
|
||||
'/TR',
|
||||
'"${Platform.resolvedExecutable}"',
|
||||
'/RL',
|
||||
'HIGHEST',
|
||||
'/F'
|
||||
].join(" "),
|
||||
) ??
|
||||
false;
|
||||
}
|
||||
|
||||
Future<bool> disable() async {
|
||||
return await launchAtStartup.disable();
|
||||
}
|
||||
|
||||
updateStatus(bool value) async {
|
||||
final isEnable = await this.isEnable;
|
||||
if (isEnable == value) return;
|
||||
if (value == true) {
|
||||
updateStatus(AutoLaunchState state) async {
|
||||
final isOpenTun = state.isOpenTun;
|
||||
final isAutoLaunch = state.isAutoLaunch;
|
||||
if (Platform.isWindows && isOpenTun) {
|
||||
if (await windowsIsEnable == isAutoLaunch) return;
|
||||
if (isAutoLaunch) {
|
||||
final isEnable = await windowsEnable();
|
||||
if (!isEnable) {
|
||||
enable();
|
||||
}
|
||||
} else {
|
||||
windowsDisable();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (await isEnable == isAutoLaunch) return;
|
||||
if (isAutoLaunch == true) {
|
||||
enable();
|
||||
} else {
|
||||
disable();
|
||||
|
||||
@@ -2,4 +2,17 @@ extension ListExtension<T> on List<T> {
|
||||
List<T> intersection(List<T> list) {
|
||||
return where((item) => list.contains(item)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
List<List<T>> batch(int maxConcurrent) {
|
||||
final batches = (length / maxConcurrent).ceil();
|
||||
final List<List<T>> res = [];
|
||||
for (int i = 0; i < batches; i++) {
|
||||
if (i != batches - 1) {
|
||||
res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1)));
|
||||
} else {
|
||||
res.add(sublist(i * maxConcurrent, length));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,21 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Measure {
|
||||
Measure.of(this.context);
|
||||
final TextScaler _textScale;
|
||||
late BuildContext context;
|
||||
|
||||
final _textScaleFactor =
|
||||
WidgetsBinding.instance.platformDispatcher.textScaleFactor;
|
||||
Measure.of(this.context) : _textScale = MediaQuery.of(context).textScaler;
|
||||
|
||||
Size computeTextSize(Text text) {
|
||||
final textPainter = TextPainter(
|
||||
text: TextSpan(text: text.data, style: text.style),
|
||||
maxLines: text.maxLines,
|
||||
textScaler: TextScaler.linear(_textScaleFactor),
|
||||
textScaler: _textScale,
|
||||
textDirection: text.textDirection ?? TextDirection.ltr,
|
||||
)..layout();
|
||||
return textPainter.size;
|
||||
}
|
||||
|
||||
late BuildContext context;
|
||||
|
||||
double? _bodyMediumHeight;
|
||||
double? _bodySmallHeight;
|
||||
double? _labelSmallHeight;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fl_clash/common/app_localizations.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/common/constant.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;
|
||||
|
||||
@@ -84,7 +84,7 @@ class Other {
|
||||
if (charA == charB) {
|
||||
return sortByChar(a.substring(1), b.substring(1));
|
||||
} else {
|
||||
return charA.compareTo(charB);
|
||||
return charA.compareToLower(charB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,12 @@ class Other {
|
||||
return build1.compareTo(build2);
|
||||
}
|
||||
|
||||
String getPinyin(String value) {
|
||||
return value.isNotEmpty
|
||||
? PinyinHelper.getFirstWordPinyin(value.substring(0, 1))
|
||||
: "";
|
||||
}
|
||||
|
||||
Future<String?> parseQRCode(Uint8List? bytes) {
|
||||
return Isolate.run<String?>(() {
|
||||
if (bytes == null) return null;
|
||||
@@ -192,25 +198,20 @@ class Other {
|
||||
return ViewMode.desktop;
|
||||
}
|
||||
|
||||
int getColumns(ViewMode viewMode, int currentColumns) {
|
||||
final targetColumnsArray = viewModeColumnsMap[viewMode]!;
|
||||
if (targetColumnsArray.contains(currentColumns)) {
|
||||
return currentColumns;
|
||||
}
|
||||
return targetColumnsArray.first;
|
||||
}
|
||||
|
||||
String getColumnsTextForInt(int number){
|
||||
return switch(number){
|
||||
1 => appLocalizations.oneColumn,
|
||||
2 => appLocalizations.twoColumns,
|
||||
3 => appLocalizations.threeColumns,
|
||||
4 => appLocalizations.fourColumns,
|
||||
int() => throw UnimplementedError(),
|
||||
int getProxiesColumns(double viewWidth, ProxiesLayout proxiesLayout) {
|
||||
final columns = max((viewWidth / 300).ceil(), 2);
|
||||
return switch (proxiesLayout) {
|
||||
ProxiesLayout.tight => columns - 1,
|
||||
ProxiesLayout.standard => columns,
|
||||
ProxiesLayout.loose => columns + 1,
|
||||
};
|
||||
}
|
||||
|
||||
String getBackupFileName(){
|
||||
int getProfilesColumns(double viewWidth) {
|
||||
return max((viewWidth / 400).floor(), 1);
|
||||
}
|
||||
|
||||
String getBackupFileName() {
|
||||
return "${appName}_backup_${DateTime.now().show}.zip";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
@@ -14,12 +15,16 @@ class Picker {
|
||||
return filePickerResult?.files.first;
|
||||
}
|
||||
|
||||
Future<String?> saveFile(String fileName,Uint8List bytes) async {
|
||||
Future<String?> saveFile(String fileName, Uint8List bytes) async {
|
||||
final path = await FilePicker.platform.saveFile(
|
||||
fileName: fileName,
|
||||
initialDirectory: await appPath.getDownloadDirPath(),
|
||||
bytes: bytes,
|
||||
bytes: Platform.isAndroid ? bytes : null,
|
||||
);
|
||||
if (!Platform.isAndroid && path != null) {
|
||||
final file = await File(path).create(recursive: true);
|
||||
await file.writeAsBytes(bytes);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,66 +1,57 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/ip.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class Request {
|
||||
late final Dio _dio;
|
||||
int? _port;
|
||||
bool _isStart = false;
|
||||
String? userAgent;
|
||||
|
||||
Request() {
|
||||
_dio = Dio();
|
||||
_dio.options = BaseOptions(
|
||||
headers: {"User-Agent": globalState.appController.clashConfig.globalUa},
|
||||
);
|
||||
_dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
_updateAdapter();
|
||||
return handler.next(options); // 继续请求
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_updateAdapter() {
|
||||
final port = globalState.appController.clashConfig.mixedPort;
|
||||
final isStart = globalState.appController.appState.isStart;
|
||||
if (_port != port || isStart != _isStart) {
|
||||
_port = port;
|
||||
_isStart = isStart;
|
||||
_dio.httpClientAdapter = IOHttpClientAdapter(
|
||||
createHttpClient: () {
|
||||
final client = HttpClient();
|
||||
if (!_isStart) return client;
|
||||
client.userAgent = globalState.appController.clashConfig.globalUa;
|
||||
client.findProxy = (url) {
|
||||
return "PROXY localhost:$_port;DIRECT";
|
||||
};
|
||||
return client;
|
||||
},
|
||||
validateCertificate: (_, __, ___) => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response> getFileResponseForUrl(String url) async {
|
||||
final response = await _dio
|
||||
.get(
|
||||
url,
|
||||
options: Options(
|
||||
headers: {
|
||||
"User-Agent": globalState.appController.clashConfig.globalUa
|
||||
},
|
||||
responseType: ResponseType.bytes,
|
||||
),
|
||||
)
|
||||
.timeout(
|
||||
httpTimeoutDuration * 2,
|
||||
httpTimeoutDuration * 6,
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<MemoryImage?> getImage(String url) async {
|
||||
if (url.isEmpty) return null;
|
||||
final response = await _dio.get<Uint8List>(
|
||||
url,
|
||||
options: Options(
|
||||
responseType: ResponseType.bytes,
|
||||
),
|
||||
);
|
||||
final data = response.data;
|
||||
if (data == null) return null;
|
||||
return MemoryImage(data);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> checkForUpdate() async {
|
||||
final response = await _dio.get(
|
||||
"https://api.github.com/repos/$repository/releases/latest",
|
||||
@@ -86,10 +77,13 @@ class Request {
|
||||
};
|
||||
|
||||
Future<IpInfo?> checkIp({CancelToken? cancelToken}) async {
|
||||
for (final source in _ipInfoSources.entries) {
|
||||
for (final source in _ipInfoSources.entries.toList()..shuffle(Random())) {
|
||||
try {
|
||||
final response = await _dio
|
||||
.get<Map<String, dynamic>>(source.key, cancelToken: cancelToken)
|
||||
.get<Map<String, dynamic>>(
|
||||
source.key,
|
||||
cancelToken: cancelToken,
|
||||
)
|
||||
.timeout(
|
||||
httpTimeoutDuration,
|
||||
);
|
||||
@@ -97,6 +91,9 @@ class Request {
|
||||
return source.value(response.data!);
|
||||
}
|
||||
} catch (e) {
|
||||
if (cancelToken?.isCancelled == true) {
|
||||
throw "cancelled";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -2,4 +2,10 @@ extension StringExtension on String {
|
||||
bool get isUrl {
|
||||
return RegExp(r'^(http|https|ftp)://').hasMatch(this);
|
||||
}
|
||||
|
||||
int compareToLower(String other) {
|
||||
return toLowerCase().compareTo(
|
||||
other.toLowerCase(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ class System {
|
||||
bool get isDesktop =>
|
||||
Platform.isWindows || Platform.isMacOS || Platform.isLinux;
|
||||
|
||||
get isAdmin async {
|
||||
if (!Platform.isWindows) return false;
|
||||
final result = await Process.run('net', ['session'], runInShell: true);
|
||||
return result.exitCode == 0;
|
||||
}
|
||||
|
||||
back() async {
|
||||
await app?.moveTaskToBack();
|
||||
await window?.hide();
|
||||
|
||||
59
lib/common/windows.dart
Normal file
59
lib/common/windows.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
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!;
|
||||
}
|
||||
|
||||
bool runas(String command, String arguments) {
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
final windows = Platform.isWindows ? Windows() : null;
|
||||
@@ -21,10 +21,10 @@ class AppController {
|
||||
late AppState appState;
|
||||
late Config config;
|
||||
late ClashConfig clashConfig;
|
||||
late Measure measure;
|
||||
late Function updateClashConfigDebounce;
|
||||
late Function updateGroupDebounce;
|
||||
late Function addCheckIpNumDebounce;
|
||||
late Function applyProfileDebounce;
|
||||
|
||||
AppController(this.context) {
|
||||
appState = context.read<AppState>();
|
||||
@@ -33,19 +33,20 @@ class AppController {
|
||||
updateClashConfigDebounce = debounce<Function()>(() async {
|
||||
await updateClashConfig();
|
||||
});
|
||||
applyProfileDebounce = debounce<Function()>(() async {
|
||||
await applyProfile(isPrue: true);
|
||||
});
|
||||
addCheckIpNumDebounce = debounce(() {
|
||||
appState.checkIpNum++;
|
||||
});
|
||||
updateGroupDebounce = debounce(() async {
|
||||
await updateGroups();
|
||||
});
|
||||
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,
|
||||
);
|
||||
@@ -55,10 +56,9 @@ class AppController {
|
||||
updateRunTime,
|
||||
updateTraffic,
|
||||
];
|
||||
if (Platform.isAndroid) return;
|
||||
await applyProfile(isPrue: true);
|
||||
applyProfileDebounce();
|
||||
} else {
|
||||
await globalState.stopSystemProxy();
|
||||
await globalState.handleStop();
|
||||
clashCore.resetTraffic();
|
||||
appState.traffics = [];
|
||||
appState.totalTraffic = Traffic();
|
||||
@@ -72,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 {
|
||||
@@ -101,7 +102,8 @@ class AppController {
|
||||
final updateId = config.profiles.first.id;
|
||||
changeProfile(updateId);
|
||||
} else {
|
||||
updateSystemProxy(false);
|
||||
changeProfile(null);
|
||||
updateStatus(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,7 +229,8 @@ class AppController {
|
||||
}
|
||||
|
||||
handleExit() async {
|
||||
await updateSystemProxy(false);
|
||||
await updateStatus(false);
|
||||
await proxy?.stopProxy();
|
||||
await savePreferences();
|
||||
clashCore.shutdown();
|
||||
system.exit();
|
||||
@@ -296,16 +299,26 @@ 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();
|
||||
}
|
||||
|
||||
updateTray() {
|
||||
globalState.updateTray(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
}
|
||||
|
||||
setDelay(Delay delay) {
|
||||
appState.setDelay(delay);
|
||||
}
|
||||
@@ -364,6 +377,10 @@ class AppController {
|
||||
);
|
||||
}
|
||||
|
||||
showSnackBar(String message) {
|
||||
globalState.showSnackBar(context, message: message);
|
||||
}
|
||||
|
||||
addProfileFormURL(String url) async {
|
||||
if (globalState.navigatorKey.currentState?.canPop() ?? false) {
|
||||
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
|
||||
@@ -413,8 +430,6 @@ class AppController {
|
||||
addProfileFormURL(url);
|
||||
}
|
||||
|
||||
int get columns => other.getColumns(appState.viewMode, config.proxiesColumns);
|
||||
|
||||
updateViewWidth(double width) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
appState.viewWidth = width;
|
||||
@@ -424,7 +439,10 @@ class AppController {
|
||||
List<Proxy> _sortOfName(List<Proxy> proxies) {
|
||||
return List.of(proxies)
|
||||
..sort(
|
||||
(a, b) => other.sortByChar(a.name, b.name),
|
||||
(a, b) => other.sortByChar(
|
||||
other.getPinyin(a.name),
|
||||
other.getPinyin(b.name),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
enum GroupType { Selector, URLTest, Fallback, LoadBalance, Relay }
|
||||
|
||||
enum GroupName { GLOBAL, Proxy, Auto, Fallback }
|
||||
@@ -52,6 +54,8 @@ enum TunStack { gvisor, system, mixed }
|
||||
|
||||
enum AccessControlMode { acceptSelected, rejectSelected }
|
||||
|
||||
enum AccessSortType { none, name, time }
|
||||
|
||||
enum ProfileType { file, url }
|
||||
|
||||
enum ResultType { success, error }
|
||||
@@ -84,4 +88,17 @@ enum CommonCardType { plain, filled }
|
||||
|
||||
enum ProxiesType { tab, list }
|
||||
|
||||
enum ProxiesLayout { loose, standard, tight }
|
||||
|
||||
enum ProxyCardType { expand, shrink, min }
|
||||
|
||||
|
||||
enum DnsMode {
|
||||
normal,
|
||||
@JsonValue("fake-ip")
|
||||
fakeIp,
|
||||
@JsonValue("redir-host")
|
||||
redirHost,
|
||||
hosts
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class AboutFragment extends StatelessWidget {
|
||||
title: const Text("Telegram"),
|
||||
onTap: () {
|
||||
globalState.openUrl(
|
||||
"https://t.me/+G-veVtwBOl4wODc1",
|
||||
"https://t.me/FlClash",
|
||||
);
|
||||
},
|
||||
trailing: const Icon(Icons.launch),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
@@ -6,15 +7,9 @@ import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
extension AccessControlExtension on AccessControl {
|
||||
List<String> get currentList => switch (mode) {
|
||||
AccessControlMode.acceptSelected => acceptList,
|
||||
AccessControlMode.rejectSelected => rejectList,
|
||||
};
|
||||
}
|
||||
|
||||
class AccessFragment extends StatefulWidget {
|
||||
const AccessFragment({super.key});
|
||||
|
||||
@@ -23,9 +18,13 @@ class AccessFragment extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AccessFragmentState extends State<AccessFragment> {
|
||||
List<String> acceptList = [];
|
||||
List<String> rejectList = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateInitList();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final appState = globalState.appController.appState;
|
||||
if (appState.packages.isEmpty) {
|
||||
@@ -36,297 +35,83 @@ class _AccessFragmentState extends State<AccessFragment> {
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildAppProxyModePopup() {
|
||||
final items = [
|
||||
CommonPopupMenuItem(
|
||||
action: AccessControlMode.rejectSelected,
|
||||
label: appLocalizations.blacklistMode,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: AccessControlMode.acceptSelected,
|
||||
label: appLocalizations.whitelistMode,
|
||||
),
|
||||
];
|
||||
return Selector<Config, AccessControlMode>(
|
||||
selector: (_, config) => config.accessControl.mode,
|
||||
builder: (context, mode, __) {
|
||||
return CommonPopupMenu<AccessControlMode>.radio(
|
||||
icon: Icon(
|
||||
Icons.mode_standby,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
items: items,
|
||||
onSelected: (value) {
|
||||
final config = context.read<Config>();
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
mode: value,
|
||||
);
|
||||
},
|
||||
selectedValue: mode,
|
||||
);
|
||||
},
|
||||
);
|
||||
_updateInitList() {
|
||||
final accessControl = globalState.appController.config.accessControl;
|
||||
acceptList = accessControl.acceptList;
|
||||
rejectList = accessControl.rejectList;
|
||||
}
|
||||
|
||||
Widget _buildFilterSystemAppButton() {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.accessControl.isFilterSystemApp,
|
||||
builder: (context, isFilterSystemApp, __) {
|
||||
final tooltip = isFilterSystemApp
|
||||
? appLocalizations.cancelFilterSystemApp
|
||||
: appLocalizations.filterSystemApp;
|
||||
return IconButton(
|
||||
tooltip: tooltip,
|
||||
onPressed: () {
|
||||
final config = context.read<Config>();
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
isFilterSystemApp: !isFilterSystemApp,
|
||||
);
|
||||
},
|
||||
icon: isFilterSystemApp
|
||||
? const Icon(Icons.filter_list_off)
|
||||
: const Icon(Icons.filter_list),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearchButton(List<Package> packages) {
|
||||
Widget _buildSearchButton() {
|
||||
return IconButton(
|
||||
tooltip: appLocalizations.search,
|
||||
onPressed: () {
|
||||
showSearch(
|
||||
context: context,
|
||||
delegate: AccessControlSearchDelegate(
|
||||
packages: packages,
|
||||
acceptList: acceptList,
|
||||
rejectList: rejectList,
|
||||
),
|
||||
).then((_) => {setState(() {})});
|
||||
).then((_) => setState(() {
|
||||
_updateInitList();
|
||||
}));
|
||||
},
|
||||
icon: const Icon(Icons.search),
|
||||
);
|
||||
}
|
||||
|
||||
// Widget _buildSelectedAllButton({
|
||||
// required bool isAccessControl,
|
||||
// required bool isSelectedAll,
|
||||
// required List<String> allValueList,
|
||||
// }) {
|
||||
// final tooltip = isSelectedAll
|
||||
// ? appLocalizations.cancelSelectAll
|
||||
// : appLocalizations.selectAll;
|
||||
// return AbsorbPointer(
|
||||
// absorbing: !isAccessControl,
|
||||
// child: FloatingActionButton(
|
||||
// tooltip: tooltip,
|
||||
// onPressed: () {
|
||||
// final config = globalState.appController.config;
|
||||
// final isAccept =
|
||||
// config.accessControl.mode == AccessControlMode.acceptSelected;
|
||||
//
|
||||
// if (isSelectedAll) {
|
||||
// config.accessControl = switch (isAccept) {
|
||||
// true => config.accessControl.copyWith(
|
||||
// acceptList: [],
|
||||
// ),
|
||||
// false => config.accessControl.copyWith(
|
||||
// rejectList: [],
|
||||
// ),
|
||||
// };
|
||||
// } else {
|
||||
// config.accessControl = switch (isAccept) {
|
||||
// true => config.accessControl.copyWith(
|
||||
// acceptList: allValueList,
|
||||
// ),
|
||||
// false => config.accessControl.copyWith(
|
||||
// rejectList: allValueList,
|
||||
// ),
|
||||
// };
|
||||
// }
|
||||
// },
|
||||
// child: isSelectedAll
|
||||
// ? const Icon(Icons.deselect)
|
||||
// : const Icon(Icons.select_all),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget _buildPackageList() {
|
||||
return Selector<AppState, List<Package>>(
|
||||
selector: (_, appState) => appState.packages,
|
||||
builder: (_, packages, ___) {
|
||||
final accessControl = globalState.appController.config.accessControl;
|
||||
final acceptList = accessControl.acceptList;
|
||||
final rejectList = accessControl.rejectList;
|
||||
final acceptPackages = packages.sorted((a, b) {
|
||||
final isSelectA = acceptList.contains(a.packageName);
|
||||
final isSelectB = acceptList.contains(b.packageName);
|
||||
if (isSelectA && isSelectB) return 0;
|
||||
if (isSelectA) return -1;
|
||||
if (isSelectB) return 1;
|
||||
return 0;
|
||||
});
|
||||
final rejectPackages = packages.sorted((a, b) {
|
||||
final isSelectA = rejectList.contains(a.packageName);
|
||||
final isSelectB = rejectList.contains(b.packageName);
|
||||
if (isSelectA && isSelectB) return 0;
|
||||
if (isSelectA) return -1;
|
||||
if (isSelectB) return 1;
|
||||
return 0;
|
||||
});
|
||||
return Selector<Config, PackageListSelectorState>(
|
||||
selector: (_, config) => PackageListSelectorState(
|
||||
accessControl: config.accessControl,
|
||||
isAccessControl: config.isAccessControl,
|
||||
),
|
||||
builder: (context, state, __) {
|
||||
final accessControl = state.accessControl;
|
||||
final isAccessControl = state.isAccessControl;
|
||||
final isFilterSystemApp = accessControl.isFilterSystemApp;
|
||||
final accessControlMode = accessControl.mode;
|
||||
final packages =
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? acceptPackages
|
||||
: rejectPackages;
|
||||
final currentList = accessControl.currentList;
|
||||
final currentPackages = isFilterSystemApp
|
||||
? packages
|
||||
.where((element) => element.isSystem == false)
|
||||
.toList()
|
||||
: packages;
|
||||
final packageNameList =
|
||||
currentPackages.map((e) => e.packageName).toList();
|
||||
final valueList = currentList.intersection(packageNameList);
|
||||
final describe =
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? appLocalizations.accessControlAllowDesc
|
||||
: appLocalizations.accessControlNotAllowDesc;
|
||||
return DisabledMask(
|
||||
status: !isAccessControl,
|
||||
child: Column(
|
||||
children: [
|
||||
AbsorbPointer(
|
||||
absorbing: !isAccessControl,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: 16,
|
||||
right: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
appLocalizations.selected,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Flexible(
|
||||
child: SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
"${valueList.length}",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(describe),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(
|
||||
child: _buildSearchButton(currentPackages)),
|
||||
Flexible(child: _buildFilterSystemAppButton()),
|
||||
Flexible(child: _buildAppProxyModePopup()),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: currentPackages.isEmpty
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: currentPackages.length,
|
||||
itemBuilder: (_, index) {
|
||||
final package = currentPackages[index];
|
||||
return PackageListItem(
|
||||
key: Key(package.packageName),
|
||||
package: package,
|
||||
value: valueList.contains(package.packageName),
|
||||
isActive: isAccessControl,
|
||||
onChanged: (value) {
|
||||
if (value == true) {
|
||||
valueList.add(package.packageName);
|
||||
} else {
|
||||
valueList.remove(package.packageName);
|
||||
}
|
||||
final config =
|
||||
globalState.appController.config;
|
||||
if (accessControlMode ==
|
||||
AccessControlMode.acceptSelected) {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
acceptList: valueList,
|
||||
);
|
||||
} else {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
rejectList: valueList,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
Widget _buildSelectedAllButton({
|
||||
required bool isSelectedAll,
|
||||
required List<String> allValueList,
|
||||
}) {
|
||||
final tooltip = isSelectedAll
|
||||
? appLocalizations.cancelSelectAll
|
||||
: appLocalizations.selectAll;
|
||||
return IconButton(
|
||||
tooltip: tooltip,
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
final isAccept =
|
||||
config.accessControl.mode == AccessControlMode.acceptSelected;
|
||||
if (isSelectedAll) {
|
||||
config.accessControl = switch (isAccept) {
|
||||
true => config.accessControl.copyWith(
|
||||
acceptList: [],
|
||||
),
|
||||
false => config.accessControl.copyWith(
|
||||
rejectList: [],
|
||||
),
|
||||
};
|
||||
} else {
|
||||
config.accessControl = switch (isAccept) {
|
||||
true => config.accessControl.copyWith(
|
||||
acceptList: allValueList,
|
||||
),
|
||||
false => config.accessControl.copyWith(
|
||||
rejectList: allValueList,
|
||||
),
|
||||
};
|
||||
}
|
||||
},
|
||||
icon: isSelectedAll
|
||||
? const Icon(Icons.deselect)
|
||||
: const Icon(Icons.select_all),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSettingButton() {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
showSheet(
|
||||
title: appLocalizations.proxiesSetting,
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return AccessControlWidget(
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.tune),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -363,7 +148,170 @@ class _AccessFragmentState extends State<AccessFragment> {
|
||||
],
|
||||
);
|
||||
},
|
||||
child: _buildPackageList(),
|
||||
child: Selector<AppState, List<Package>>(
|
||||
selector: (_, appState) => appState.packages,
|
||||
builder: (_, packages, ___) {
|
||||
return Selector2<AppState, Config, PackageListSelectorState>(
|
||||
selector: (_, appState, config) => PackageListSelectorState(
|
||||
accessControl: config.accessControl,
|
||||
isAccessControl: config.isAccessControl,
|
||||
packages: appState.packages,
|
||||
),
|
||||
builder: (context, state, __) {
|
||||
final accessControl = state.accessControl;
|
||||
final isAccessControl = state.isAccessControl;
|
||||
final accessControlMode = accessControl.mode;
|
||||
final packages = state.getList(
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? acceptList
|
||||
: rejectList,
|
||||
);
|
||||
final currentList = accessControl.currentList;
|
||||
final packageNameList =
|
||||
packages.map((e) => e.packageName).toList();
|
||||
final valueList = currentList.intersection(packageNameList);
|
||||
final describe =
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? appLocalizations.accessControlAllowDesc
|
||||
: appLocalizations.accessControlNotAllowDesc;
|
||||
return DisabledMask(
|
||||
status: !isAccessControl,
|
||||
child: Column(
|
||||
children: [
|
||||
AbsorbPointer(
|
||||
absorbing: !isAccessControl,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: 16,
|
||||
right: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
appLocalizations.selected,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Flexible(
|
||||
child: SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
"${valueList.length}",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(describe),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(
|
||||
child: _buildSearchButton(),
|
||||
),
|
||||
Flexible(
|
||||
child: _buildSelectedAllButton(
|
||||
isSelectedAll: valueList.length ==
|
||||
packageNameList.length,
|
||||
allValueList: packageNameList,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: _buildSettingButton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: packages.isEmpty
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: packages.length,
|
||||
itemBuilder: (_, index) {
|
||||
final package = packages[index];
|
||||
return PackageListItem(
|
||||
key: Key(package.packageName),
|
||||
package: package,
|
||||
value:
|
||||
valueList.contains(package.packageName),
|
||||
isActive: isAccessControl,
|
||||
onChanged: (value) {
|
||||
if (value == true) {
|
||||
valueList.add(package.packageName);
|
||||
} else {
|
||||
valueList.remove(package.packageName);
|
||||
}
|
||||
final config =
|
||||
globalState.appController.config;
|
||||
if (accessControlMode ==
|
||||
AccessControlMode.acceptSelected) {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
acceptList: valueList,
|
||||
);
|
||||
} else {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
rejectList: valueList,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -430,23 +378,14 @@ class PackageListItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
class AccessControlSearchDelegate extends SearchDelegate {
|
||||
final List<Package> packages;
|
||||
List<String> acceptList = [];
|
||||
List<String> rejectList = [];
|
||||
|
||||
AccessControlSearchDelegate({
|
||||
required this.packages,
|
||||
required this.acceptList,
|
||||
required this.rejectList,
|
||||
});
|
||||
|
||||
List<Package> get _results {
|
||||
final lowQuery = query.toLowerCase();
|
||||
return packages
|
||||
.where(
|
||||
(package) =>
|
||||
package.label.toLowerCase().contains(lowQuery) ||
|
||||
package.packageName.contains(lowQuery),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Widget>? buildActions(BuildContext context) {
|
||||
return [
|
||||
@@ -476,26 +415,39 @@ class AccessControlSearchDelegate extends SearchDelegate {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _packageList(List<Package> packages) {
|
||||
return Selector<Config, PackageListSelectorState>(
|
||||
selector: (_, config) => PackageListSelectorState(
|
||||
Widget _packageList() {
|
||||
final lowQuery = query.toLowerCase();
|
||||
return Selector2<AppState, Config, PackageListSelectorState>(
|
||||
selector: (_, appState, config) => PackageListSelectorState(
|
||||
packages: appState.packages,
|
||||
accessControl: config.accessControl,
|
||||
isAccessControl: config.isAccessControl,
|
||||
),
|
||||
builder: (context, state, __) {
|
||||
final accessControl = state.accessControl;
|
||||
final isAccessControl = state.isAccessControl;
|
||||
final accessControlMode = accessControl.mode;
|
||||
final packages = state.getList(
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? acceptList
|
||||
: rejectList,
|
||||
);
|
||||
final queryPackages = packages
|
||||
.where(
|
||||
(package) =>
|
||||
package.label.toLowerCase().contains(lowQuery) ||
|
||||
package.packageName.contains(lowQuery),
|
||||
)
|
||||
.toList();
|
||||
final isAccessControl = state.isAccessControl;
|
||||
final currentList = accessControl.currentList;
|
||||
final packageNameList =
|
||||
this.packages.map((e) => e.packageName).toList();
|
||||
final packageNameList = packages.map((e) => e.packageName).toList();
|
||||
final valueList = currentList.intersection(packageNameList);
|
||||
return DisabledMask(
|
||||
status: !isAccessControl,
|
||||
child: ListView.builder(
|
||||
itemCount: packages.length,
|
||||
itemCount: queryPackages.length,
|
||||
itemBuilder: (_, index) {
|
||||
final package = packages[index];
|
||||
final package = queryPackages[index];
|
||||
return PackageListItem(
|
||||
key: Key(package.packageName),
|
||||
package: package,
|
||||
@@ -533,6 +485,268 @@ class AccessControlSearchDelegate extends SearchDelegate {
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return _packageList(_results);
|
||||
return _packageList();
|
||||
}
|
||||
}
|
||||
|
||||
class AccessControlWidget extends StatelessWidget {
|
||||
final BuildContext context;
|
||||
|
||||
const AccessControlWidget({
|
||||
super.key,
|
||||
required this.context,
|
||||
});
|
||||
|
||||
IconData _getIconWithAccessControlMode(AccessControlMode mode) {
|
||||
return switch (mode) {
|
||||
AccessControlMode.acceptSelected => Icons.adjust_outlined,
|
||||
AccessControlMode.rejectSelected => Icons.block_outlined,
|
||||
};
|
||||
}
|
||||
|
||||
String _getTextWithAccessControlMode(AccessControlMode mode) {
|
||||
return switch (mode) {
|
||||
AccessControlMode.acceptSelected => appLocalizations.whitelistMode,
|
||||
AccessControlMode.rejectSelected => appLocalizations.blacklistMode,
|
||||
};
|
||||
}
|
||||
|
||||
String _getTextWithAccessSortType(AccessSortType type) {
|
||||
return switch (type) {
|
||||
AccessSortType.none => appLocalizations.defaultText,
|
||||
AccessSortType.name => appLocalizations.name,
|
||||
AccessSortType.time => appLocalizations.time,
|
||||
};
|
||||
}
|
||||
|
||||
IconData _getIconWithProxiesSortType(AccessSortType type) {
|
||||
return switch (type) {
|
||||
AccessSortType.none => Icons.sort,
|
||||
AccessSortType.name => Icons.sort_by_alpha,
|
||||
AccessSortType.time => Icons.timeline,
|
||||
};
|
||||
}
|
||||
|
||||
String _getTextWithIsFilterSystemApp(bool isFilterSystemApp) {
|
||||
return switch (isFilterSystemApp) {
|
||||
true => appLocalizations.onlyOtherApps,
|
||||
false => appLocalizations.allApps,
|
||||
};
|
||||
}
|
||||
|
||||
List<Widget> _buildModeSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.mode,
|
||||
items: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Selector<Config, AccessControlMode>(
|
||||
selector: (_, config) => config.accessControl.mode,
|
||||
builder: (_, accessControlMode, __) {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final item in AccessControlMode.values)
|
||||
SettingInfoCard(
|
||||
Info(
|
||||
label: _getTextWithAccessControlMode(item),
|
||||
iconData: _getIconWithAccessControlMode(item),
|
||||
),
|
||||
isSelected: accessControlMode == item,
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
mode: item,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildSortSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.sort,
|
||||
items: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Selector<Config, AccessSortType>(
|
||||
selector: (_, config) => config.accessControl.sort,
|
||||
builder: (_, accessSortType, __) {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final item in AccessSortType.values)
|
||||
SettingInfoCard(
|
||||
Info(
|
||||
label: _getTextWithAccessSortType(item),
|
||||
iconData: _getIconWithProxiesSortType(item),
|
||||
),
|
||||
isSelected: accessSortType == item,
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
sort: item,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildSourceSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.source,
|
||||
items: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Selector<Config, bool>(
|
||||
selector: (_, config) => config.accessControl.isFilterSystemApp,
|
||||
builder: (_, isFilterSystemApp, __) {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final item in [false, true])
|
||||
SettingTextCard(
|
||||
_getTextWithIsFilterSystemApp(item),
|
||||
isSelected: isFilterSystemApp == item,
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
isFilterSystemApp: item,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_intelligentSelected() async {
|
||||
final appState = globalState.appController.appState;
|
||||
final config = globalState.appController.config;
|
||||
final accessControl = config.accessControl;
|
||||
final packageNames = appState.packages
|
||||
.where(
|
||||
(item) =>
|
||||
accessControl.isFilterSystemApp ? item.isSystem == false : true,
|
||||
)
|
||||
.map((item) => item.packageName);
|
||||
Navigator.of(context).pop();
|
||||
final commonScaffoldState = context.commonScaffoldState;
|
||||
if (commonScaffoldState?.mounted != true) return;
|
||||
final selectedPackageNames =
|
||||
(await commonScaffoldState?.loadingRun<List<String>>(
|
||||
() async {
|
||||
return await app?.getChinaPackageNames() ?? [];
|
||||
},
|
||||
))
|
||||
?.toSet() ??
|
||||
{};
|
||||
final acceptList = packageNames
|
||||
.where((item) => !selectedPackageNames.contains(item))
|
||||
.toList();
|
||||
final rejectList = packageNames
|
||||
.where((item) => selectedPackageNames.contains(item))
|
||||
.toList();
|
||||
config.accessControl = accessControl.copyWith(
|
||||
acceptList: acceptList,
|
||||
rejectList: rejectList,
|
||||
);
|
||||
}
|
||||
|
||||
_copyToClipboard() async {
|
||||
await globalState.safeRun(() {
|
||||
final data = globalState.appController.config.accessControl.toJson();
|
||||
Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: json.encode(data),
|
||||
),
|
||||
);
|
||||
});
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
_pasteToClipboard() async {
|
||||
await globalState.safeRun(() async {
|
||||
final config = globalState.appController.config;
|
||||
final data = await Clipboard.getData('text/plain');
|
||||
final text = data?.text;
|
||||
if (text == null) return;
|
||||
config.accessControl = AccessControl.fromJson(
|
||||
json.decode(text),
|
||||
);
|
||||
});
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
List<Widget> _buildActionSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.action,
|
||||
items: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
spacing: 16,
|
||||
children: [
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.auto_awesome),
|
||||
label: appLocalizations.intelligentSelected,
|
||||
onPressed: _intelligentSelected,
|
||||
),
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.paste),
|
||||
label: appLocalizations.clipboardImport,
|
||||
onPressed: _pasteToClipboard,
|
||||
),
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.content_copy),
|
||||
label: appLocalizations.clipboardExport,
|
||||
onPressed: _copyToClipboard,
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
..._buildModeSetting(),
|
||||
..._buildSortSetting(),
|
||||
..._buildSourceSetting(),
|
||||
..._buildActionSetting(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
@@ -7,7 +6,6 @@ import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/config.dart';
|
||||
import 'package:fl_clash/models/dav.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/card.dart';
|
||||
import 'package:fl_clash/widgets/fade_box.dart';
|
||||
import 'package:fl_clash/widgets/list.dart';
|
||||
import 'package:fl_clash/widgets/text.dart';
|
||||
@@ -75,10 +73,11 @@ class BackupAndRecovery extends StatelessWidget {
|
||||
final res = await commonScaffoldState?.loadingRun<bool>(
|
||||
() async {
|
||||
final backupData = await globalState.appController.backupData();
|
||||
await picker.saveFile(
|
||||
final value = await picker.saveFile(
|
||||
other.getBackupFileName(),
|
||||
Uint8List.fromList(backupData),
|
||||
);
|
||||
if (value == null) return false;
|
||||
return true;
|
||||
},
|
||||
title: appLocalizations.backup,
|
||||
@@ -205,6 +204,24 @@ class BackupAndRecovery extends StatelessWidget {
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
ListItem.input(
|
||||
title: Text(appLocalizations.file),
|
||||
subtitle: Text(dav.fileName),
|
||||
delegate: InputDelegate(
|
||||
title: appLocalizations.file,
|
||||
value: dav.fileName,
|
||||
resetValue: defaultDavFileName,
|
||||
onChanged: (String? value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
globalState.appController.config.dav =
|
||||
globalState.appController.config.dav?.copyWith(
|
||||
fileName: value,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_backupOnWebDAV(context, client);
|
||||
|
||||
@@ -1,694 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
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:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ConfigFragment extends StatefulWidget {
|
||||
const ConfigFragment({super.key});
|
||||
|
||||
@override
|
||||
State<ConfigFragment> createState() => _ConfigFragmentState();
|
||||
}
|
||||
|
||||
class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
_modifyMixedPort(num mixedPort) async {
|
||||
final port = await globalState.showCommonDialog(
|
||||
child: MixedPortFormDialog(
|
||||
mixedPort: mixedPort,
|
||||
),
|
||||
);
|
||||
if (port != null && port != mixedPort && mounted) {
|
||||
try {
|
||||
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,
|
||||
message: TextSpan(
|
||||
text: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_showLogLevelDialog(LogLevel value) {
|
||||
globalState.showCommonDialog(
|
||||
child: AlertDialog(
|
||||
title: Text(appLocalizations.logLevel),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 16,
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 250,
|
||||
child: Wrap(
|
||||
children: [
|
||||
for (final logLevel in LogLevel.values)
|
||||
ListItem.radio(
|
||||
delegate: RadioDelegate<LogLevel>(
|
||||
value: logLevel,
|
||||
groupValue: value,
|
||||
onChanged: (LogLevel? value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.logLevel = value;
|
||||
appController.updateClashConfigDebounce();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: Text(logLevel.name),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_showUaDialog(String? value) {
|
||||
const uas = [
|
||||
null,
|
||||
"clash-verge/v1.6.6",
|
||||
"ClashforWindows/0.19.23",
|
||||
];
|
||||
globalState.showCommonDialog(
|
||||
child: AlertDialog(
|
||||
title: const Text("UA"),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 16,
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 250,
|
||||
child: Wrap(
|
||||
children: [
|
||||
for (final ua in uas)
|
||||
ListItem.radio(
|
||||
delegate: RadioDelegate<String?>(
|
||||
value: ua,
|
||||
groupValue: value,
|
||||
onChanged: (String? value) {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.globalRealUa = value;
|
||||
appController.updateClashConfigDebounce();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: Text(ua ?? appLocalizations.defaultText),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_modifyTestUrl(String testUrl) async {
|
||||
final newTestUrl = await globalState.showCommonDialog<String>(
|
||||
child: TestUrlFormDialog(
|
||||
testUrl: testUrl,
|
||||
),
|
||||
);
|
||||
if (newTestUrl != null && newTestUrl != testUrl && mounted) {
|
||||
try {
|
||||
if (!newTestUrl.isUrl) {
|
||||
throw "Invalid url";
|
||||
}
|
||||
globalState.appController.config.testUrl = newTestUrl;
|
||||
globalState.appController.updateClashConfigDebounce();
|
||||
} catch (e) {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.testUrl,
|
||||
message: TextSpan(
|
||||
text: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_updateKeepAliveInterval(int keepAliveInterval) async {
|
||||
final newKeepAliveIntervalString =
|
||||
await globalState.showCommonDialog<String>(
|
||||
child: KeepAliveIntervalFormDialog(
|
||||
keepAliveInterval: keepAliveInterval,
|
||||
),
|
||||
);
|
||||
if (newKeepAliveIntervalString != null &&
|
||||
newKeepAliveIntervalString != "$keepAliveInterval" &&
|
||||
mounted) {
|
||||
try {
|
||||
final newKeepAliveInterval = int.parse(newKeepAliveIntervalString);
|
||||
if (newKeepAliveInterval <= 0) {
|
||||
throw "Invalid keepAliveInterval";
|
||||
}
|
||||
globalState.appController.clashConfig.keepAliveInterval =
|
||||
newKeepAliveInterval;
|
||||
globalState.appController.updateClashConfigDebounce();
|
||||
} catch (e) {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.testUrl,
|
||||
message: TextSpan(
|
||||
text: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildAppSection() {
|
||||
return generateSection(
|
||||
title: appLocalizations.app,
|
||||
items: [
|
||||
if (Platform.isAndroid) ...[
|
||||
Selector<Config, bool>(
|
||||
selector: (_, config) => config.allowBypass,
|
||||
builder: (_, allowBypass, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.arrow_forward_outlined),
|
||||
title: Text(appLocalizations.allowBypass),
|
||||
subtitle: Text(appLocalizations.allowBypassDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: allowBypass,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.config.allowBypass = value;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<Config, bool>(
|
||||
selector: (_, config) => config.systemProxy,
|
||||
builder: (_, systemProxy, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.settings_ethernet),
|
||||
title: Text(appLocalizations.systemProxy),
|
||||
subtitle: Text(appLocalizations.systemProxyDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: systemProxy,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.config.systemProxy = value;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
Selector<Config, bool>(
|
||||
selector: (_, config) => config.isCloseConnections,
|
||||
builder: (_, isCloseConnections, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.auto_delete_outlined),
|
||||
title: Text(appLocalizations.autoCloseConnections),
|
||||
subtitle: Text(appLocalizations.autoCloseConnectionsDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: isCloseConnections,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.config.isCloseConnections = value;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<Config, bool>(
|
||||
selector: (_, config) => config.onlyProxy,
|
||||
builder: (_, onlyProxy, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.data_usage_outlined),
|
||||
title: Text(appLocalizations.onlyStatisticsProxy),
|
||||
subtitle: Text(appLocalizations.onlyStatisticsProxyDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: onlyProxy,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.config.onlyProxy = value;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// Selector<Config, bool>(
|
||||
// selector: (_, config) => config.isCompatible,
|
||||
// builder: (_, isCompatible, __) {
|
||||
// return ListItem.switchItem(
|
||||
// leading: const Icon(Icons.expand_outlined),
|
||||
// title: Text(appLocalizations.compatible),
|
||||
// subtitle: Text(appLocalizations.compatibleDesc),
|
||||
// delegate: SwitchDelegate(
|
||||
// value: isCompatible,
|
||||
// onChanged: (bool value) async {
|
||||
// final appController = globalState.appController;
|
||||
// appController.config.isCompatible = value;
|
||||
// await appController.applyProfile();
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildGeneralSection() {
|
||||
return generateSection(
|
||||
title: appLocalizations.general,
|
||||
items: [
|
||||
Selector<ClashConfig, LogLevel>(
|
||||
selector: (_, clashConfig) => clashConfig.logLevel,
|
||||
builder: (_, value, __) {
|
||||
return ListItem(
|
||||
leading: const Icon(Icons.info_outline),
|
||||
title: Text(appLocalizations.logLevel),
|
||||
subtitle: Text(value.name),
|
||||
onTap: () {
|
||||
_showLogLevelDialog(value);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<ClashConfig, String?>(
|
||||
selector: (_, clashConfig) => clashConfig.globalRealUa,
|
||||
builder: (_, value, __) {
|
||||
return ListItem(
|
||||
leading: const Icon(Icons.computer_outlined),
|
||||
title: const Text("UA"),
|
||||
subtitle: Text(value ?? appLocalizations.defaultText),
|
||||
onTap: () {
|
||||
_showUaDialog(value);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<ClashConfig, int>(
|
||||
selector: (_, config) => config.keepAliveInterval,
|
||||
builder: (_, value, __) {
|
||||
return ListItem(
|
||||
leading: const Icon(Icons.timer_outlined),
|
||||
title: Text(appLocalizations.keepAliveIntervalDesc),
|
||||
subtitle: Text("$value ${appLocalizations.seconds}"),
|
||||
onTap: () {
|
||||
_updateKeepAliveInterval(value);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<Config, String>(
|
||||
selector: (_, config) => config.testUrl,
|
||||
builder: (_, value, __) {
|
||||
return ListItem(
|
||||
leading: const Icon(Icons.timeline),
|
||||
title: Text(appLocalizations.testUrl),
|
||||
subtitle: Text(value),
|
||||
onTap: () {
|
||||
_modifyTestUrl(value);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<ClashConfig, int>(
|
||||
selector: (_, clashConfig) => clashConfig.mixedPort,
|
||||
builder: (_, mixedPort, __) {
|
||||
return ListItem(
|
||||
onTap: () {
|
||||
_modifyMixedPort(mixedPort);
|
||||
},
|
||||
leading: const Icon(Icons.adjust_outlined),
|
||||
title: Text(appLocalizations.proxyPort),
|
||||
subtitle: Text(appLocalizations.proxyPortDesc),
|
||||
trailing: FilledButton.tonal(
|
||||
onPressed: () {
|
||||
_modifyMixedPort(mixedPort);
|
||||
},
|
||||
child: Text(
|
||||
"$mixedPort",
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.ipv6,
|
||||
builder: (_, ipv6, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.water_outlined),
|
||||
title: const Text("IPv6"),
|
||||
subtitle: Text(appLocalizations.ipv6Desc),
|
||||
delegate: SwitchDelegate(
|
||||
value: ipv6,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.ipv6 = value;
|
||||
appController.updateClashConfigDebounce();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.allowLan,
|
||||
builder: (_, allowLan, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.device_hub),
|
||||
title: Text(appLocalizations.allowLan),
|
||||
subtitle: Text(appLocalizations.allowLanDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: allowLan,
|
||||
onChanged: (bool value) async {
|
||||
final clashConfig = context.read<ClashConfig>();
|
||||
clashConfig.allowLan = value;
|
||||
globalState.appController.updateClashConfigDebounce();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.unifiedDelay,
|
||||
builder: (_, unifiedDelay, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.compress_outlined),
|
||||
title: Text(appLocalizations.unifiedDelay),
|
||||
subtitle: Text(appLocalizations.unifiedDelayDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: unifiedDelay,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.unifiedDelay = value;
|
||||
appController.updateClashConfigDebounce();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) =>
|
||||
clashConfig.findProcessMode == FindProcessMode.always,
|
||||
builder: (_, findProcess, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.polymer_outlined),
|
||||
title: Text(appLocalizations.findProcessMode),
|
||||
subtitle: Text(appLocalizations.findProcessModeDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: findProcess,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.findProcessMode =
|
||||
value ? FindProcessMode.always : FindProcessMode.off;
|
||||
appController.updateClashConfigDebounce();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.tcpConcurrent,
|
||||
builder: (_, tcpConcurrent, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.double_arrow_outlined),
|
||||
title: Text(appLocalizations.tcpConcurrent),
|
||||
subtitle: Text(appLocalizations.tcpConcurrentDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: tcpConcurrent,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.tcpConcurrent = value;
|
||||
appController.updateClashConfigDebounce();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) =>
|
||||
clashConfig.geodataLoader == geodataLoaderMemconservative,
|
||||
builder: (_, memconservative, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.memory),
|
||||
title: Text(appLocalizations.geodataLoader),
|
||||
subtitle: Text(appLocalizations.geodataLoaderDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: memconservative,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.geodataLoader = value
|
||||
? geodataLoaderMemconservative
|
||||
: geodataLoaderStandard;
|
||||
appController.updateClashConfigDebounce();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) =>
|
||||
clashConfig.externalController.isNotEmpty,
|
||||
builder: (_, hasExternalController, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.api_outlined),
|
||||
title: Text(appLocalizations.externalController),
|
||||
subtitle: Text(appLocalizations.externalControllerDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: hasExternalController,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.externalController =
|
||||
value ? defaultExternalController : '';
|
||||
appController.updateClashConfigDebounce();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildMoreSection() {
|
||||
return generateSection(
|
||||
title: appLocalizations.more,
|
||||
items: [
|
||||
if (system.isDesktop)
|
||||
Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.tun.enable,
|
||||
builder: (_, tunEnable, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.important_devices_outlined),
|
||||
title: Text(appLocalizations.tun),
|
||||
subtitle: Text(appLocalizations.tunDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: tunEnable,
|
||||
onChanged: (bool value) async {
|
||||
final clashConfig = context.read<ClashConfig>();
|
||||
clashConfig.tun = Tun(enable: value);
|
||||
globalState.appController.updateClashConfigDebounce();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> items = [
|
||||
..._buildAppSection(),
|
||||
..._buildGeneralSection(),
|
||||
..._buildMoreSection(),
|
||||
];
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 32),
|
||||
itemBuilder: (_, index) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
child: items[index],
|
||||
);
|
||||
},
|
||||
itemCount: items.length,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MixedPortFormDialog extends StatefulWidget {
|
||||
final num mixedPort;
|
||||
|
||||
const MixedPortFormDialog({super.key, required this.mixedPort});
|
||||
|
||||
@override
|
||||
State<MixedPortFormDialog> createState() => _MixedPortFormDialogState();
|
||||
}
|
||||
|
||||
class _MixedPortFormDialogState extends State<MixedPortFormDialog> {
|
||||
late TextEditingController portController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
portController = TextEditingController(text: "${widget.mixedPort}");
|
||||
}
|
||||
|
||||
_handleUpdate() async {
|
||||
final port = portController.value.text;
|
||||
if (port.isEmpty) return;
|
||||
Navigator.of(context).pop<String>(port);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(appLocalizations.proxyPort),
|
||||
content: SizedBox(
|
||||
width: 300,
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
TextField(
|
||||
controller: portController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _handleUpdate,
|
||||
child: Text(appLocalizations.submit),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TestUrlFormDialog extends StatefulWidget {
|
||||
final String testUrl;
|
||||
|
||||
const TestUrlFormDialog({
|
||||
super.key,
|
||||
required this.testUrl,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TestUrlFormDialog> createState() => _TestUrlFormDialogState();
|
||||
}
|
||||
|
||||
class _TestUrlFormDialogState extends State<TestUrlFormDialog> {
|
||||
late TextEditingController testUrlController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
testUrlController = TextEditingController(text: widget.testUrl);
|
||||
}
|
||||
|
||||
_handleUpdate() async {
|
||||
final testUrl = testUrlController.value.text;
|
||||
if (testUrl.isEmpty) return;
|
||||
Navigator.of(context).pop<String>(testUrl);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(appLocalizations.testUrl),
|
||||
content: SizedBox(
|
||||
width: 300,
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
TextField(
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
controller: testUrlController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _handleUpdate,
|
||||
child: Text(appLocalizations.submit),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class KeepAliveIntervalFormDialog extends StatefulWidget {
|
||||
final int keepAliveInterval;
|
||||
|
||||
const KeepAliveIntervalFormDialog({
|
||||
super.key,
|
||||
required this.keepAliveInterval,
|
||||
});
|
||||
|
||||
@override
|
||||
State<KeepAliveIntervalFormDialog> createState() =>
|
||||
_KeepAliveIntervalFormDialogState();
|
||||
}
|
||||
|
||||
class _KeepAliveIntervalFormDialogState
|
||||
extends State<KeepAliveIntervalFormDialog> {
|
||||
late TextEditingController keepAliveIntervalController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
keepAliveIntervalController =
|
||||
TextEditingController(text: "${widget.keepAliveInterval}");
|
||||
}
|
||||
|
||||
_handleUpdate() async {
|
||||
final keepAliveInterval = keepAliveIntervalController.value.text;
|
||||
if (keepAliveInterval.isEmpty) return;
|
||||
Navigator.of(context).pop<String>(keepAliveInterval);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(appLocalizations.keepAliveIntervalDesc),
|
||||
content: SizedBox(
|
||||
width: 300,
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
TextField(
|
||||
maxLines: 1,
|
||||
minLines: 1,
|
||||
controller: keepAliveIntervalController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
suffixText: appLocalizations.seconds,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _handleUpdate,
|
||||
child: Text(appLocalizations.submit),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
89
lib/fragments/config/app.dart
Normal file
89
lib/fragments/config/app.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/config.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' show dirname, join;
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class CloseConnectionsSwitch extends StatelessWidget {
|
||||
const CloseConnectionsSwitch({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.isCloseConnections,
|
||||
builder: (_, isCloseConnections, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.auto_delete_outlined),
|
||||
title: Text(appLocalizations.autoCloseConnections),
|
||||
subtitle: Text(appLocalizations.autoCloseConnectionsDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: isCloseConnections,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.config.isCloseConnections = value;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UsageSwitch extends StatelessWidget {
|
||||
const UsageSwitch({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.onlyProxy,
|
||||
builder: (_, onlyProxy, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.data_usage_outlined),
|
||||
title: Text(appLocalizations.onlyStatisticsProxy),
|
||||
subtitle: Text(appLocalizations.onlyStatisticsProxyDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: onlyProxy,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.config.onlyProxy = value;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UWPLoopbackUtil extends StatelessWidget {
|
||||
const UWPLoopbackUtil({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.onlyProxy,
|
||||
builder: (_, onlyProxy, __) {
|
||||
return ListItem(
|
||||
leading: const Icon(Icons.lock_open),
|
||||
title: Text(appLocalizations.loopback),
|
||||
subtitle: Text(appLocalizations.loopbackDesc),
|
||||
onTap: () {
|
||||
windows?.runas(
|
||||
'"${join(dirname(Platform.resolvedExecutable), "EnableLoopback.exe")}"',
|
||||
"",
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final appItems = [
|
||||
if (Platform.isWindows) const UWPLoopbackUtil(),
|
||||
const CloseConnectionsSwitch(),
|
||||
const UsageSwitch(),
|
||||
];
|
||||
91
lib/fragments/config/config.dart
Normal file
91
lib/fragments/config/config.dart
Normal file
@@ -0,0 +1,91 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/fragments/config/app.dart';
|
||||
import 'package:fl_clash/fragments/config/dns.dart';
|
||||
import 'package:fl_clash/fragments/config/general.dart';
|
||||
import 'package:fl_clash/fragments/config/vpn.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ConfigFragment extends StatefulWidget {
|
||||
const ConfigFragment({super.key});
|
||||
|
||||
@override
|
||||
State<ConfigFragment> createState() => _ConfigFragmentState();
|
||||
}
|
||||
|
||||
class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> items = [
|
||||
ListItem.open(
|
||||
title: Text(appLocalizations.app),
|
||||
subtitle: Text(appLocalizations.appDesc),
|
||||
leading: const Icon(Icons.settings_applications),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.app,
|
||||
isBlur: false,
|
||||
widget: generateListView(
|
||||
appItems
|
||||
.separated(
|
||||
const Divider(
|
||||
height: 0,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (Platform.isAndroid)
|
||||
ListItem.open(
|
||||
title: const Text("VPN"),
|
||||
subtitle: Text(appLocalizations.vpnDesc),
|
||||
leading: const Icon(Icons.vpn_key),
|
||||
delegate: OpenDelegate(
|
||||
title: "VPN",
|
||||
isBlur: false,
|
||||
widget: generateListView(
|
||||
vpnItems,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListItem.open(
|
||||
title: Text(appLocalizations.general),
|
||||
subtitle: Text(appLocalizations.generalDesc),
|
||||
leading: const Icon(Icons.build),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.general,
|
||||
widget: generateListView(
|
||||
generalItems,
|
||||
),
|
||||
isBlur: false,
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
),
|
||||
ListItem.open(
|
||||
title: const Text("DNS"),
|
||||
subtitle: Text(appLocalizations.dnsDesc),
|
||||
leading: const Icon(Icons.dns),
|
||||
delegate: OpenDelegate(
|
||||
title: "DNS",
|
||||
widget: generateListView(
|
||||
dnsItems,
|
||||
),
|
||||
isScaffold: true,
|
||||
isBlur: false,
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
)
|
||||
];
|
||||
return generateListView(
|
||||
items
|
||||
.separated(
|
||||
const Divider(
|
||||
height: 0,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
854
lib/fragments/config/dns.dart
Normal file
854
lib/fragments/config/dns.dart
Normal file
@@ -0,0 +1,854 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_clash/common/app_localizations.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:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
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.appController.clashConfig.dns = const Dns();
|
||||
},
|
||||
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, __) {
|
||||
return ListItem.switchItem(
|
||||
title: Text(appLocalizations.overrideDns),
|
||||
subtitle: Text(appLocalizations.overrideDnsDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: override,
|
||||
onChanged: (bool value) async {
|
||||
final config = globalState.appController.config;
|
||||
config.overrideDns = value;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.enable,
|
||||
builder: (_, enable, __) {
|
||||
return ListItem.switchItem(
|
||||
title: Text(appLocalizations.status),
|
||||
subtitle: Text(appLocalizations.statusDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: enable,
|
||||
onChanged: (bool value) async {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
enable: value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PreferH3Item extends StatelessWidget {
|
||||
const PreferH3Item({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.preferH3,
|
||||
builder: (_, preferH3, __) {
|
||||
return ListItem.switchItem(
|
||||
title: const Text("PreferH3"),
|
||||
subtitle: Text(appLocalizations.preferH3Desc),
|
||||
delegate: SwitchDelegate(
|
||||
value: preferH3,
|
||||
onChanged: (bool value) async {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
preferH3: value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IPv6Item extends StatelessWidget {
|
||||
const IPv6Item({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.ipv6,
|
||||
builder: (_, ipv6, __) {
|
||||
return ListItem.switchItem(
|
||||
title: const Text("IPv6"),
|
||||
delegate: SwitchDelegate(
|
||||
value: ipv6,
|
||||
onChanged: (bool value) async {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
ipv6: value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RespectRulesItem extends StatelessWidget {
|
||||
const RespectRulesItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.respectRules,
|
||||
builder: (_, respectRules, __) {
|
||||
return ListItem.switchItem(
|
||||
title: Text(appLocalizations.respectRules),
|
||||
subtitle: Text(appLocalizations.respectRulesDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: respectRules,
|
||||
onChanged: (bool value) async {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
respectRules: value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DnsModeItem extends StatelessWidget {
|
||||
const DnsModeItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, DnsMode>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.enhancedMode,
|
||||
builder: (_, enhancedMode, __) {
|
||||
return ListItem<DnsMode>.options(
|
||||
title: Text(appLocalizations.dnsMode),
|
||||
subtitle: Text(enhancedMode.name),
|
||||
delegate: OptionsDelegate(
|
||||
title: appLocalizations.dnsMode,
|
||||
options: DnsMode.values,
|
||||
onChanged: (value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(enhancedMode: value);
|
||||
},
|
||||
textBuilder: (dnsMode) => dnsMode.name,
|
||||
value: enhancedMode,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FakeIpRangeItem extends StatelessWidget {
|
||||
const FakeIpRangeItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, String>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.fakeIpRange,
|
||||
builder: (_, fakeIpRange, __) {
|
||||
return ListItem.input(
|
||||
title: Text(appLocalizations.fakeipRange),
|
||||
subtitle: Text(fakeIpRange),
|
||||
delegate: InputDelegate(
|
||||
title: appLocalizations.fakeipRange,
|
||||
value: fakeIpRange,
|
||||
onChanged: (String? value) {
|
||||
if (value != null) {
|
||||
try {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
clashConfig.dns = clashConfig.dns.copyWith(
|
||||
fakeIpRange: value,
|
||||
);
|
||||
} catch (e) {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.fakeipRange,
|
||||
message: TextSpan(
|
||||
text: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FakeIpFilterItem extends StatelessWidget {
|
||||
const FakeIpFilterItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem.open(
|
||||
title: Text(appLocalizations.fakeipFilter),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
title: appLocalizations.fakeipFilter,
|
||||
widget: Selector<ClashConfig, List<String>>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.fakeIpFilter,
|
||||
shouldRebuild: (prev, next) =>
|
||||
!const ListEquality<String>().equals(prev, next),
|
||||
builder: (_, fakeIpFilter, __) {
|
||||
return UpdatePage(
|
||||
title: appLocalizations.fakeipFilter,
|
||||
items: fakeIpFilter,
|
||||
titleBuilder: (item) => Text(item),
|
||||
onRemove: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
fakeIpFilter: List.from(dns.fakeIpFilter)
|
||||
..remove(value),
|
||||
);
|
||||
},
|
||||
onAdd: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
if (fakeIpFilter.contains(value)) return;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
fakeIpFilter: List.from(dns.fakeIpFilter)
|
||||
..add(value),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultNameserverItem extends StatelessWidget {
|
||||
const DefaultNameserverItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem.open(
|
||||
title: Text(appLocalizations.defaultNameserver),
|
||||
subtitle: Text(appLocalizations.defaultNameserverDesc),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
title: appLocalizations.defaultNameserver,
|
||||
widget: Selector<ClashConfig, List<String>>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.defaultNameserver,
|
||||
shouldRebuild: (prev, next) =>
|
||||
!const ListEquality<String>().equals(prev, next),
|
||||
builder: (_, defaultNameserver, __) {
|
||||
return UpdatePage(
|
||||
title: appLocalizations.defaultNameserver,
|
||||
items: defaultNameserver,
|
||||
titleBuilder: (item) => Text(item),
|
||||
onRemove: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
defaultNameserver: List.from(dns.defaultNameserver)
|
||||
..remove(value),
|
||||
);
|
||||
},
|
||||
onAdd: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
if (defaultNameserver.contains(value)) return;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
defaultNameserver: List.from(dns.defaultNameserver)
|
||||
..add(value),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NameserverItem extends StatelessWidget {
|
||||
const NameserverItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem.open(
|
||||
title: Text(appLocalizations.nameserver),
|
||||
subtitle: Text(appLocalizations.nameserverDesc),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.nameserver,
|
||||
isBlur: false,
|
||||
widget: Selector<ClashConfig, List<String>>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.nameserver,
|
||||
shouldRebuild: (prev, next) =>
|
||||
!const ListEquality<String>().equals(prev, next),
|
||||
builder: (_, nameserver, __) {
|
||||
return UpdatePage(
|
||||
title: "域名服务器",
|
||||
items: nameserver,
|
||||
titleBuilder: (item) => Text(item),
|
||||
onRemove: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
nameserver: List.from(dns.nameserver)
|
||||
..remove(value),
|
||||
);
|
||||
},
|
||||
onAdd: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
if (nameserver.contains(value)) return;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
nameserver: List.from(dns.nameserver)
|
||||
..add(value),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UseHostsItem extends StatelessWidget {
|
||||
const UseHostsItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.useHosts,
|
||||
builder: (_, useHosts, __) {
|
||||
return ListItem.switchItem(
|
||||
title: Text(appLocalizations.useHosts),
|
||||
delegate: SwitchDelegate(
|
||||
value: useHosts,
|
||||
onChanged: (bool value) async {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
useHosts: value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UseSystemHostsItem extends StatelessWidget {
|
||||
const UseSystemHostsItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.useSystemHosts,
|
||||
builder: (_, useSystemHosts, __) {
|
||||
return ListItem.switchItem(
|
||||
title: Text(appLocalizations.useSystemHosts),
|
||||
delegate: SwitchDelegate(
|
||||
value: useSystemHosts,
|
||||
onChanged: (bool value) async {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
useSystemHosts: value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NameserverPolicyItem extends StatelessWidget {
|
||||
const NameserverPolicyItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem.open(
|
||||
title: Text(appLocalizations.nameserverPolicy),
|
||||
subtitle: Text(appLocalizations.nameserverPolicyDesc),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
title: appLocalizations.nameserverPolicy,
|
||||
widget: Selector<ClashConfig, Map<String, String>>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.nameserverPolicy,
|
||||
shouldRebuild: (prev, next) =>
|
||||
!const MapEquality<String, String>().equals(prev, next),
|
||||
builder: (_, nameserverPolicy, __) {
|
||||
return UpdatePage(
|
||||
title: appLocalizations.nameserverPolicy,
|
||||
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;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
nameserverPolicy: Map.from(dns.nameserverPolicy)
|
||||
..remove(value.key),
|
||||
);
|
||||
},
|
||||
onAdd: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
nameserverPolicy: Map.from(dns.nameserverPolicy)
|
||||
..addEntries([value]),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProxyServerNameserverItem extends StatelessWidget {
|
||||
const ProxyServerNameserverItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem.open(
|
||||
title: Text(appLocalizations.proxyNameserver),
|
||||
subtitle: Text(appLocalizations.proxyNameserverDesc),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
title: appLocalizations.proxyNameserver,
|
||||
widget: Selector<ClashConfig, List<String>>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.proxyServerNameserver,
|
||||
shouldRebuild: (prev, next) =>
|
||||
!const ListEquality<String>().equals(prev, next),
|
||||
builder: (_, proxyServerNameserver, __) {
|
||||
return UpdatePage(
|
||||
title: appLocalizations.proxyNameserver,
|
||||
items: proxyServerNameserver,
|
||||
titleBuilder: (item) => Text(item),
|
||||
onRemove: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
proxyServerNameserver: List.from(dns.proxyServerNameserver)
|
||||
..remove(value),
|
||||
);
|
||||
},
|
||||
onAdd: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
if (proxyServerNameserver.contains(value)) return;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
proxyServerNameserver: List.from(dns.proxyServerNameserver)
|
||||
..add(value),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FallbackItem extends StatelessWidget {
|
||||
const FallbackItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem.open(
|
||||
title: Text(appLocalizations.fallback),
|
||||
subtitle: Text(appLocalizations.fallbackDesc),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
title: appLocalizations.fallback,
|
||||
widget: Selector<ClashConfig, List<String>>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.fallback,
|
||||
shouldRebuild: (prev, next) =>
|
||||
!const ListEquality<String>().equals(prev, next),
|
||||
builder: (_, fallback, __) {
|
||||
return UpdatePage(
|
||||
title: appLocalizations.fallback,
|
||||
items: fallback,
|
||||
titleBuilder: (item) => Text(item),
|
||||
onRemove: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
fallback: List.from(dns.fallback)
|
||||
..remove(value),
|
||||
);
|
||||
},
|
||||
onAdd: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
if (fallback.contains(value)) return;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
fallback: List.from(dns.fallback)
|
||||
..add(value),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GeoipItem extends StatelessWidget {
|
||||
const GeoipItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.fallbackFilter.geoip,
|
||||
builder: (_, geoip, __) {
|
||||
return ListItem.switchItem(
|
||||
title: const Text("Geoip"),
|
||||
delegate: SwitchDelegate(
|
||||
value: geoip,
|
||||
onChanged: (bool value) async {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
fallbackFilter: dns.fallbackFilter.copyWith(geoip: value),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GeoipCodeItem extends StatelessWidget {
|
||||
const GeoipCodeItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, String>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.fallbackFilter.geoipCode,
|
||||
builder: (_, geoipCode, __) {
|
||||
return ListItem.input(
|
||||
title: Text(appLocalizations.geoipCode),
|
||||
subtitle: Text(geoipCode),
|
||||
delegate: InputDelegate(
|
||||
title: appLocalizations.geoipCode,
|
||||
value: geoipCode,
|
||||
onChanged: (String? value) {
|
||||
if (value != null) {
|
||||
try {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
fallbackFilter: dns.fallbackFilter.copyWith(
|
||||
geoipCode: value,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.geoipCode,
|
||||
message: TextSpan(
|
||||
text: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GeositeItem extends StatelessWidget {
|
||||
const GeositeItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem.open(
|
||||
title: const Text("Geosite"),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
title: "Geosite",
|
||||
widget: Selector<ClashConfig, List<String>>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.fallbackFilter.geosite,
|
||||
shouldRebuild: (prev, next) =>
|
||||
!const ListEquality<String>().equals(prev, next),
|
||||
builder: (_, geosite, __) {
|
||||
return UpdatePage(
|
||||
title: "Geosite",
|
||||
items: geosite,
|
||||
titleBuilder: (item) => Text(item),
|
||||
onRemove: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
fallbackFilter: dns.fallbackFilter.copyWith(
|
||||
geosite: List.from(geosite)
|
||||
..remove(value),
|
||||
),
|
||||
);
|
||||
},
|
||||
onAdd: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
fallbackFilter: dns.fallbackFilter.copyWith(
|
||||
geosite: List.from(geosite)
|
||||
..add(value),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IpcidrItem extends StatelessWidget {
|
||||
const IpcidrItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem.open(
|
||||
title: Text(appLocalizations.ipcidr),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
title: appLocalizations.ipcidr,
|
||||
widget: Selector<ClashConfig, List<String>>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.fallbackFilter.ipcidr,
|
||||
shouldRebuild: (prev, next) =>
|
||||
!const ListEquality<String>().equals(prev, next),
|
||||
builder: (_, ipcidr, __) {
|
||||
return UpdatePage(
|
||||
title: appLocalizations.ipcidr,
|
||||
items: ipcidr,
|
||||
titleBuilder: (item) => Text(item),
|
||||
onRemove: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
fallbackFilter: dns.fallbackFilter.copyWith(
|
||||
ipcidr: List.from(ipcidr)
|
||||
..remove(value),
|
||||
),
|
||||
);
|
||||
},
|
||||
onAdd: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
fallbackFilter: dns.fallbackFilter.copyWith(
|
||||
ipcidr: List.from(ipcidr)
|
||||
..add(value),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DomainItem extends StatelessWidget {
|
||||
const DomainItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem.open(
|
||||
title: Text(appLocalizations.domain),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
title: appLocalizations.domain,
|
||||
widget: Selector<ClashConfig, List<String>>(
|
||||
selector: (_, clashConfig) => clashConfig.dns.fallbackFilter.domain,
|
||||
shouldRebuild: (prev, next) =>
|
||||
!const ListEquality<String>().equals(prev, next),
|
||||
builder: (_, domain, __) {
|
||||
return UpdatePage(
|
||||
title: appLocalizations.domain,
|
||||
items: domain,
|
||||
titleBuilder: (item) => Text(item),
|
||||
onRemove: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
fallbackFilter: dns.fallbackFilter.copyWith(
|
||||
domain: List.from(domain)
|
||||
..remove(value),
|
||||
),
|
||||
);
|
||||
},
|
||||
onAdd: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
final dns = clashConfig.dns;
|
||||
clashConfig.dns = dns.copyWith(
|
||||
fallbackFilter: dns.fallbackFilter.copyWith(
|
||||
domain: List.from(domain)
|
||||
..add(value),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DnsOptions extends StatelessWidget {
|
||||
const DnsOptions({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FallbackFilterOptions extends StatelessWidget {
|
||||
const FallbackFilterOptions({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DnsDisabledContainer(
|
||||
Column(
|
||||
children: generateSection(
|
||||
title: appLocalizations.fallbackFilter,
|
||||
items: [
|
||||
const GeoipItem(),
|
||||
const GeoipCodeItem(),
|
||||
const GeositeItem(),
|
||||
const IpcidrItem(),
|
||||
const DomainItem(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const dnsItems = <Widget>[
|
||||
OverrideItem(),
|
||||
DnsOptions(),
|
||||
FallbackFilterOptions(),
|
||||
];
|
||||
439
lib/fragments/config/general.dart
Normal file
439
lib/fragments/config/general.dart
Normal file
@@ -0,0 +1,439 @@
|
||||
import 'package:collection/collection.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:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class LogLevelItem extends StatelessWidget {
|
||||
const LogLevelItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, LogLevel>(
|
||||
selector: (_, clashConfig) => clashConfig.logLevel,
|
||||
builder: (_, value, __) {
|
||||
return ListItem<LogLevel>.options(
|
||||
leading: const Icon(Icons.info_outline),
|
||||
title: Text(appLocalizations.logLevel),
|
||||
subtitle: Text(value.name),
|
||||
delegate: OptionsDelegate<LogLevel>(
|
||||
title: appLocalizations.logLevel,
|
||||
options: LogLevel.values,
|
||||
onChanged: (LogLevel? value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.logLevel = value;
|
||||
},
|
||||
textBuilder: (logLevel) => logLevel.name,
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UaItem extends StatelessWidget {
|
||||
const UaItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, String?>(
|
||||
selector: (_, clashConfig) => clashConfig.globalRealUa,
|
||||
builder: (_, value, __) {
|
||||
return ListItem<String?>.options(
|
||||
leading: const Icon(Icons.computer_outlined),
|
||||
title: const Text("UA"),
|
||||
subtitle: Text(value ?? appLocalizations.defaultText),
|
||||
delegate: OptionsDelegate<String?>(
|
||||
title: "UA",
|
||||
options: [
|
||||
null,
|
||||
"clash-verge/v1.6.6",
|
||||
"ClashforWindows/0.19.23",
|
||||
],
|
||||
value: value,
|
||||
onChanged: (ua) {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.globalRealUa = ua;
|
||||
},
|
||||
textBuilder: (ua) => ua ?? appLocalizations.defaultText,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class KeepAliveIntervalItem extends StatelessWidget {
|
||||
const KeepAliveIntervalItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, int>(
|
||||
selector: (_, config) => config.keepAliveInterval,
|
||||
builder: (_, value, __) {
|
||||
return ListItem.input(
|
||||
leading: const Icon(Icons.timer_outlined),
|
||||
title: Text(appLocalizations.keepAliveIntervalDesc),
|
||||
subtitle: Text("$value ${appLocalizations.seconds}"),
|
||||
delegate: InputDelegate(
|
||||
title: appLocalizations.keepAliveIntervalDesc,
|
||||
suffixText: appLocalizations.seconds,
|
||||
resetValue: "$defaultKeepAliveInterval",
|
||||
value: "$value",
|
||||
onChanged: (String? value) {
|
||||
if (value != null) {
|
||||
try {
|
||||
final intValue = int.parse(value);
|
||||
if (intValue <= 0) {
|
||||
throw "Invalid keepAliveInterval";
|
||||
}
|
||||
globalState.appController.clashConfig.keepAliveInterval =
|
||||
intValue;
|
||||
} catch (e) {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.keepAliveIntervalDesc,
|
||||
message: TextSpan(
|
||||
text: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TestUrlItem extends StatelessWidget {
|
||||
const TestUrlItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<Config, String>(
|
||||
selector: (_, config) => config.testUrl,
|
||||
builder: (_, value, __) {
|
||||
return ListItem.input(
|
||||
leading: const Icon(Icons.timeline),
|
||||
title: Text(appLocalizations.testUrl),
|
||||
subtitle: Text(value),
|
||||
delegate: InputDelegate(
|
||||
resetValue: defaultTestUrl,
|
||||
title: appLocalizations.testUrl,
|
||||
value: value,
|
||||
onChanged: (String? value) {
|
||||
if (value != null) {
|
||||
try {
|
||||
if (!value.isUrl) {
|
||||
throw "Invalid url";
|
||||
}
|
||||
globalState.appController.config.testUrl = value;
|
||||
} catch (e) {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.testUrl,
|
||||
message: TextSpan(
|
||||
text: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MixedPortItem extends StatelessWidget {
|
||||
const MixedPortItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, int>(
|
||||
selector: (_, clashConfig) => clashConfig.mixedPort,
|
||||
builder: (_, value, __) {
|
||||
return ListItem.input(
|
||||
leading: const Icon(Icons.adjust_outlined),
|
||||
title: Text(appLocalizations.proxyPort),
|
||||
subtitle: Text("$value"),
|
||||
delegate: InputDelegate(
|
||||
title: appLocalizations.proxyPort,
|
||||
value: "$value",
|
||||
onChanged: (String? value) {
|
||||
if (value != null) {
|
||||
try {
|
||||
final mixedPort = int.parse(value);
|
||||
if (mixedPort < 1024 || mixedPort > 49151) {
|
||||
throw "Invalid port";
|
||||
}
|
||||
globalState.appController.clashConfig.mixedPort = mixedPort;
|
||||
} catch (e) {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.proxyPort,
|
||||
message: TextSpan(
|
||||
text: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
resetValue: "$defaultMixedPort",
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HostsItem extends StatelessWidget {
|
||||
const HostsItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem.open(
|
||||
leading: const Icon(Icons.view_list_outlined),
|
||||
title: const Text("Hosts"),
|
||||
subtitle: Text(appLocalizations.hostsDesc),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
title: "Hosts",
|
||||
widget: Selector<ClashConfig, HostsMap>(
|
||||
selector: (_, clashConfig) => clashConfig.hosts,
|
||||
shouldRebuild: (prev, next) =>
|
||||
!const MapEquality<String, String>().equals(prev, next),
|
||||
builder: (_, hosts, ___) {
|
||||
final entries = hosts.entries;
|
||||
return UpdatePage(
|
||||
title: "Hosts",
|
||||
items: entries,
|
||||
titleBuilder: (item) => Text(item.key),
|
||||
subtitleBuilder: (item) => Text(item.value),
|
||||
onRemove: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
clashConfig.hosts = Map.from(hosts)..remove(value.key);
|
||||
},
|
||||
onAdd: (value) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
clashConfig.hosts = Map.from(clashConfig.hosts)
|
||||
..addEntries([value]);
|
||||
},
|
||||
isMap: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Ipv6Item extends StatelessWidget {
|
||||
const Ipv6Item({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.ipv6,
|
||||
builder: (_, ipv6, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.water_outlined),
|
||||
title: const Text("IPv6"),
|
||||
subtitle: Text(appLocalizations.ipv6Desc),
|
||||
delegate: SwitchDelegate(
|
||||
value: ipv6,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.ipv6 = value;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AllowLanItem extends StatelessWidget {
|
||||
const AllowLanItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.allowLan,
|
||||
builder: (_, allowLan, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.device_hub),
|
||||
title: Text(appLocalizations.allowLan),
|
||||
subtitle: Text(appLocalizations.allowLanDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: allowLan,
|
||||
onChanged: (bool value) async {
|
||||
final clashConfig = context.read<ClashConfig>();
|
||||
clashConfig.allowLan = value;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UnifiedDelayItem extends StatelessWidget {
|
||||
const UnifiedDelayItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.unifiedDelay,
|
||||
builder: (_, unifiedDelay, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.compress_outlined),
|
||||
title: Text(appLocalizations.unifiedDelay),
|
||||
subtitle: Text(appLocalizations.unifiedDelayDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: unifiedDelay,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.unifiedDelay = value;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FindProcessItem extends StatelessWidget {
|
||||
const FindProcessItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) =>
|
||||
clashConfig.findProcessMode == FindProcessMode.always,
|
||||
builder: (_, findProcess, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.polymer_outlined),
|
||||
title: Text(appLocalizations.findProcessMode),
|
||||
subtitle: Text(appLocalizations.findProcessModeDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: findProcess,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.findProcessMode =
|
||||
value ? FindProcessMode.always : FindProcessMode.off;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TcpConcurrentItem extends StatelessWidget {
|
||||
const TcpConcurrentItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.tcpConcurrent,
|
||||
builder: (_, tcpConcurrent, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.double_arrow_outlined),
|
||||
title: Text(appLocalizations.tcpConcurrent),
|
||||
subtitle: Text(appLocalizations.tcpConcurrentDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: tcpConcurrent,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.tcpConcurrent = value;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GeodataLoaderItem extends StatelessWidget {
|
||||
const GeodataLoaderItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) =>
|
||||
clashConfig.geodataLoader == geodataLoaderMemconservative,
|
||||
builder: (_, memconservative, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.memory),
|
||||
title: Text(appLocalizations.geodataLoader),
|
||||
subtitle: Text(appLocalizations.geodataLoaderDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: memconservative,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.geodataLoader =
|
||||
value ? geodataLoaderMemconservative : geodataLoaderStandard;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExternalControllerItem extends StatelessWidget {
|
||||
const ExternalControllerItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.externalController.isNotEmpty,
|
||||
builder: (_, hasExternalController, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.api_outlined),
|
||||
title: Text(appLocalizations.externalController),
|
||||
subtitle: Text(appLocalizations.externalControllerDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: hasExternalController,
|
||||
onChanged: (bool value) async {
|
||||
final appController = globalState.appController;
|
||||
appController.clashConfig.externalController =
|
||||
value ? defaultExternalController : '';
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final generalItems = const [
|
||||
LogLevelItem(),
|
||||
UaItem(),
|
||||
KeepAliveIntervalItem(),
|
||||
TestUrlItem(),
|
||||
MixedPortItem(),
|
||||
HostsItem(),
|
||||
Ipv6Item(),
|
||||
AllowLanItem(),
|
||||
UnifiedDelayItem(),
|
||||
FindProcessItem(),
|
||||
TcpConcurrentItem(),
|
||||
GeodataLoaderItem(),
|
||||
ExternalControllerItem(),
|
||||
]
|
||||
.separated(
|
||||
const Divider(
|
||||
height: 0,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
140
lib/fragments/config/vpn.dart
Normal file
140
lib/fragments/config/vpn.dart
Normal file
@@ -0,0 +1,140 @@
|
||||
import 'package:fl_clash/common/common.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 Selector<Config, bool>(
|
||||
selector: (_, config) => config.vpnProps.enable,
|
||||
builder: (_, enable, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.stacked_line_chart),
|
||||
title: const Text("VPN"),
|
||||
subtitle: Text(appLocalizations.vpnEnableDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: enable,
|
||||
onChanged: (bool value) async {
|
||||
final config = globalState.appController.config;
|
||||
final vpnProps = config.vpnProps;
|
||||
config.vpnProps = vpnProps.copyWith(
|
||||
enable: value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VPNDisabledContainer extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const VPNDisabledContainer(
|
||||
this.child, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.vpnProps.enable,
|
||||
builder: (_, enable, child) {
|
||||
return AbsorbPointer(
|
||||
absorbing: !enable,
|
||||
child: DisabledMask(
|
||||
status: !enable,
|
||||
child: child!,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AllowBypassSwitch extends StatelessWidget {
|
||||
const AllowBypassSwitch({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.vpnProps.allowBypass,
|
||||
builder: (_, allowBypass, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.arrow_forward_outlined),
|
||||
title: Text(appLocalizations.allowBypass),
|
||||
subtitle: Text(appLocalizations.allowBypassDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: allowBypass,
|
||||
onChanged: (bool value) async {
|
||||
final config = globalState.appController.config;
|
||||
final vpnProps = config.vpnProps;
|
||||
config.vpnProps = vpnProps.copyWith(
|
||||
allowBypass: value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SystemProxySwitch extends StatelessWidget {
|
||||
const SystemProxySwitch({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.vpnProps.systemProxy,
|
||||
builder: (_, systemProxy, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.settings_ethernet),
|
||||
title: Text(appLocalizations.systemProxy),
|
||||
subtitle: Text(appLocalizations.systemProxyDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: systemProxy,
|
||||
onChanged: (bool value) async {
|
||||
final config = globalState.appController.config;
|
||||
final vpnProps = config.vpnProps;
|
||||
config.vpnProps = vpnProps.copyWith(
|
||||
systemProxy: value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VpnOptions extends StatelessWidget {
|
||||
const VpnOptions({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return VPNDisabledContainer(
|
||||
Column(
|
||||
children: generateSection(
|
||||
title: appLocalizations.options,
|
||||
items: [
|
||||
const SystemProxySwitch(),
|
||||
const AllowBypassSwitch(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final vpnItems = [
|
||||
const VPNSwitch(),
|
||||
const VpnOptions(),
|
||||
];
|
||||
@@ -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(),
|
||||
),
|
||||
|
||||
@@ -53,7 +53,7 @@ class _IntranetIPState extends State<IntranetIP> {
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16).copyWith(top: 0),
|
||||
height: globalState.appController.measure.titleLargeHeight + 24 - 2,
|
||||
height: globalState.measure.titleLargeHeight + 24 - 2,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: ipNotifier,
|
||||
builder: (_, value, __) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:country_flags/country_flags.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
@@ -15,28 +14,39 @@ 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;
|
||||
|
||||
_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;
|
||||
final ipInfo = await request.checkIp();
|
||||
if (ipInfo == null) {
|
||||
timeoutNotifier.value = true;
|
||||
return;
|
||||
} else {
|
||||
timeoutNotifier.value = false;
|
||||
if (cancelToken != null) {
|
||||
cancelToken!.cancel();
|
||||
cancelToken = null;
|
||||
}
|
||||
ipInfoNotifier.value = ipInfo;
|
||||
cancelToken = CancelToken();
|
||||
try {
|
||||
final ipInfo = await request.checkIp(cancelToken: cancelToken);
|
||||
networkDetectionState.value = networkDetectionState.value.copyWith(
|
||||
isTesting: false,
|
||||
ipInfo: ipInfo,
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
_checkIpContainer(Widget child) {
|
||||
@@ -57,17 +67,28 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
ipInfoNotifier.dispose();
|
||||
timeoutNotifier.dispose();
|
||||
networkDetectionState.dispose();
|
||||
}
|
||||
|
||||
String countryCodeToEmoji(String countryCode) {
|
||||
final String code = countryCode.toUpperCase();
|
||||
if (code.length != 2) {
|
||||
return countryCode;
|
||||
}
|
||||
final int firstLetter = code.codeUnitAt(0) - 0x41 + 0x1F1E6;
|
||||
final int secondLetter = code.codeUnitAt(1) - 0x41 + 0x1F1E6;
|
||||
return String.fromCharCode(firstLetter) + String.fromCharCode(secondLetter);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_checkIpDebounce = debounce(_checkIp);
|
||||
_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(
|
||||
@@ -88,37 +109,38 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: FadeBox(
|
||||
child: ipInfo != null
|
||||
? CountryFlag.fromCountryCode(
|
||||
ipInfo.countryCode,
|
||||
width: 24,
|
||||
height: 24,
|
||||
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
|
||||
.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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -126,9 +148,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: globalState.appController.measure.titleLargeHeight +
|
||||
24 -
|
||||
2,
|
||||
height: globalState.measure.titleLargeHeight + 24 - 2,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.all(16).copyWith(top: 0),
|
||||
child: FadeBox(
|
||||
@@ -151,28 +171,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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
|
||||
style: bodyMedium,
|
||||
maxLines: 1,
|
||||
);
|
||||
final size = globalState.appController.measure.computeTextSize(valueText);
|
||||
final size = globalState.measure.computeTextSize(valueText);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -74,7 +74,7 @@ class _StartButtonState extends State<StartButton>
|
||||
if (!state.isInit || !state.hasProfile) {
|
||||
return Container();
|
||||
}
|
||||
final textWidth = globalState.appController.measure
|
||||
final textWidth = globalState.measure
|
||||
.computeTextSize(
|
||||
Text(
|
||||
other.getTimeDifference(
|
||||
|
||||
121
lib/fragments/dashboard/status_switch.dart
Normal file
121
lib/fragments/dashboard/status_switch.dart
Normal 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,
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ export 'profiles/profiles.dart';
|
||||
export 'logs.dart';
|
||||
export 'connections.dart';
|
||||
export 'access.dart';
|
||||
export 'config.dart';
|
||||
export 'config/config.dart';
|
||||
export 'application_setting.dart';
|
||||
export 'about.dart';
|
||||
export 'backup_and_recovery.dart';
|
||||
export 'resources.dart';
|
||||
export 'requests.dart';
|
||||
export 'requests.dart';
|
||||
|
||||
@@ -11,8 +11,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'view_profile.dart';
|
||||
|
||||
class EditProfile extends StatefulWidget {
|
||||
final Profile profile;
|
||||
final BuildContext context;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/profiles/edit_profile.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
@@ -37,32 +39,24 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
);
|
||||
}
|
||||
|
||||
_getColumns(ViewMode viewMode) {
|
||||
switch (viewMode) {
|
||||
case ViewMode.mobile:
|
||||
return 1;
|
||||
case ViewMode.laptop:
|
||||
return 1;
|
||||
case ViewMode.desktop:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
_updateProfiles() async {
|
||||
final appController = globalState.appController;
|
||||
final config = appController.config;
|
||||
final profiles = appController.config.profiles;
|
||||
final messages = [];
|
||||
final updateProfiles = profiles.map<Future>(
|
||||
(profile) async {
|
||||
if (profile.type == ProfileType.file) return;
|
||||
config.setProfile(
|
||||
profile.copyWith(isUpdating: true),
|
||||
);
|
||||
try {
|
||||
await appController.updateProfile(profile);
|
||||
if (profile.id == appController.config.currentProfile?.id) {
|
||||
appController.applyProfile(isPrue: true);
|
||||
appController.applyProfileDebounce();
|
||||
}
|
||||
} catch (_) {
|
||||
} catch (e) {
|
||||
messages.add("${profile.label ?? profile.id}: $e \n");
|
||||
config.setProfile(
|
||||
profile.copyWith(
|
||||
isUpdating: false,
|
||||
@@ -71,15 +65,27 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
}
|
||||
},
|
||||
);
|
||||
final titleMedium = context.textTheme.titleMedium;
|
||||
await Future.wait(updateProfiles);
|
||||
if (messages.isNotEmpty) {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.tip,
|
||||
message: TextSpan(
|
||||
children: [
|
||||
for (final message in messages)
|
||||
TextSpan(text: message, style: titleMedium)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_initScaffoldState() {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) {
|
||||
if (!mounted) return;
|
||||
final commonScaffoldState =
|
||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
||||
if (!context.mounted) return;
|
||||
commonScaffoldState?.actions = [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
@@ -87,6 +93,24 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
},
|
||||
icon: const Icon(Icons.sync),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
final profiles = globalState.appController.config.profiles;
|
||||
showSheet(
|
||||
title: appLocalizations.profilesSort,
|
||||
context: context,
|
||||
builder: (_) => SizedBox(
|
||||
height: 400,
|
||||
child: ReorderableProfiles(profiles: profiles),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.sort),
|
||||
iconSize: 26,
|
||||
),
|
||||
];
|
||||
},
|
||||
);
|
||||
@@ -116,7 +140,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
selector: (_, appState, config) => ProfilesSelectorState(
|
||||
profiles: config.profiles,
|
||||
currentProfileId: config.currentProfileId,
|
||||
viewMode: appState.viewMode,
|
||||
columns: other.getProfilesColumns(appState.viewWidth),
|
||||
),
|
||||
builder: (context, state, child) {
|
||||
if (state.profiles.isEmpty) {
|
||||
@@ -124,7 +148,6 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
label: appLocalizations.nullProfileDesc,
|
||||
);
|
||||
}
|
||||
final columns = _getColumns(state.viewMode);
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: SingleChildScrollView(
|
||||
@@ -137,7 +160,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
child: Grid(
|
||||
mainAxisSpacing: 16,
|
||||
crossAxisSpacing: 16,
|
||||
crossAxisCount: columns,
|
||||
crossAxisCount: state.columns,
|
||||
children: [
|
||||
for (int i = 0; i < state.profiles.length; i++)
|
||||
GridItem(
|
||||
@@ -145,8 +168,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
key: Key(state.profiles[i].id),
|
||||
profile: state.profiles[i],
|
||||
groupValue: state.currentProfileId,
|
||||
onChanged:
|
||||
globalState.appController.changeProfile,
|
||||
onChanged: globalState.appController.changeProfile,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -204,7 +226,7 @@ class ProfileItem extends StatelessWidget {
|
||||
);
|
||||
await appController.updateProfile(profile);
|
||||
if (profile.id == appController.config.currentProfile?.id) {
|
||||
appController.applyProfile(isPrue: true);
|
||||
appController.applyProfileDebounce();
|
||||
}
|
||||
} catch (e) {
|
||||
config.setProfile(
|
||||
@@ -244,6 +266,7 @@ class ProfileItem extends StatelessWidget {
|
||||
LinearProgressIndicator(
|
||||
minHeight: 6,
|
||||
value: progress,
|
||||
backgroundColor: context.colorScheme.primary.toSoft(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
@@ -372,3 +395,132 @@ class ProfileItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReorderableProfiles extends StatefulWidget {
|
||||
final List<Profile> profiles;
|
||||
|
||||
const ReorderableProfiles({
|
||||
super.key,
|
||||
required this.profiles,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ReorderableProfiles> createState() => _ReorderableProfilesState();
|
||||
}
|
||||
|
||||
class _ReorderableProfilesState extends State<ReorderableProfiles> {
|
||||
late List<Profile> profiles;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
profiles = List.from(widget.profiles);
|
||||
}
|
||||
|
||||
Widget proxyDecorator(
|
||||
Widget child,
|
||||
int index,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
final profile = profiles[index];
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (_, Widget? child) {
|
||||
final double animValue = Curves.easeInOut.transform(animation.value);
|
||||
final double scale = lerpDouble(1, 1.02, animValue)!;
|
||||
return Transform.scale(
|
||||
scale: scale,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
key: Key(profile.id),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: CommonCard(
|
||||
type: CommonCardType.filled,
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
right: 44,
|
||||
left: 16,
|
||||
),
|
||||
title: Text(profile.label ?? profile.id),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: ReorderableListView.builder(
|
||||
buildDefaultDragHandles: false,
|
||||
padding: const EdgeInsets.all(12),
|
||||
proxyDecorator: proxyDecorator,
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
if (oldIndex == newIndex) return;
|
||||
setState(() {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final profile = profiles.removeAt(oldIndex);
|
||||
profiles.insert(newIndex, profile);
|
||||
});
|
||||
},
|
||||
itemBuilder: (_, index) {
|
||||
final profile = profiles[index];
|
||||
return Container(
|
||||
key: Key(profile.id),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: CommonCard(
|
||||
type: CommonCardType.filled,
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
right: 16,
|
||||
left: 16,
|
||||
),
|
||||
title: Text(profile.label ?? profile.id),
|
||||
trailing: ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: const Icon(Icons.drag_handle),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: profiles.length,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 24,
|
||||
),
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
globalState.appController.config.profiles = profiles;
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(
|
||||
const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
appLocalizations.confirm,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class ProxyCard extends StatelessWidget {
|
||||
required this.type,
|
||||
});
|
||||
|
||||
Measure get measure => globalState.appController.measure;
|
||||
Measure get measure => globalState.measure;
|
||||
|
||||
Widget _buildDelayText() {
|
||||
return SizedBox(
|
||||
@@ -69,23 +69,21 @@ class ProxyCard extends StatelessWidget {
|
||||
if (type == ProxyCardType.min) {
|
||||
return SizedBox(
|
||||
height: measure.bodyMediumHeight * 1,
|
||||
child: Text(
|
||||
child: EmojiText(
|
||||
proxy.name,
|
||||
maxLines: 1,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SizedBox(
|
||||
height: measure.bodyMediumHeight * 2,
|
||||
child: Text(
|
||||
child: EmojiText(
|
||||
proxy.name,
|
||||
maxLines: 2,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -121,7 +119,7 @@ class ProxyCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final measure = globalState.appController.measure;
|
||||
final measure = globalState.measure;
|
||||
final delayText = _buildDelayText();
|
||||
final proxyNameText = _buildProxyNameText(context);
|
||||
return currentGroupProxyNameBuilder(
|
||||
@@ -155,14 +153,12 @@ class ProxyCard extends StatelessWidget {
|
||||
proxy.name,
|
||||
),
|
||||
builder: (_, desc, __) {
|
||||
return TooltipText(
|
||||
text: Text(
|
||||
desc,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: context.textTheme.bodySmall?.color
|
||||
?.toLight(),
|
||||
),
|
||||
return EmojiText(
|
||||
desc,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: context.textTheme.bodySmall?.color
|
||||
?.toLight(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/constant.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';
|
||||
@@ -25,12 +25,12 @@ Widget currentGroupProxyNameBuilder({
|
||||
}
|
||||
|
||||
double get listHeaderHeight {
|
||||
final measure = globalState.appController.measure;
|
||||
final measure = globalState.measure;
|
||||
return 24 + measure.titleMediumHeight + 4 + measure.bodyMediumHeight;
|
||||
}
|
||||
|
||||
double getItemHeight(ProxyCardType proxyCardType) {
|
||||
final measure = globalState.appController.measure;
|
||||
final measure = globalState.measure;
|
||||
final baseHeight =
|
||||
12 * 2 + measure.bodyMediumHeight * 2 + measure.bodySmallHeight + 8;
|
||||
return switch (proxyCardType) {
|
||||
@@ -51,8 +51,12 @@ delayTest(List<Proxy> proxies) async {
|
||||
),
|
||||
);
|
||||
globalState.appController.setDelay(await clashCore.getDelay(proxyName));
|
||||
});
|
||||
await Future.wait(delayProxies);
|
||||
}).toList();
|
||||
|
||||
final batchesDelayProxies = delayProxies.batch(100);
|
||||
for (final batchDelayProxies in batchesDelayProxies) {
|
||||
await Future.wait(batchDelayProxies);
|
||||
}
|
||||
appController.appState.sortNum++;
|
||||
}
|
||||
|
||||
@@ -61,7 +65,10 @@ double getScrollToSelectedOffset({
|
||||
required List<Proxy> proxies,
|
||||
}) {
|
||||
final appController = globalState.appController;
|
||||
final columns = appController.columns;
|
||||
final columns = other.getProxiesColumns(
|
||||
appController.appState.viewWidth,
|
||||
appController.config.proxiesLayout,
|
||||
);
|
||||
final proxyCardType = appController.config.proxyCardType;
|
||||
final selectedName = appController.getCurrentSelectedName(groupName);
|
||||
final findSelectedIndex = proxies.indexWhere(
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:collection/collection.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:fl_clash/widgets/card.dart';
|
||||
import 'package:fl_clash/widgets/fade_box.dart';
|
||||
import 'package:fl_clash/widgets/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -237,7 +240,10 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
|
||||
currentUnfoldSet: config.currentUnfoldSet,
|
||||
proxyCardType: config.proxyCardType,
|
||||
proxiesSortType: config.proxiesSortType,
|
||||
columns: globalState.appController.columns,
|
||||
columns: other.getProxiesColumns(
|
||||
appState.viewWidth,
|
||||
config.proxiesLayout,
|
||||
),
|
||||
sortNum: appState.sortNum,
|
||||
);
|
||||
},
|
||||
@@ -349,6 +355,8 @@ class _ListHeaderState extends State<ListHeader>
|
||||
late Animation<double> _iconTurns;
|
||||
var isLock = false;
|
||||
|
||||
String get icon => widget.group.icon;
|
||||
|
||||
String get groupName => widget.group.name;
|
||||
|
||||
String get groupType => widget.group.type.name;
|
||||
@@ -408,6 +416,7 @@ class _ListHeaderState extends State<ListHeader>
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
key: widget.key,
|
||||
radius: 24,
|
||||
type: CommonCardType.filled,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
@@ -415,57 +424,96 @@ class _ListHeaderState extends State<ListHeader>
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
groupName,
|
||||
style: context.textTheme.titleMedium,
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
Container(
|
||||
height: 48,
|
||||
width: 48,
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.secondaryContainer,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: icon.isNotEmpty
|
||||
? CachedNetworkImage(
|
||||
imageUrl: icon,
|
||||
errorWidget: (_, __, ___) => const Icon(
|
||||
IconsExt.target,
|
||||
size: 32,
|
||||
),
|
||||
)
|
||||
: const Icon(
|
||||
IconsExt.target,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
width: 16,
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
groupType,
|
||||
style: context.textTheme.labelMedium?.toLight,
|
||||
groupName,
|
||||
style: context.textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: currentGroupProxyNameBuilder(
|
||||
groupName: groupName,
|
||||
builder: (currentGroupName) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (currentGroupName.isNotEmpty) ...[
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
" · $currentGroupName",
|
||||
style: context
|
||||
.textTheme.labelMedium?.toLight,
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
groupType,
|
||||
style: context.textTheme.labelMedium?.toLight,
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: currentGroupProxyNameBuilder(
|
||||
groupName: groupName,
|
||||
builder: (currentGroupName) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (currentGroupName.isNotEmpty) ...[
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: EmojiText(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
" · $currentGroupName",
|
||||
style: context.textTheme
|
||||
.labelMedium?.toLight,
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/app.dart';
|
||||
@@ -50,7 +53,6 @@ class _ProvidersState extends State<Providers> {
|
||||
);
|
||||
await clashCore.updateExternalProvider(
|
||||
providerName: provider.name,
|
||||
providerType: provider.type,
|
||||
);
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
@@ -58,6 +60,7 @@ class _ProvidersState extends State<Providers> {
|
||||
},
|
||||
);
|
||||
await Future.wait(updateProviders);
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -65,19 +68,30 @@ class _ProvidersState extends State<Providers> {
|
||||
return Selector<AppState, List<ExternalProvider>>(
|
||||
selector: (_, appState) => appState.providers,
|
||||
builder: (_, providers, ___) {
|
||||
return ListView.separated(
|
||||
itemBuilder: (_, index) {
|
||||
return ProviderItem(
|
||||
provider: providers[index],
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, index) {
|
||||
return const Divider(
|
||||
height: 0,
|
||||
);
|
||||
},
|
||||
itemCount: providers.length,
|
||||
final proxyProviders =
|
||||
providers.where((item) => item.type == "Proxy").map(
|
||||
(item) => ProviderItem(
|
||||
provider: item,
|
||||
),
|
||||
);
|
||||
final ruleProviders =
|
||||
providers.where((item) => item.type == "Rule").map(
|
||||
(item) => ProviderItem(
|
||||
provider: item,
|
||||
),
|
||||
);
|
||||
final proxySection = generateSection(
|
||||
title: appLocalizations.proxyProviders,
|
||||
items: proxyProviders,
|
||||
);
|
||||
final ruleSection = generateSection(
|
||||
title: appLocalizations.ruleProviders,
|
||||
items: ruleProviders,
|
||||
);
|
||||
return generateListView([
|
||||
...proxySection,
|
||||
...ruleSection,
|
||||
]);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -91,28 +105,48 @@ class ProviderItem extends StatelessWidget {
|
||||
required this.provider,
|
||||
});
|
||||
|
||||
_handleUpdateProfile() async {
|
||||
await globalState.safeRun<void>(updateProvider);
|
||||
_handleUpdateProvider() async {
|
||||
await globalState.safeRun<void>(() async {
|
||||
final appState = globalState.appController.appState;
|
||||
if (provider.vehicleType != "HTTP") return;
|
||||
await globalState.safeRun(() async {
|
||||
appState.setProvider(
|
||||
provider.copyWith(
|
||||
isUpdating: true,
|
||||
),
|
||||
);
|
||||
final message = await clashCore.updateExternalProvider(
|
||||
providerName: provider.name,
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
});
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
});
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
}
|
||||
|
||||
updateProvider() async {
|
||||
final appState = globalState.appController.appState;
|
||||
if (provider.vehicleType != "HTTP") return;
|
||||
await globalState.safeRun(() async {
|
||||
appState.setProvider(
|
||||
provider.copyWith(
|
||||
isUpdating: true,
|
||||
),
|
||||
_handleSideLoadProvider() async {
|
||||
await globalState.safeRun<void>(() async {
|
||||
final platformFile = await picker.pickerFile();
|
||||
final appState = globalState.appController.appState;
|
||||
final bytes = platformFile?.bytes;
|
||||
if (bytes == null) return;
|
||||
final file = await File(provider.path).create(recursive: true);
|
||||
await file.writeAsBytes(bytes);
|
||||
final providerName = provider.name;
|
||||
var message = await clashCore.sideLoadExternalProvider(
|
||||
providerName: providerName,
|
||||
data: utf8.decode(bytes),
|
||||
);
|
||||
final message = await clashCore.updateExternalProvider(
|
||||
providerName: provider.name,
|
||||
providerType: provider.type,
|
||||
if (message.isNotEmpty) throw message;
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
});
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
}
|
||||
|
||||
String _buildProviderDesc() {
|
||||
@@ -153,18 +187,16 @@ class ProviderItem extends StatelessWidget {
|
||||
runSpacing: 6,
|
||||
spacing: 12,
|
||||
children: [
|
||||
// CommonChip(
|
||||
// avatar: const Icon(Icons.upload),
|
||||
// label: appLocalizations.upload,
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.upload),
|
||||
label: appLocalizations.upload,
|
||||
onPressed: _handleSideLoadProvider,
|
||||
),
|
||||
if (provider.vehicleType == "HTTP")
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.sync),
|
||||
label: appLocalizations.sync,
|
||||
onPressed: () {
|
||||
_handleUpdateProfile();
|
||||
},
|
||||
onPressed: _handleUpdateProvider,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -29,11 +29,11 @@ class _ProxiesFragmentState extends State<ProxiesFragment> {
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showExtendPage(
|
||||
forceNotSide: true,
|
||||
isScaffold: true,
|
||||
extendPageWidth: 360,
|
||||
context,
|
||||
body: const Providers(),
|
||||
title: appLocalizations.externalResources,
|
||||
title: appLocalizations.providers,
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
|
||||
@@ -33,6 +33,14 @@ class ProxiesSettingWidget extends StatelessWidget {
|
||||
};
|
||||
}
|
||||
|
||||
String getTextForProxiesLayout(ProxiesLayout proxiesLayout) {
|
||||
return switch (proxiesLayout) {
|
||||
ProxiesLayout.tight => appLocalizations.tight,
|
||||
ProxiesLayout.standard => appLocalizations.standard,
|
||||
ProxiesLayout.loose => appLocalizations.loose,
|
||||
};
|
||||
}
|
||||
|
||||
List<Widget> _buildStyleSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.style,
|
||||
@@ -132,36 +140,28 @@ class ProxiesSettingWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildColumnsSetting() {
|
||||
List<Widget> _buildLayoutSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.columns,
|
||||
title: appLocalizations.layout,
|
||||
items: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Selector2<AppState, Config, ColumnsSelectorState>(
|
||||
selector: (_, appState, config) => ColumnsSelectorState(
|
||||
columns: config.proxiesColumns,
|
||||
viewMode: appState.viewMode,
|
||||
),
|
||||
builder: (_, state, __) {
|
||||
child: Selector< Config, ProxiesLayout>(
|
||||
selector: (_, config) => config.proxiesLayout,
|
||||
builder: (_, proxiesLayout, __) {
|
||||
final config = globalState.appController.config;
|
||||
final targetColumnsArray = viewModeColumnsMap[state.viewMode]!;
|
||||
final currentColumns = other.getColumns(
|
||||
state.viewMode,
|
||||
state.columns,
|
||||
);
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final item in targetColumnsArray)
|
||||
for (final item in ProxiesLayout.values)
|
||||
SettingTextCard(
|
||||
other.getColumnsTextForInt(item),
|
||||
isSelected: item == currentColumns,
|
||||
getTextForProxiesLayout(item),
|
||||
isSelected: item == proxiesLayout,
|
||||
onPressed: () {
|
||||
config.proxiesColumns = item;
|
||||
config.proxiesLayout = item;
|
||||
},
|
||||
)
|
||||
],
|
||||
@@ -183,80 +183,10 @@ class ProxiesSettingWidget extends StatelessWidget {
|
||||
children: [
|
||||
..._buildStyleSetting(),
|
||||
..._buildSortSetting(),
|
||||
..._buildColumnsSetting(),
|
||||
..._buildLayoutSetting(),
|
||||
..._buildSizeSetting(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingInfoCard extends StatelessWidget {
|
||||
final Info info;
|
||||
final bool? isSelected;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const SettingInfoCard(
|
||||
this.info, {
|
||||
super.key,
|
||||
this.isSelected,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
isSelected: isSelected,
|
||||
onPressed: onPressed,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Icon(info.iconData),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
info.label,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingTextCard extends StatelessWidget {
|
||||
final String text;
|
||||
final bool? isSelected;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const SettingTextCard(
|
||||
this.text, {
|
||||
super.key,
|
||||
this.isSelected,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
onPressed: onPressed,
|
||||
isSelected: isSelected,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
text,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/proxies/setting.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
@@ -285,7 +284,10 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
return ProxyGroupSelectorState(
|
||||
proxyCardType: config.proxyCardType,
|
||||
proxiesSortType: config.proxiesSortType,
|
||||
columns: globalState.appController.columns,
|
||||
columns: other.getProxiesColumns(
|
||||
appState.viewWidth,
|
||||
config.proxiesLayout,
|
||||
),
|
||||
sortNum: appState.sortNum,
|
||||
proxies: group.all,
|
||||
groupType: group.type,
|
||||
|
||||
@@ -33,13 +33,21 @@ class Resources extends StatelessWidget {
|
||||
fileName: geoIpFileName,
|
||||
key: "geoip",
|
||||
),
|
||||
GeoItem(label: "GeoSite", fileName: geoSiteFileName, key: "geosite"),
|
||||
GeoItem(
|
||||
label: "GeoSite",
|
||||
fileName: geoSiteFileName,
|
||||
key: "geosite",
|
||||
),
|
||||
GeoItem(
|
||||
label: "MMDB",
|
||||
fileName: mmdbFileName,
|
||||
key: "mmdb",
|
||||
),
|
||||
GeoItem(label: "ASN", fileName: asnFileName, key: "asn"),
|
||||
GeoItem(
|
||||
label: "ASN",
|
||||
fileName: asnFileName,
|
||||
key: "asn",
|
||||
),
|
||||
];
|
||||
|
||||
return ListView.separated(
|
||||
@@ -81,6 +89,7 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
||||
child: UpdateGeoUrlFormDialog(
|
||||
title: geoItem.label,
|
||||
url: url,
|
||||
defaultValue: defaultGeoXMap[geoItem.key],
|
||||
),
|
||||
);
|
||||
if (newUrl != null && newUrl != url && mounted) {
|
||||
@@ -91,7 +100,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,
|
||||
@@ -182,9 +190,9 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
||||
updateGeoDateItem() async {
|
||||
isUpdating.value = true;
|
||||
try {
|
||||
final message = await clashCore.updateExternalProvider(
|
||||
providerName: geoItem.fileName,
|
||||
providerType: geoItem.label,
|
||||
final message = await clashCore.updateGeoData(
|
||||
geoName: geoItem.fileName,
|
||||
geoType: geoItem.label,
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
} catch (e) {
|
||||
@@ -239,11 +247,13 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
||||
class UpdateGeoUrlFormDialog extends StatefulWidget {
|
||||
final String title;
|
||||
final String url;
|
||||
final String? defaultValue;
|
||||
|
||||
const UpdateGeoUrlFormDialog({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.url,
|
||||
this.defaultValue
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -259,6 +269,13 @@ class _UpdateGeoUrlFormDialogState extends State<UpdateGeoUrlFormDialog> {
|
||||
urlController = TextEditingController(text: widget.url);
|
||||
}
|
||||
|
||||
_handleReset() async {
|
||||
if (widget.defaultValue == null) {
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop<String>(widget.defaultValue);
|
||||
}
|
||||
|
||||
_handleUpdate() async {
|
||||
final url = urlController.value.text;
|
||||
if (url.isEmpty) return;
|
||||
@@ -286,6 +303,16 @@ class _UpdateGeoUrlFormDialogState extends State<UpdateGeoUrlFormDialog> {
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
if (widget.defaultValue != null &&
|
||||
urlController.value.text != widget.defaultValue) ...[
|
||||
TextButton(
|
||||
onPressed: _handleReset,
|
||||
child: Text(appLocalizations.reset),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
],
|
||||
TextButton(
|
||||
onPressed: _handleUpdate,
|
||||
child: Text(appLocalizations.submit),
|
||||
|
||||
@@ -238,15 +238,66 @@ class _ThemeColorsBoxState extends State<ThemeColorsBox> {
|
||||
),
|
||||
title: Text(appLocalizations.prueBlackMode),
|
||||
delegate: SwitchDelegate(
|
||||
value: value,
|
||||
onChanged: (value){
|
||||
globalState.appController.config.prueBlack = value;
|
||||
}
|
||||
),
|
||||
value: value,
|
||||
onChanged: (value) {
|
||||
globalState.appController.config.prueBlack = value;
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
// child: Selector<Config, bool>(
|
||||
// selector: (_, config) => config.scaleProps.custom,
|
||||
// builder: (_, value, ___) {
|
||||
// return ListItem.switchItem(
|
||||
// leading: Icon(
|
||||
// Icons.format_size_sharp,
|
||||
// color: context.colorScheme.primary,
|
||||
// ),
|
||||
// title: const Text("自定义字体大小"),
|
||||
// delegate: SwitchDelegate(
|
||||
// value: value,
|
||||
// onChanged: (value) {
|
||||
// globalState.appController.config.scaleProps =
|
||||
// globalState.appController.config.scaleProps.copyWith(
|
||||
// custom: value,
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: 20,
|
||||
// child: Selector<Config, ScaleProps>(
|
||||
// selector: (_, config) => config.scaleProps,
|
||||
// builder: (_, props, ___) {
|
||||
// return AbsorbPointer(
|
||||
// absorbing: !props.custom,
|
||||
// child: DisabledMask(
|
||||
// status: !props.custom,
|
||||
// child: Slider(
|
||||
// value: props.scale,
|
||||
// min: 0.8,
|
||||
// max: 1.2,
|
||||
// onChanged: (value) {
|
||||
// globalState.appController.config.scaleProps =
|
||||
// globalState.appController.config.scaleProps.copyWith(
|
||||
// scale: value,
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
const SizedBox(
|
||||
height: 64,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/about.dart';
|
||||
import 'package:fl_clash/fragments/access.dart';
|
||||
import 'package:fl_clash/fragments/application_setting.dart';
|
||||
import 'package:fl_clash/fragments/config.dart';
|
||||
import 'package:fl_clash/fragments/config/config.dart';
|
||||
import 'package:fl_clash/l10n/l10n.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
@@ -82,44 +82,23 @@ class _ToolboxFragmentState extends State<ToolsFragment> {
|
||||
builder: (_, localeString, __) {
|
||||
final subTitle = localeString ?? appLocalizations.defaultText;
|
||||
final currentLocale = other.getLocaleForString(localeString);
|
||||
return ListTile(
|
||||
return ListItem<Locale?>.options(
|
||||
leading: const Icon(Icons.language_outlined),
|
||||
title: Text(appLocalizations.language),
|
||||
subtitle: Text(Intl.message(subTitle)),
|
||||
onTap: () {
|
||||
globalState.showCommonDialog(
|
||||
child: AlertDialog(
|
||||
title: Text(appLocalizations.language),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 16,
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 250,
|
||||
child: Wrap(
|
||||
children: [
|
||||
for (final locale in [
|
||||
null,
|
||||
...AppLocalizations.delegate.supportedLocales
|
||||
])
|
||||
ListItem.radio(
|
||||
delegate: RadioDelegate<Locale?>(
|
||||
value: locale,
|
||||
groupValue: currentLocale,
|
||||
onChanged: (Locale? value) {
|
||||
final config = context.read<Config>();
|
||||
config.locale = value?.toString();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: Text(_getLocaleString(locale)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
delegate: OptionsDelegate(
|
||||
title: appLocalizations.language,
|
||||
options: [
|
||||
null,
|
||||
...AppLocalizations.delegate.supportedLocales
|
||||
],
|
||||
onChanged: (Locale? value) {
|
||||
final config = context.read<Config>();
|
||||
config.locale = value?.toString();
|
||||
},
|
||||
textBuilder: (locale) => _getLocaleString(locale),
|
||||
value: currentLocale,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -159,11 +138,10 @@ class _ToolboxFragmentState extends State<ToolsFragment> {
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.override,
|
||||
widget: const ConfigFragment(),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
),
|
||||
ListItem.open(
|
||||
leading: const Icon(Icons.settings_applications),
|
||||
leading: const Icon(Icons.settings),
|
||||
title: Text(appLocalizations.application),
|
||||
subtitle: Text(appLocalizations.applicationDesc),
|
||||
delegate: OpenDelegate(
|
||||
|
||||
@@ -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",
|
||||
@@ -227,5 +227,66 @@
|
||||
"remoteBackupDesc": "Backup local data to WebDAV",
|
||||
"remoteRecoveryDesc": "Recovery data from WebDAV",
|
||||
"localBackupDesc": "Backup local data to local",
|
||||
"localRecoveryDesc": "Recovery data from file"
|
||||
"localRecoveryDesc": "Recovery data from file",
|
||||
"mode": "Mode",
|
||||
"time": "Time",
|
||||
"source": "Source",
|
||||
"allApps": "All apps",
|
||||
"onlyOtherApps": "Only third-party apps",
|
||||
"action": "Action",
|
||||
"intelligentSelected": "Intelligent selection",
|
||||
"clipboardImport": "Clipboard import",
|
||||
"clipboardExport": "Export clipboard",
|
||||
"layout": "Layout",
|
||||
"tight": "Tight",
|
||||
"standard": "Standard",
|
||||
"loose": "Loose",
|
||||
"profilesSort": "Profiles sort",
|
||||
"start": "Start",
|
||||
"stop": "Stop",
|
||||
"appDesc": "Processing app related settings",
|
||||
"vpnDesc": "Modify VPN related settings",
|
||||
"generalDesc": "Overwrite general settings",
|
||||
"dnsDesc": "Update DNS related settings",
|
||||
"key": "Key",
|
||||
"value": "Value",
|
||||
"keyNotEmpty": "The key cannot be empty",
|
||||
"valueNotEmpty": "The value cannot be empty",
|
||||
"hostsDesc": "Add Hosts",
|
||||
"vpnTip": "Changes take effect after restarting the VPN",
|
||||
"vpnEnableDesc": "Auto routes all system traffic through VpnService",
|
||||
"options": "Options",
|
||||
"loopback": "Loopback unlock tool",
|
||||
"loopbackDesc": "Used for UWP loopback unlocking",
|
||||
"providers": "Providers",
|
||||
"proxyProviders": "Proxy providers",
|
||||
"ruleProviders": "Rule providers",
|
||||
"overrideDns": "Override Dns",
|
||||
"overrideDnsDesc": "Turning it on will override the DNS options in the profile",
|
||||
"status": "Status",
|
||||
"statusDesc": "System DNS will be used when turned off",
|
||||
"preferH3Desc": "Prioritize the use of DOH's http/3",
|
||||
"respectRules": "Respect rules",
|
||||
"respectRulesDesc": "DNS connection following rules, need to configure proxy-server-nameserver",
|
||||
"dnsMode": "DNS mode",
|
||||
"fakeipRange": "Fakeip range",
|
||||
"fakeipFilter": "Fakeip filter",
|
||||
"defaultNameserver": "Default nameserver",
|
||||
"defaultNameserverDesc": "For resolving DNS server",
|
||||
"nameserver": "Nameserver",
|
||||
"nameserverDesc": "For resolving domain",
|
||||
"useHosts": "Use hosts",
|
||||
"useSystemHosts": "Use system hosts",
|
||||
"nameserverPolicy": "Nameserver policy",
|
||||
"nameserverPolicyDesc": "Specify the corresponding nameserver policy",
|
||||
"proxyNameserver": "Proxy nameserver",
|
||||
"proxyNameserverDesc": "Domain for resolving proxy nodes",
|
||||
"fallback": "Fallback",
|
||||
"fallbackDesc": "Generally use offshore DNS",
|
||||
"fallbackFilter": "Fallback filter",
|
||||
"geoipCode": "Geoip code",
|
||||
"ipcidr": "Ipcidr",
|
||||
"domain": "Domain",
|
||||
"resetDns": "Reset Dns",
|
||||
"reset": "Reset"
|
||||
}
|
||||
@@ -37,7 +37,7 @@
|
||||
"overrideDesc": "覆写代理相关配置",
|
||||
"allowLan": "局域网代理",
|
||||
"allowLanDesc": "允许通过局域网访问代理",
|
||||
"tun": "TUN模式",
|
||||
"tun": "虚拟网卡",
|
||||
"tunDesc": "仅在管理员模式生效",
|
||||
"minimizeOnExit": "退出时最小化",
|
||||
"minimizeOnExitDesc": "修改系统默认退出事件",
|
||||
@@ -227,5 +227,66 @@
|
||||
"remoteBackupDesc": "备份数据到WebDAV",
|
||||
"remoteRecoveryDesc": "通过WebDAV恢复数据",
|
||||
"localBackupDesc": "备份数据到本地",
|
||||
"localRecoveryDesc": "通过文件恢复数据"
|
||||
"localRecoveryDesc": "通过文件恢复数据",
|
||||
"mode": "模式",
|
||||
"time": "时间",
|
||||
"source": "来源",
|
||||
"allApps": "所有应用",
|
||||
"onlyOtherApps": "仅第三方应用",
|
||||
"action": "操作",
|
||||
"intelligentSelected": "智能选择",
|
||||
"clipboardImport": "剪贴板导入",
|
||||
"clipboardExport": "导出剪贴板",
|
||||
"layout": "布局",
|
||||
"tight": "宽松",
|
||||
"standard": "标准",
|
||||
"loose": "紧凑",
|
||||
"profilesSort": "配置排序",
|
||||
"start": "启动",
|
||||
"stop": "暂停",
|
||||
"appDesc": "处理应用相关设置",
|
||||
"vpnDesc": "修改VPN相关设置",
|
||||
"generalDesc": "覆写基础设置",
|
||||
"dnsDesc": "更新DNS相关设置",
|
||||
"key": "键",
|
||||
"value": "值",
|
||||
"keyNotEmpty": "键不能为空",
|
||||
"valueNotEmpty": "值不能为空",
|
||||
"hostsDesc": "追加Hosts",
|
||||
"vpnTip": "重启VPN后改变生效",
|
||||
"vpnEnableDesc": "通过VpnService自动路由系统所有流量",
|
||||
"options": "选项",
|
||||
"loopback": "回环解锁工具",
|
||||
"loopbackDesc": "用于UWP回环解锁",
|
||||
"providers": "提供者",
|
||||
"proxyProviders": "代理提供者",
|
||||
"ruleProviders": "规则提供者",
|
||||
"overrideDns": "覆写DNS",
|
||||
"overrideDnsDesc": "开启后将覆盖配置中的DNS选项",
|
||||
"status": "状态",
|
||||
"statusDesc": "关闭后将使用系统DNS",
|
||||
"preferH3Desc": "优先使用DOH的http/3",
|
||||
"respectRules": "遵守规则",
|
||||
"respectRulesDesc": "DNS连接跟随rules,需配置proxy-server-nameserver",
|
||||
"dnsMode": "DNS模式",
|
||||
"fakeipRange": "Fakeip范围",
|
||||
"fakeipFilter": "Fakeip过滤",
|
||||
"defaultNameserver": "默认域名服务器",
|
||||
"defaultNameserverDesc": "用于解析DNS服务器",
|
||||
"nameserver": "域名服务器",
|
||||
"nameserverDesc": "用于解析域名",
|
||||
"useHosts": "使用Hosts",
|
||||
"useSystemHosts": "使用系统Hosts",
|
||||
"nameserverPolicy": "域名服务器策略",
|
||||
"nameserverPolicyDesc": "指定对应域名服务器策略",
|
||||
"proxyNameserver": "代理域名服务器",
|
||||
"proxyNameserverDesc": "用于解析代理节点的域名",
|
||||
"fallback": "Fallback",
|
||||
"fallbackDesc": "一般情况下使用境外DNS",
|
||||
"fallbackFilter": "Fallback过滤",
|
||||
"geoipCode": "Geoip代码",
|
||||
"ipcidr": "IP/掩码",
|
||||
"domain": "域名",
|
||||
"resetDns": "重置DNS",
|
||||
"reset": "重置"
|
||||
}
|
||||
@@ -33,6 +33,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"account": MessageLookupByLibrary.simpleMessage("Account"),
|
||||
"accountTip":
|
||||
MessageLookupByLibrary.simpleMessage("Account cannot be empty"),
|
||||
"action": MessageLookupByLibrary.simpleMessage("Action"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("Add"),
|
||||
"address": MessageLookupByLibrary.simpleMessage("Address"),
|
||||
"addressHelp":
|
||||
@@ -40,6 +41,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"addressTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Please enter a valid WebDAV address"),
|
||||
"ago": MessageLookupByLibrary.simpleMessage(" Ago"),
|
||||
"allApps": MessageLookupByLibrary.simpleMessage("All apps"),
|
||||
"allowBypass": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow applications to bypass VPN"),
|
||||
"allowBypassDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -50,6 +52,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"app": MessageLookupByLibrary.simpleMessage("App"),
|
||||
"appAccessControl":
|
||||
MessageLookupByLibrary.simpleMessage("App access control"),
|
||||
"appDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Processing app related settings"),
|
||||
"application": MessageLookupByLibrary.simpleMessage("Application"),
|
||||
"applicationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Modify application related settings"),
|
||||
@@ -89,6 +93,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage(
|
||||
"The current application is already the latest version"),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("Checking..."),
|
||||
"clipboardExport":
|
||||
MessageLookupByLibrary.simpleMessage("Export clipboard"),
|
||||
"clipboardImport":
|
||||
MessageLookupByLibrary.simpleMessage("Clipboard import"),
|
||||
"columns": MessageLookupByLibrary.simpleMessage("Columns"),
|
||||
"compatible":
|
||||
MessageLookupByLibrary.simpleMessage("Compatibility mode"),
|
||||
@@ -108,6 +116,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"dark": MessageLookupByLibrary.simpleMessage("Dark"),
|
||||
"dashboard": MessageLookupByLibrary.simpleMessage("Dashboard"),
|
||||
"days": MessageLookupByLibrary.simpleMessage("Days"),
|
||||
"defaultNameserver":
|
||||
MessageLookupByLibrary.simpleMessage("Default nameserver"),
|
||||
"defaultNameserverDesc":
|
||||
MessageLookupByLibrary.simpleMessage("For resolving DNS server"),
|
||||
"defaultSort": MessageLookupByLibrary.simpleMessage("Sort by default"),
|
||||
"defaultText": MessageLookupByLibrary.simpleMessage("Default"),
|
||||
"delay": MessageLookupByLibrary.simpleMessage("Delay"),
|
||||
@@ -122,8 +134,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Discover the new version"),
|
||||
"discovery":
|
||||
MessageLookupByLibrary.simpleMessage("Discovery a new version"),
|
||||
"dnsDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Update DNS related settings"),
|
||||
"dnsMode": MessageLookupByLibrary.simpleMessage("DNS mode"),
|
||||
"doYouWantToPass":
|
||||
MessageLookupByLibrary.simpleMessage("Do you want to pass"),
|
||||
"domain": MessageLookupByLibrary.simpleMessage("Domain"),
|
||||
"download": MessageLookupByLibrary.simpleMessage("Download"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("Edit"),
|
||||
"en": MessageLookupByLibrary.simpleMessage("English"),
|
||||
@@ -143,6 +159,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"externalLink": MessageLookupByLibrary.simpleMessage("External link"),
|
||||
"externalResources":
|
||||
MessageLookupByLibrary.simpleMessage("External resources"),
|
||||
"fakeipFilter": MessageLookupByLibrary.simpleMessage("Fakeip filter"),
|
||||
"fakeipRange": MessageLookupByLibrary.simpleMessage("Fakeip range"),
|
||||
"fallback": MessageLookupByLibrary.simpleMessage("Fallback"),
|
||||
"fallbackDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Generally use offshore DNS"),
|
||||
"fallbackFilter":
|
||||
MessageLookupByLibrary.simpleMessage("Fallback filter"),
|
||||
"file": MessageLookupByLibrary.simpleMessage("File"),
|
||||
"fileDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Directly upload profile"),
|
||||
@@ -153,27 +176,38 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"There is a risk of flashback after opening"),
|
||||
"fourColumns": MessageLookupByLibrary.simpleMessage("Four columns"),
|
||||
"general": MessageLookupByLibrary.simpleMessage("General"),
|
||||
"generalDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Overwrite general settings"),
|
||||
"geoData": MessageLookupByLibrary.simpleMessage("GeoData"),
|
||||
"geodataLoader":
|
||||
MessageLookupByLibrary.simpleMessage("Geo Low Memory Mode"),
|
||||
"geodataLoaderDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Enabling will use the Geo low memory loader"),
|
||||
"geoipCode": MessageLookupByLibrary.simpleMessage("Geoip code"),
|
||||
"global": MessageLookupByLibrary.simpleMessage("Global"),
|
||||
"go": MessageLookupByLibrary.simpleMessage("Go"),
|
||||
"goDownload": MessageLookupByLibrary.simpleMessage("Go to download"),
|
||||
"hostsDesc": MessageLookupByLibrary.simpleMessage("Add Hosts"),
|
||||
"hours": MessageLookupByLibrary.simpleMessage("Hours"),
|
||||
"importFromURL":
|
||||
MessageLookupByLibrary.simpleMessage("Import from URL"),
|
||||
"infiniteTime":
|
||||
MessageLookupByLibrary.simpleMessage("Long term effective"),
|
||||
"init": MessageLookupByLibrary.simpleMessage("Init"),
|
||||
"intelligentSelected":
|
||||
MessageLookupByLibrary.simpleMessage("Intelligent selection"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
||||
"When turned on it will be able to receive IPv6 traffic"),
|
||||
"just": MessageLookupByLibrary.simpleMessage("Just"),
|
||||
"keepAliveIntervalDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Tcp keep alive interval"),
|
||||
"key": MessageLookupByLibrary.simpleMessage("Key"),
|
||||
"keyNotEmpty":
|
||||
MessageLookupByLibrary.simpleMessage("The key cannot be empty"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("Language"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("Layout"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("Light"),
|
||||
"list": MessageLookupByLibrary.simpleMessage("List"),
|
||||
"local": MessageLookupByLibrary.simpleMessage("Local"),
|
||||
@@ -187,16 +221,29 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Disabling will hide the log entry"),
|
||||
"logs": MessageLookupByLibrary.simpleMessage("Logs"),
|
||||
"logsDesc": MessageLookupByLibrary.simpleMessage("Log capture records"),
|
||||
"loopback":
|
||||
MessageLookupByLibrary.simpleMessage("Loopback unlock tool"),
|
||||
"loopbackDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Used for UWP loopback unlocking"),
|
||||
"loose": MessageLookupByLibrary.simpleMessage("Loose"),
|
||||
"min": MessageLookupByLibrary.simpleMessage("Min"),
|
||||
"minimizeOnExit":
|
||||
MessageLookupByLibrary.simpleMessage("Minimize on exit"),
|
||||
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Modify the default system exit event"),
|
||||
"minutes": MessageLookupByLibrary.simpleMessage("Minutes"),
|
||||
"mode": MessageLookupByLibrary.simpleMessage("Mode"),
|
||||
"months": MessageLookupByLibrary.simpleMessage("Months"),
|
||||
"more": MessageLookupByLibrary.simpleMessage("More"),
|
||||
"name": MessageLookupByLibrary.simpleMessage("Name"),
|
||||
"nameSort": MessageLookupByLibrary.simpleMessage("Sort by name"),
|
||||
"nameserver": MessageLookupByLibrary.simpleMessage("Nameserver"),
|
||||
"nameserverDesc":
|
||||
MessageLookupByLibrary.simpleMessage("For resolving domain"),
|
||||
"nameserverPolicy":
|
||||
MessageLookupByLibrary.simpleMessage("Nameserver policy"),
|
||||
"nameserverPolicyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Specify the corresponding nameserver policy"),
|
||||
"networkDetection":
|
||||
MessageLookupByLibrary.simpleMessage("Network detection"),
|
||||
"networkSpeed": MessageLookupByLibrary.simpleMessage("Network speed"),
|
||||
@@ -216,10 +263,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"No profile, Please add a profile"),
|
||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"),
|
||||
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
|
||||
"onlyOtherApps":
|
||||
MessageLookupByLibrary.simpleMessage("Only third-party apps"),
|
||||
"onlyStatisticsProxy":
|
||||
MessageLookupByLibrary.simpleMessage("Only statistics proxy"),
|
||||
"onlyStatisticsProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"When turned on, only statistics proxy traffic"),
|
||||
"options": MessageLookupByLibrary.simpleMessage("Options"),
|
||||
"other": MessageLookupByLibrary.simpleMessage("Other"),
|
||||
"otherContributors":
|
||||
MessageLookupByLibrary.simpleMessage("Other contributors"),
|
||||
@@ -227,6 +277,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"override": MessageLookupByLibrary.simpleMessage("Override"),
|
||||
"overrideDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Override Proxy related config"),
|
||||
"overrideDns": MessageLookupByLibrary.simpleMessage("Override Dns"),
|
||||
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Turning it on will override the DNS options in the profile"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Password"),
|
||||
"passwordTip":
|
||||
MessageLookupByLibrary.simpleMessage("Password cannot be empty"),
|
||||
@@ -238,6 +291,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"pleaseUploadValidQrcode": MessageLookupByLibrary.simpleMessage(
|
||||
"Please upload a valid QR code"),
|
||||
"port": MessageLookupByLibrary.simpleMessage("Port"),
|
||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Prioritize the use of DOH\'s http/3"),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("Preview"),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("Profile"),
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||
@@ -255,14 +310,22 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"profileUrlNullValidationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Please input the profile URL"),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("Profiles"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("Profiles sort"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("Project"),
|
||||
"providers": MessageLookupByLibrary.simpleMessage("Providers"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("Proxies"),
|
||||
"proxiesSetting":
|
||||
MessageLookupByLibrary.simpleMessage("Proxies setting"),
|
||||
"proxyGroup": MessageLookupByLibrary.simpleMessage("Proxy group"),
|
||||
"proxyNameserver":
|
||||
MessageLookupByLibrary.simpleMessage("Proxy nameserver"),
|
||||
"proxyNameserverDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Domain for resolving proxy nodes"),
|
||||
"proxyPort": MessageLookupByLibrary.simpleMessage("ProxyPort"),
|
||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Set the Clash listening port"),
|
||||
"proxyProviders":
|
||||
MessageLookupByLibrary.simpleMessage("Proxy providers"),
|
||||
"prueBlackMode":
|
||||
MessageLookupByLibrary.simpleMessage("Prue black mode"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
|
||||
@@ -283,10 +346,16 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"requests": MessageLookupByLibrary.simpleMessage("Requests"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"View recently request records"),
|
||||
"reset": MessageLookupByLibrary.simpleMessage("Reset"),
|
||||
"resetDns": MessageLookupByLibrary.simpleMessage("Reset Dns"),
|
||||
"resources": MessageLookupByLibrary.simpleMessage("Resources"),
|
||||
"resourcesDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"External resource related info"),
|
||||
"respectRules": MessageLookupByLibrary.simpleMessage("Respect rules"),
|
||||
"respectRulesDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"DNS connection following rules, need to configure proxy-server-nameserver"),
|
||||
"rule": MessageLookupByLibrary.simpleMessage("Rule"),
|
||||
"ruleProviders": MessageLookupByLibrary.simpleMessage("Rule providers"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("Save"),
|
||||
"search": MessageLookupByLibrary.simpleMessage("Search"),
|
||||
"seconds": MessageLookupByLibrary.simpleMessage("Seconds"),
|
||||
@@ -300,12 +369,19 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Start in the background"),
|
||||
"size": MessageLookupByLibrary.simpleMessage("Size"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("Sort"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("Source"),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("Standard"),
|
||||
"start": MessageLookupByLibrary.simpleMessage("Start"),
|
||||
"startVpn": MessageLookupByLibrary.simpleMessage("Staring VPN..."),
|
||||
"status": MessageLookupByLibrary.simpleMessage("Status"),
|
||||
"statusDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"System DNS will be used when turned off"),
|
||||
"stop": MessageLookupByLibrary.simpleMessage("Stop"),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."),
|
||||
"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"),
|
||||
@@ -322,10 +398,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Set dark mode,adjust the color"),
|
||||
"themeMode": MessageLookupByLibrary.simpleMessage("Theme mode"),
|
||||
"threeColumns": MessageLookupByLibrary.simpleMessage("Three columns"),
|
||||
"tight": MessageLookupByLibrary.simpleMessage("Tight"),
|
||||
"time": MessageLookupByLibrary.simpleMessage("Time"),
|
||||
"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"),
|
||||
@@ -341,7 +419,19 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
||||
"urlDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Obtain profile through URL"),
|
||||
"useHosts": MessageLookupByLibrary.simpleMessage("Use hosts"),
|
||||
"useSystemHosts":
|
||||
MessageLookupByLibrary.simpleMessage("Use system hosts"),
|
||||
"value": MessageLookupByLibrary.simpleMessage("Value"),
|
||||
"valueNotEmpty":
|
||||
MessageLookupByLibrary.simpleMessage("The value cannot be empty"),
|
||||
"view": MessageLookupByLibrary.simpleMessage("View"),
|
||||
"vpnDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Modify VPN related settings"),
|
||||
"vpnEnableDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Auto routes all system traffic through VpnService"),
|
||||
"vpnTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Changes take effect after restarting the VPN"),
|
||||
"webDAVConfiguration":
|
||||
MessageLookupByLibrary.simpleMessage("WebDAV configuration"),
|
||||
"whitelistMode": MessageLookupByLibrary.simpleMessage("Whitelist mode"),
|
||||
|
||||
@@ -31,11 +31,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("选中应用将会被排除在VPN之外"),
|
||||
"account": MessageLookupByLibrary.simpleMessage("账号"),
|
||||
"accountTip": MessageLookupByLibrary.simpleMessage("账号不能为空"),
|
||||
"action": MessageLookupByLibrary.simpleMessage("操作"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("添加"),
|
||||
"address": MessageLookupByLibrary.simpleMessage("地址"),
|
||||
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
|
||||
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
|
||||
"ago": MessageLookupByLibrary.simpleMessage("前"),
|
||||
"allApps": MessageLookupByLibrary.simpleMessage("所有应用"),
|
||||
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过VPN"),
|
||||
"allowBypassDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"),
|
||||
@@ -43,6 +45,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"allowLanDesc": MessageLookupByLibrary.simpleMessage("允许通过局域网访问代理"),
|
||||
"app": MessageLookupByLibrary.simpleMessage("应用"),
|
||||
"appAccessControl": MessageLookupByLibrary.simpleMessage("应用访问控制"),
|
||||
"appDesc": MessageLookupByLibrary.simpleMessage("处理应用相关设置"),
|
||||
"application": MessageLookupByLibrary.simpleMessage("应用程序"),
|
||||
"applicationDesc": MessageLookupByLibrary.simpleMessage("修改应用程序相关设置"),
|
||||
"auto": MessageLookupByLibrary.simpleMessage("自动"),
|
||||
@@ -73,6 +76,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"),
|
||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("检测中..."),
|
||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
|
||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
|
||||
"columns": MessageLookupByLibrary.simpleMessage("列数"),
|
||||
"compatible": MessageLookupByLibrary.simpleMessage("兼容模式"),
|
||||
"compatibleDesc":
|
||||
@@ -90,6 +95,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"dark": MessageLookupByLibrary.simpleMessage("深色"),
|
||||
"dashboard": MessageLookupByLibrary.simpleMessage("仪表盘"),
|
||||
"days": MessageLookupByLibrary.simpleMessage("天"),
|
||||
"defaultNameserver": MessageLookupByLibrary.simpleMessage("默认域名服务器"),
|
||||
"defaultNameserverDesc":
|
||||
MessageLookupByLibrary.simpleMessage("用于解析DNS服务器"),
|
||||
"defaultSort": MessageLookupByLibrary.simpleMessage("按默认排序"),
|
||||
"defaultText": MessageLookupByLibrary.simpleMessage("默认"),
|
||||
"delay": MessageLookupByLibrary.simpleMessage("延迟"),
|
||||
@@ -101,7 +109,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"direct": MessageLookupByLibrary.simpleMessage("直连"),
|
||||
"discoverNewVersion": MessageLookupByLibrary.simpleMessage("发现新版本"),
|
||||
"discovery": MessageLookupByLibrary.simpleMessage("发现新版本"),
|
||||
"dnsDesc": MessageLookupByLibrary.simpleMessage("更新DNS相关设置"),
|
||||
"dnsMode": MessageLookupByLibrary.simpleMessage("DNS模式"),
|
||||
"doYouWantToPass": MessageLookupByLibrary.simpleMessage("是否要通过"),
|
||||
"domain": MessageLookupByLibrary.simpleMessage("域名"),
|
||||
"download": MessageLookupByLibrary.simpleMessage("下载"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("编辑"),
|
||||
"en": MessageLookupByLibrary.simpleMessage("英语"),
|
||||
@@ -117,6 +128,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制Clash内核"),
|
||||
"externalLink": MessageLookupByLibrary.simpleMessage("外部链接"),
|
||||
"externalResources": MessageLookupByLibrary.simpleMessage("外部资源"),
|
||||
"fakeipFilter": MessageLookupByLibrary.simpleMessage("Fakeip过滤"),
|
||||
"fakeipRange": MessageLookupByLibrary.simpleMessage("Fakeip范围"),
|
||||
"fallback": MessageLookupByLibrary.simpleMessage("Fallback"),
|
||||
"fallbackDesc": MessageLookupByLibrary.simpleMessage("一般情况下使用境外DNS"),
|
||||
"fallbackFilter": MessageLookupByLibrary.simpleMessage("Fallback过滤"),
|
||||
"file": MessageLookupByLibrary.simpleMessage("文件"),
|
||||
"fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
|
||||
"filterSystemApp": MessageLookupByLibrary.simpleMessage("过滤系统应用"),
|
||||
@@ -125,23 +141,31 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("开启后存在闪退风险"),
|
||||
"fourColumns": MessageLookupByLibrary.simpleMessage("四列"),
|
||||
"general": MessageLookupByLibrary.simpleMessage("基础"),
|
||||
"generalDesc": MessageLookupByLibrary.simpleMessage("覆写基础设置"),
|
||||
"geoData": MessageLookupByLibrary.simpleMessage("地理数据"),
|
||||
"geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低内存模式"),
|
||||
"geodataLoaderDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启将使用Geo低内存加载器"),
|
||||
"geoipCode": MessageLookupByLibrary.simpleMessage("Geoip代码"),
|
||||
"global": MessageLookupByLibrary.simpleMessage("全局"),
|
||||
"go": MessageLookupByLibrary.simpleMessage("前往"),
|
||||
"goDownload": MessageLookupByLibrary.simpleMessage("前往下载"),
|
||||
"hostsDesc": MessageLookupByLibrary.simpleMessage("追加Hosts"),
|
||||
"hours": MessageLookupByLibrary.simpleMessage("小时"),
|
||||
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
|
||||
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
|
||||
"init": MessageLookupByLibrary.simpleMessage("初始化"),
|
||||
"intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
|
||||
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
|
||||
"keepAliveIntervalDesc":
|
||||
MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
|
||||
"key": MessageLookupByLibrary.simpleMessage("键"),
|
||||
"keyNotEmpty": MessageLookupByLibrary.simpleMessage("键不能为空"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("语言"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("布局"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("浅色"),
|
||||
"list": MessageLookupByLibrary.simpleMessage("列表"),
|
||||
"local": MessageLookupByLibrary.simpleMessage("本地"),
|
||||
@@ -152,15 +176,24 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"),
|
||||
"logs": MessageLookupByLibrary.simpleMessage("日志"),
|
||||
"logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"),
|
||||
"loopback": MessageLookupByLibrary.simpleMessage("回环解锁工具"),
|
||||
"loopbackDesc": MessageLookupByLibrary.simpleMessage("用于UWP回环解锁"),
|
||||
"loose": MessageLookupByLibrary.simpleMessage("紧凑"),
|
||||
"min": MessageLookupByLibrary.simpleMessage("最小"),
|
||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
|
||||
"minimizeOnExitDesc":
|
||||
MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
|
||||
"minutes": MessageLookupByLibrary.simpleMessage("分钟"),
|
||||
"mode": MessageLookupByLibrary.simpleMessage("模式"),
|
||||
"months": MessageLookupByLibrary.simpleMessage("月"),
|
||||
"more": MessageLookupByLibrary.simpleMessage("更多"),
|
||||
"name": MessageLookupByLibrary.simpleMessage("名称"),
|
||||
"nameSort": MessageLookupByLibrary.simpleMessage("按名称排序"),
|
||||
"nameserver": MessageLookupByLibrary.simpleMessage("域名服务器"),
|
||||
"nameserverDesc": MessageLookupByLibrary.simpleMessage("用于解析域名"),
|
||||
"nameserverPolicy": MessageLookupByLibrary.simpleMessage("域名服务器策略"),
|
||||
"nameserverPolicyDesc":
|
||||
MessageLookupByLibrary.simpleMessage("指定对应域名服务器策略"),
|
||||
"networkDetection": MessageLookupByLibrary.simpleMessage("网络检测"),
|
||||
"networkSpeed": MessageLookupByLibrary.simpleMessage("网络速度"),
|
||||
"noInfo": MessageLookupByLibrary.simpleMessage("暂无信息"),
|
||||
@@ -176,14 +209,19 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
|
||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
|
||||
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
|
||||
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("仅第三方应用"),
|
||||
"onlyStatisticsProxy": MessageLookupByLibrary.simpleMessage("仅统计代理"),
|
||||
"onlyStatisticsProxyDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启后,将只统计代理流量"),
|
||||
"options": MessageLookupByLibrary.simpleMessage("选项"),
|
||||
"other": MessageLookupByLibrary.simpleMessage("其他"),
|
||||
"otherContributors": MessageLookupByLibrary.simpleMessage("其他贡献者"),
|
||||
"outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"),
|
||||
"override": MessageLookupByLibrary.simpleMessage("覆写"),
|
||||
"overrideDesc": MessageLookupByLibrary.simpleMessage("覆写代理相关配置"),
|
||||
"overrideDns": MessageLookupByLibrary.simpleMessage("覆写DNS"),
|
||||
"overrideDnsDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启后将覆盖配置中的DNS选项"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("密码"),
|
||||
"passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("粘贴"),
|
||||
@@ -192,6 +230,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"pleaseUploadValidQrcode":
|
||||
MessageLookupByLibrary.simpleMessage("请上传有效的二维码"),
|
||||
"port": MessageLookupByLibrary.simpleMessage("端口"),
|
||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage("优先使用DOH的http/3"),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("预览"),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("配置"),
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||
@@ -207,12 +246,18 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"profileUrlNullValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage("请输入配置URL"),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("配置"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("配置排序"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("项目"),
|
||||
"providers": MessageLookupByLibrary.simpleMessage("提供者"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("代理"),
|
||||
"proxiesSetting": MessageLookupByLibrary.simpleMessage("代理设置"),
|
||||
"proxyGroup": MessageLookupByLibrary.simpleMessage("代理组"),
|
||||
"proxyNameserver": MessageLookupByLibrary.simpleMessage("代理域名服务器"),
|
||||
"proxyNameserverDesc":
|
||||
MessageLookupByLibrary.simpleMessage("用于解析代理节点的域名"),
|
||||
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
|
||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
|
||||
"proxyProviders": MessageLookupByLibrary.simpleMessage("代理提供者"),
|
||||
"prueBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
|
||||
@@ -226,9 +271,15 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("通过WebDAV恢复数据"),
|
||||
"requests": MessageLookupByLibrary.simpleMessage("请求"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"),
|
||||
"reset": MessageLookupByLibrary.simpleMessage("重置"),
|
||||
"resetDns": MessageLookupByLibrary.simpleMessage("重置DNS"),
|
||||
"resources": MessageLookupByLibrary.simpleMessage("资源"),
|
||||
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"),
|
||||
"respectRules": MessageLookupByLibrary.simpleMessage("遵守规则"),
|
||||
"respectRulesDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"DNS连接跟随rules,需配置proxy-server-nameserver"),
|
||||
"rule": MessageLookupByLibrary.simpleMessage("规则"),
|
||||
"ruleProviders": MessageLookupByLibrary.simpleMessage("规则提供者"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("保存"),
|
||||
"search": MessageLookupByLibrary.simpleMessage("搜索"),
|
||||
"seconds": MessageLookupByLibrary.simpleMessage("秒"),
|
||||
@@ -241,7 +292,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("后台启动"),
|
||||
"size": MessageLookupByLibrary.simpleMessage("尺寸"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("排序"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("来源"),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("标准"),
|
||||
"start": MessageLookupByLibrary.simpleMessage("启动"),
|
||||
"startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."),
|
||||
"status": MessageLookupByLibrary.simpleMessage("状态"),
|
||||
"statusDesc": MessageLookupByLibrary.simpleMessage("关闭后将使用系统DNS"),
|
||||
"stop": MessageLookupByLibrary.simpleMessage("暂停"),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("风格"),
|
||||
"submit": MessageLookupByLibrary.simpleMessage("提交"),
|
||||
@@ -261,10 +318,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
|
||||
"themeMode": MessageLookupByLibrary.simpleMessage("主题模式"),
|
||||
"threeColumns": MessageLookupByLibrary.simpleMessage("三列"),
|
||||
"tight": MessageLookupByLibrary.simpleMessage("宽松"),
|
||||
"time": MessageLookupByLibrary.simpleMessage("时间"),
|
||||
"tip": MessageLookupByLibrary.simpleMessage("提示"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("工具"),
|
||||
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),
|
||||
"tun": MessageLookupByLibrary.simpleMessage("TUN模式"),
|
||||
"tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
|
||||
"tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"),
|
||||
"twoColumns": MessageLookupByLibrary.simpleMessage("两列"),
|
||||
"unableToUpdateCurrentProfileDesc":
|
||||
@@ -276,7 +335,15 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"upload": MessageLookupByLibrary.simpleMessage("上传"),
|
||||
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
||||
"urlDesc": MessageLookupByLibrary.simpleMessage("通过URL获取配置文件"),
|
||||
"useHosts": MessageLookupByLibrary.simpleMessage("使用Hosts"),
|
||||
"useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"),
|
||||
"value": MessageLookupByLibrary.simpleMessage("值"),
|
||||
"valueNotEmpty": MessageLookupByLibrary.simpleMessage("值不能为空"),
|
||||
"view": MessageLookupByLibrary.simpleMessage("查看"),
|
||||
"vpnDesc": MessageLookupByLibrary.simpleMessage("修改VPN相关设置"),
|
||||
"vpnEnableDesc":
|
||||
MessageLookupByLibrary.simpleMessage("通过VpnService自动路由系统所有流量"),
|
||||
"vpnTip": MessageLookupByLibrary.simpleMessage("重启VPN后改变生效"),
|
||||
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV配置"),
|
||||
"whitelistMode": MessageLookupByLibrary.simpleMessage("白名单模式"),
|
||||
"years": MessageLookupByLibrary.simpleMessage("年"),
|
||||
|
||||
@@ -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: [],
|
||||
@@ -2339,6 +2339,616 @@ class AppLocalizations {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Mode`
|
||||
String get mode {
|
||||
return Intl.message(
|
||||
'Mode',
|
||||
name: 'mode',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Time`
|
||||
String get time {
|
||||
return Intl.message(
|
||||
'Time',
|
||||
name: 'time',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Source`
|
||||
String get source {
|
||||
return Intl.message(
|
||||
'Source',
|
||||
name: 'source',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `All apps`
|
||||
String get allApps {
|
||||
return Intl.message(
|
||||
'All apps',
|
||||
name: 'allApps',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Only third-party apps`
|
||||
String get onlyOtherApps {
|
||||
return Intl.message(
|
||||
'Only third-party apps',
|
||||
name: 'onlyOtherApps',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Action`
|
||||
String get action {
|
||||
return Intl.message(
|
||||
'Action',
|
||||
name: 'action',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Intelligent selection`
|
||||
String get intelligentSelected {
|
||||
return Intl.message(
|
||||
'Intelligent selection',
|
||||
name: 'intelligentSelected',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Clipboard import`
|
||||
String get clipboardImport {
|
||||
return Intl.message(
|
||||
'Clipboard import',
|
||||
name: 'clipboardImport',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Export clipboard`
|
||||
String get clipboardExport {
|
||||
return Intl.message(
|
||||
'Export clipboard',
|
||||
name: 'clipboardExport',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Layout`
|
||||
String get layout {
|
||||
return Intl.message(
|
||||
'Layout',
|
||||
name: 'layout',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tight`
|
||||
String get tight {
|
||||
return Intl.message(
|
||||
'Tight',
|
||||
name: 'tight',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Standard`
|
||||
String get standard {
|
||||
return Intl.message(
|
||||
'Standard',
|
||||
name: 'standard',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Loose`
|
||||
String get loose {
|
||||
return Intl.message(
|
||||
'Loose',
|
||||
name: 'loose',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Profiles sort`
|
||||
String get profilesSort {
|
||||
return Intl.message(
|
||||
'Profiles sort',
|
||||
name: 'profilesSort',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Start`
|
||||
String get start {
|
||||
return Intl.message(
|
||||
'Start',
|
||||
name: 'start',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Stop`
|
||||
String get stop {
|
||||
return Intl.message(
|
||||
'Stop',
|
||||
name: 'stop',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Processing app related settings`
|
||||
String get appDesc {
|
||||
return Intl.message(
|
||||
'Processing app related settings',
|
||||
name: 'appDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Modify VPN related settings`
|
||||
String get vpnDesc {
|
||||
return Intl.message(
|
||||
'Modify VPN related settings',
|
||||
name: 'vpnDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Overwrite general settings`
|
||||
String get generalDesc {
|
||||
return Intl.message(
|
||||
'Overwrite general settings',
|
||||
name: 'generalDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Update DNS related settings`
|
||||
String get dnsDesc {
|
||||
return Intl.message(
|
||||
'Update DNS related settings',
|
||||
name: 'dnsDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Key`
|
||||
String get key {
|
||||
return Intl.message(
|
||||
'Key',
|
||||
name: 'key',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Value`
|
||||
String get value {
|
||||
return Intl.message(
|
||||
'Value',
|
||||
name: 'value',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `The key cannot be empty`
|
||||
String get keyNotEmpty {
|
||||
return Intl.message(
|
||||
'The key cannot be empty',
|
||||
name: 'keyNotEmpty',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `The value cannot be empty`
|
||||
String get valueNotEmpty {
|
||||
return Intl.message(
|
||||
'The value cannot be empty',
|
||||
name: 'valueNotEmpty',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Add Hosts`
|
||||
String get hostsDesc {
|
||||
return Intl.message(
|
||||
'Add Hosts',
|
||||
name: 'hostsDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Changes take effect after restarting the VPN`
|
||||
String get vpnTip {
|
||||
return Intl.message(
|
||||
'Changes take effect after restarting the VPN',
|
||||
name: 'vpnTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Auto routes all system traffic through VpnService`
|
||||
String get vpnEnableDesc {
|
||||
return Intl.message(
|
||||
'Auto routes all system traffic through VpnService',
|
||||
name: 'vpnEnableDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Options`
|
||||
String get options {
|
||||
return Intl.message(
|
||||
'Options',
|
||||
name: 'options',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Loopback unlock tool`
|
||||
String get loopback {
|
||||
return Intl.message(
|
||||
'Loopback unlock tool',
|
||||
name: 'loopback',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Used for UWP loopback unlocking`
|
||||
String get loopbackDesc {
|
||||
return Intl.message(
|
||||
'Used for UWP loopback unlocking',
|
||||
name: 'loopbackDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Providers`
|
||||
String get providers {
|
||||
return Intl.message(
|
||||
'Providers',
|
||||
name: 'providers',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Proxy providers`
|
||||
String get proxyProviders {
|
||||
return Intl.message(
|
||||
'Proxy providers',
|
||||
name: 'proxyProviders',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Rule providers`
|
||||
String get ruleProviders {
|
||||
return Intl.message(
|
||||
'Rule providers',
|
||||
name: 'ruleProviders',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Override Dns`
|
||||
String get overrideDns {
|
||||
return Intl.message(
|
||||
'Override Dns',
|
||||
name: 'overrideDns',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Turning it on will override the DNS options in the profile`
|
||||
String get overrideDnsDesc {
|
||||
return Intl.message(
|
||||
'Turning it on will override the DNS options in the profile',
|
||||
name: 'overrideDnsDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Status`
|
||||
String get status {
|
||||
return Intl.message(
|
||||
'Status',
|
||||
name: 'status',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `System DNS will be used when turned off`
|
||||
String get statusDesc {
|
||||
return Intl.message(
|
||||
'System DNS will be used when turned off',
|
||||
name: 'statusDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Prioritize the use of DOH's http/3`
|
||||
String get preferH3Desc {
|
||||
return Intl.message(
|
||||
'Prioritize the use of DOH\'s http/3',
|
||||
name: 'preferH3Desc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Respect rules`
|
||||
String get respectRules {
|
||||
return Intl.message(
|
||||
'Respect rules',
|
||||
name: 'respectRules',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `DNS connection following rules, need to configure proxy-server-nameserver`
|
||||
String get respectRulesDesc {
|
||||
return Intl.message(
|
||||
'DNS connection following rules, need to configure proxy-server-nameserver',
|
||||
name: 'respectRulesDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `DNS mode`
|
||||
String get dnsMode {
|
||||
return Intl.message(
|
||||
'DNS mode',
|
||||
name: 'dnsMode',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Fakeip range`
|
||||
String get fakeipRange {
|
||||
return Intl.message(
|
||||
'Fakeip range',
|
||||
name: 'fakeipRange',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Fakeip filter`
|
||||
String get fakeipFilter {
|
||||
return Intl.message(
|
||||
'Fakeip filter',
|
||||
name: 'fakeipFilter',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Default nameserver`
|
||||
String get defaultNameserver {
|
||||
return Intl.message(
|
||||
'Default nameserver',
|
||||
name: 'defaultNameserver',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `For resolving DNS server`
|
||||
String get defaultNameserverDesc {
|
||||
return Intl.message(
|
||||
'For resolving DNS server',
|
||||
name: 'defaultNameserverDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Nameserver`
|
||||
String get nameserver {
|
||||
return Intl.message(
|
||||
'Nameserver',
|
||||
name: 'nameserver',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `For resolving domain`
|
||||
String get nameserverDesc {
|
||||
return Intl.message(
|
||||
'For resolving domain',
|
||||
name: 'nameserverDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Use hosts`
|
||||
String get useHosts {
|
||||
return Intl.message(
|
||||
'Use hosts',
|
||||
name: 'useHosts',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Use system hosts`
|
||||
String get useSystemHosts {
|
||||
return Intl.message(
|
||||
'Use system hosts',
|
||||
name: 'useSystemHosts',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Nameserver policy`
|
||||
String get nameserverPolicy {
|
||||
return Intl.message(
|
||||
'Nameserver policy',
|
||||
name: 'nameserverPolicy',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Specify the corresponding nameserver policy`
|
||||
String get nameserverPolicyDesc {
|
||||
return Intl.message(
|
||||
'Specify the corresponding nameserver policy',
|
||||
name: 'nameserverPolicyDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Proxy nameserver`
|
||||
String get proxyNameserver {
|
||||
return Intl.message(
|
||||
'Proxy nameserver',
|
||||
name: 'proxyNameserver',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Domain for resolving proxy nodes`
|
||||
String get proxyNameserverDesc {
|
||||
return Intl.message(
|
||||
'Domain for resolving proxy nodes',
|
||||
name: 'proxyNameserverDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Fallback`
|
||||
String get fallback {
|
||||
return Intl.message(
|
||||
'Fallback',
|
||||
name: 'fallback',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Generally use offshore DNS`
|
||||
String get fallbackDesc {
|
||||
return Intl.message(
|
||||
'Generally use offshore DNS',
|
||||
name: 'fallbackDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Fallback filter`
|
||||
String get fallbackFilter {
|
||||
return Intl.message(
|
||||
'Fallback filter',
|
||||
name: 'fallbackFilter',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Geoip code`
|
||||
String get geoipCode {
|
||||
return Intl.message(
|
||||
'Geoip code',
|
||||
name: 'geoipCode',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Ipcidr`
|
||||
String get ipcidr {
|
||||
return Intl.message(
|
||||
'Ipcidr',
|
||||
name: 'ipcidr',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Domain`
|
||||
String get domain {
|
||||
return Intl.message(
|
||||
'Domain',
|
||||
name: 'domain',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Reset Dns`
|
||||
String get resetDns {
|
||||
return Intl.message(
|
||||
'Reset Dns',
|
||||
name: 'resetDns',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Reset`
|
||||
String get reset {
|
||||
return Intl.message(
|
||||
'Reset',
|
||||
name: 'reset',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -2,9 +2,10 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/http.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';
|
||||
@@ -18,6 +19,7 @@ Future<void> main() async {
|
||||
clashCore.initMessage();
|
||||
globalState.packageInfo = await PackageInfo.fromPlatform();
|
||||
final config = await preferences.getConfig() ?? Config();
|
||||
globalState.autoRun = config.autoRun;
|
||||
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
|
||||
await android?.init();
|
||||
await window?.init(config.windowProps);
|
||||
@@ -30,11 +32,17 @@ Future<void> main() async {
|
||||
openLogs: config.openLogs,
|
||||
hasProxies: false,
|
||||
);
|
||||
globalState.updateTray(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
await globalState.init(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
HttpOverrides.global = FlClashHttpOverrides();
|
||||
runAppWithPreferences(
|
||||
const Application(),
|
||||
appState: appState,
|
||||
@@ -61,14 +69,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 +84,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 +108,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 +117,7 @@ Future<void> vpnService() async {
|
||||
TileListenerWithVpn(
|
||||
onStop: () async {
|
||||
await app?.tip(appLocalizations.stopVpn);
|
||||
await globalState.stopSystemProxy();
|
||||
await globalState.handleStop();
|
||||
clashCore.shutdown();
|
||||
exit(0);
|
||||
},
|
||||
@@ -130,13 +137,13 @@ class ServiceMessageHandler with ServiceMessageListener {
|
||||
final Function(Fd fd) _onProtect;
|
||||
final Function(Process process) _onProcess;
|
||||
final Function(String runTime) _onStarted;
|
||||
final Function(String groupName) _onLoaded;
|
||||
final Function(String providerName) _onLoaded;
|
||||
|
||||
const ServiceMessageHandler({
|
||||
required Function(Fd fd) onProtect,
|
||||
required Function(Process process) onProcess,
|
||||
required Function(String runTime) onStarted,
|
||||
required Function(String groupName) onLoaded,
|
||||
required Function(String providerName) onLoaded,
|
||||
}) : _onProtect = onProtect,
|
||||
_onProcess = onProcess,
|
||||
_onStarted = onStarted,
|
||||
@@ -158,8 +165,8 @@ class ServiceMessageHandler with ServiceMessageListener {
|
||||
}
|
||||
|
||||
@override
|
||||
onLoaded(String groupName) {
|
||||
_onLoaded(groupName);
|
||||
onLoaded(String providerName) {
|
||||
_onLoaded(providerName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -353,7 +353,8 @@ class AppState with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
setProvider(ExternalProvider provider) {
|
||||
setProvider(ExternalProvider? provider) {
|
||||
if(provider == null) return;
|
||||
final index = _providers.indexWhere((item) => item.name == provider.name);
|
||||
if (index == -1) return;
|
||||
_providers = List.from(_providers)..[index] = provider;
|
||||
|
||||
@@ -26,89 +26,94 @@ class Tun with _$Tun {
|
||||
factory Tun.fromJson(Map<String, Object?> json) => _$TunFromJson(json);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class Dns {
|
||||
bool enable;
|
||||
bool ipv6;
|
||||
@JsonKey(name: "default-nameserver")
|
||||
List<String> defaultNameserver;
|
||||
@JsonKey(name: "enhanced-mode")
|
||||
String enhancedMode;
|
||||
@JsonKey(name: "fake-ip-range")
|
||||
String fakeIpRange;
|
||||
@JsonKey(name: "use-hosts")
|
||||
bool useHosts;
|
||||
List<String> nameserver;
|
||||
List<String> fallback;
|
||||
@JsonKey(name: "fake-ip-filter")
|
||||
List<String> fakeIpFilter;
|
||||
@freezed
|
||||
class FallbackFilter with _$FallbackFilter {
|
||||
const factory FallbackFilter({
|
||||
@Default(true) bool geoip,
|
||||
@Default("CN") @JsonKey(name: "geoip-code") String geoipCode,
|
||||
@Default(["gfw"]) List<String> geosite,
|
||||
@Default(["240.0.0.0/4"]) List<String> ipcidr,
|
||||
@Default([
|
||||
"+.google.com",
|
||||
"+.facebook.com",
|
||||
"+.youtube.com",
|
||||
])
|
||||
List<String> domain,
|
||||
}) = _FallbackFilter;
|
||||
|
||||
Dns()
|
||||
: enable = true,
|
||||
ipv6 = false,
|
||||
defaultNameserver = [
|
||||
"223.5.5.5",
|
||||
"119.29.29.29",
|
||||
"8.8.4.4",
|
||||
"1.0.0.1",
|
||||
],
|
||||
enhancedMode = "fake-ip",
|
||||
fakeIpRange = "198.18.0.1/16",
|
||||
useHosts = true,
|
||||
nameserver = [
|
||||
"8.8.8.8",
|
||||
"114.114.114.114",
|
||||
"https://doh.pub/dns-query",
|
||||
"https://dns.alidns.com/dns-query",
|
||||
],
|
||||
fallback = [
|
||||
'https://doh.dns.sb/dns-query',
|
||||
'https://dns.cloudflare.com/dns-query',
|
||||
'https://dns.twnic.tw/dns-query',
|
||||
'tls://8.8.4.4:853',
|
||||
],
|
||||
fakeIpFilter = [
|
||||
// Stun Services
|
||||
"+.stun.*.*",
|
||||
"+.stun.*.*.*",
|
||||
"+.stun.*.*.*.*",
|
||||
"+.stun.*.*.*.*.*",
|
||||
factory FallbackFilter.fromJson(Map<String, Object?> json) =>
|
||||
_$FallbackFilterFromJson(json);
|
||||
}
|
||||
|
||||
// Google Voices
|
||||
"lens.l.google.com",
|
||||
@freezed
|
||||
class Dns with _$Dns {
|
||||
const factory Dns({
|
||||
@Default(true) bool enable,
|
||||
@Default(false) @JsonKey(name: "prefer-h3") bool preferH3,
|
||||
@Default(true) @JsonKey(name: "use-hosts") bool useHosts,
|
||||
@Default(true) @JsonKey(name: "use-system-hosts") bool useSystemHosts,
|
||||
@Default(true) @JsonKey(name: "respect-rules") bool respectRules,
|
||||
@Default(false) bool ipv6,
|
||||
@Default(["223.5.5.5"])
|
||||
@JsonKey(name: "default-nameserver")
|
||||
List<String> defaultNameserver,
|
||||
@Default(DnsMode.fakeIp)
|
||||
@JsonKey(name: "enhanced-mode")
|
||||
DnsMode enhancedMode,
|
||||
@Default("198.18.0.1/16")
|
||||
@JsonKey(name: "fake-ip-range")
|
||||
String fakeIpRange,
|
||||
@Default([
|
||||
"*.lan",
|
||||
"localhost.ptlogin2.qq.com",
|
||||
])
|
||||
@JsonKey(name: "fake-ip-filter")
|
||||
List<String> fakeIpFilter,
|
||||
@Default({
|
||||
"www.baidu.com": "114.114.114.114",
|
||||
"+.internal.crop.com": "10.0.0.1",
|
||||
"geosite:cn": "https://doh.pub/dns-query"
|
||||
})
|
||||
@JsonKey(name: "nameserver-policy")
|
||||
Map<String, String> nameserverPolicy,
|
||||
@Default([
|
||||
"https://doh.pub/dns-query",
|
||||
"https://dns.alidns.com/dns-query",
|
||||
])
|
||||
List<String> nameserver,
|
||||
@Default([
|
||||
"tls://8.8.4.4",
|
||||
"tls://1.1.1.1",
|
||||
])
|
||||
List<String> fallback,
|
||||
@Default([
|
||||
"https://doh.pub/dns-query",
|
||||
])
|
||||
@JsonKey(name: "proxy-server-nameserver")
|
||||
List<String> proxyServerNameserver,
|
||||
@Default(FallbackFilter())
|
||||
@JsonKey(name: "fallback-filter")
|
||||
FallbackFilter fallbackFilter,
|
||||
}) = _Dns;
|
||||
|
||||
// Nintendo Switch STUN
|
||||
"*.n.n.srv.nintendo.net",
|
||||
factory Dns.fromJson(Map<String, Object?> json) => _$DnsFromJson(json);
|
||||
|
||||
// PlayStation STUN
|
||||
"+.stun.playstation.net",
|
||||
|
||||
// XBox
|
||||
"xbox.*.*.microsoft.com",
|
||||
"*.*.xboxlive.com",
|
||||
|
||||
// Microsoft Captive Portal
|
||||
"*.msftncsi.com",
|
||||
"*.msftconnecttest.com",
|
||||
|
||||
// Bilibili CDN
|
||||
"*.mcdn.bilivideo.cn",
|
||||
|
||||
// Windows Default LAN WorkGroup
|
||||
"WORKGROUP",
|
||||
];
|
||||
|
||||
factory Dns.fromJson(Map<String, dynamic> json) {
|
||||
return _$DnsFromJson(json);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$DnsToJson(this);
|
||||
factory Dns.safeDnsFromJson(Map<String, Object?> json) {
|
||||
try {
|
||||
return Dns.fromJson(json);
|
||||
} catch (_) {
|
||||
return const Dns();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef GeoXMap = Map<String, String>;
|
||||
|
||||
typedef HostsMap = Map<String, String>;
|
||||
|
||||
const defaultMixedPort = 7890;
|
||||
const defaultKeepAliveInterval = 30;
|
||||
|
||||
@JsonSerializable()
|
||||
class ClashConfig extends ChangeNotifier {
|
||||
int _mixedPort;
|
||||
@@ -127,9 +132,10 @@ class ClashConfig extends ChangeNotifier {
|
||||
GeoXMap _geoXUrl;
|
||||
List<String> _rules;
|
||||
String? _globalRealUa;
|
||||
HostsMap _hosts;
|
||||
|
||||
ClashConfig()
|
||||
: _mixedPort = 7890,
|
||||
: _mixedPort = defaultMixedPort,
|
||||
_mode = Mode.rule,
|
||||
_ipv6 = false,
|
||||
_findProcessMode = FindProcessMode.off,
|
||||
@@ -140,12 +146,13 @@ class ClashConfig extends ChangeNotifier {
|
||||
_unifiedDelay = false,
|
||||
_geodataLoader = geodataLoaderMemconservative,
|
||||
_externalController = '',
|
||||
_keepAliveInterval = 30,
|
||||
_dns = Dns(),
|
||||
_keepAliveInterval = defaultKeepAliveInterval,
|
||||
_dns = const Dns(),
|
||||
_geoXUrl = defaultGeoXMap,
|
||||
_rules = [];
|
||||
_rules = [],
|
||||
_hosts = {};
|
||||
|
||||
@JsonKey(name: "mixed-port", defaultValue: 7890)
|
||||
@JsonKey(name: "mixed-port", defaultValue: defaultMixedPort)
|
||||
int get mixedPort => _mixedPort;
|
||||
|
||||
set mixedPort(int value) {
|
||||
@@ -205,7 +212,7 @@ class ClashConfig extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(name: "keep-alive-interval", defaultValue: 30)
|
||||
@JsonKey(name: "keep-alive-interval", defaultValue: defaultKeepAliveInterval)
|
||||
int get keepAliveInterval => _keepAliveInterval;
|
||||
|
||||
set keepAliveInterval(int value) {
|
||||
@@ -269,6 +276,7 @@ class ClashConfig extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(fromJson: Dns.safeDnsFromJson)
|
||||
Dns get dns => _dns;
|
||||
|
||||
set dns(Dns value) {
|
||||
@@ -316,10 +324,21 @@ class ClashConfig extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: {})
|
||||
HostsMap get hosts => _hosts;
|
||||
|
||||
set hosts(HostsMap value) {
|
||||
if (!const MapEquality<String, String>().equals(value, _hosts)) {
|
||||
_hosts = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
update([ClashConfig? clashConfig]) {
|
||||
if (clashConfig != null) {
|
||||
_mixedPort = clashConfig._mixedPort;
|
||||
_allowLan = clashConfig._allowLan;
|
||||
_hosts = clashConfig._hosts;
|
||||
_mode = clashConfig._mode;
|
||||
_logLevel = clashConfig._logLevel;
|
||||
_tun = clashConfig._tun;
|
||||
|
||||
@@ -18,6 +18,7 @@ class AccessControl with _$AccessControl {
|
||||
@Default(AccessControlMode.rejectSelected) AccessControlMode mode,
|
||||
@Default([]) List<String> acceptList,
|
||||
@Default([]) List<String> rejectList,
|
||||
@Default(AccessSortType.none) AccessSortType sort,
|
||||
@Default(true) bool isFilterSystemApp,
|
||||
}) = _AccessControl;
|
||||
|
||||
@@ -25,18 +26,38 @@ class AccessControl with _$AccessControl {
|
||||
_$AccessControlFromJson(json);
|
||||
}
|
||||
|
||||
extension AccessControlExt on AccessControl {
|
||||
List<String> get currentList => switch (mode) {
|
||||
AccessControlMode.acceptSelected => acceptList,
|
||||
AccessControlMode.rejectSelected => rejectList,
|
||||
};
|
||||
}
|
||||
|
||||
@freezed
|
||||
class CoreState with _$CoreState {
|
||||
const factory CoreState({
|
||||
AccessControl? accessControl,
|
||||
required String currentProfileName,
|
||||
required bool enable,
|
||||
required bool allowBypass,
|
||||
required bool systemProxy,
|
||||
required int mixedPort,
|
||||
required bool onlyProxy,
|
||||
}) = _CoreState;
|
||||
|
||||
factory CoreState.fromJson(Map<String, Object?> json) => _$CoreStateFromJson(json);
|
||||
factory CoreState.fromJson(Map<String, Object?> json) =>
|
||||
_$CoreStateFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class VPNState with _$VPNState {
|
||||
const factory VPNState({
|
||||
required AccessControl? accessControl,
|
||||
required VpnProps vpnProps,
|
||||
}) = _VPNState;
|
||||
|
||||
factory VPNState.fromJson(Map<String, Object?> json) =>
|
||||
_$VPNStateFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -49,10 +70,45 @@ 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);
|
||||
}
|
||||
|
||||
const defaultCustomFontSizeScale = 1.0;
|
||||
|
||||
const defaultScaleProps = ScaleProps();
|
||||
|
||||
@freezed
|
||||
class ScaleProps with _$ScaleProps {
|
||||
const factory ScaleProps({
|
||||
@Default(false) bool custom,
|
||||
@Default(defaultCustomFontSizeScale) double scale,
|
||||
}) = _ScaleProps;
|
||||
|
||||
factory ScaleProps.fromJson(Map<String, Object?>? json) =>
|
||||
json == null ? defaultScaleProps : _$ScalePropsFromJson(json);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class Config extends ChangeNotifier {
|
||||
@@ -72,18 +128,21 @@ class Config extends ChangeNotifier {
|
||||
AccessControl _accessControl;
|
||||
bool _isAnimateToPage;
|
||||
bool _autoCheckUpdate;
|
||||
bool _allowBypass;
|
||||
bool _systemProxy;
|
||||
bool _isExclude;
|
||||
DAV? _dav;
|
||||
bool _isCloseConnections;
|
||||
ProxiesType _proxiesType;
|
||||
ProxyCardType _proxyCardType;
|
||||
int _proxiesColumns;
|
||||
ProxiesLayout _proxiesLayout;
|
||||
String _testUrl;
|
||||
WindowProps _windowProps;
|
||||
bool _onlyProxy;
|
||||
bool _prueBlack;
|
||||
VpnProps _vpnProps;
|
||||
ScaleProps _scaleProps;
|
||||
DesktopProps _desktopProps;
|
||||
bool _showLabel;
|
||||
bool _overrideDns;
|
||||
|
||||
Config()
|
||||
: _profiles = [],
|
||||
@@ -99,18 +158,21 @@ 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,
|
||||
_proxiesColumns = 2,
|
||||
_prueBlack = false,
|
||||
_onlyProxy = false;
|
||||
_onlyProxy = false,
|
||||
_proxiesLayout = ProxiesLayout.standard,
|
||||
_vpnProps = const VpnProps(),
|
||||
_desktopProps = const DesktopProps(),
|
||||
_showLabel = false,
|
||||
_overrideDns = false,
|
||||
_scaleProps = const ScaleProps();
|
||||
|
||||
deleteProfileById(String id) {
|
||||
_profiles = profiles.where((element) => element.id != id).toList();
|
||||
@@ -312,6 +374,16 @@ class Config extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: ProxiesLayout.standard)
|
||||
ProxiesLayout get proxiesLayout => _proxiesLayout;
|
||||
|
||||
set proxiesLayout(ProxiesLayout value) {
|
||||
if (_proxiesLayout != value) {
|
||||
_proxiesLayout = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
bool get isMinimizeOnExit => _isMinimizeOnExit;
|
||||
|
||||
@@ -390,30 +462,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;
|
||||
@@ -473,16 +521,6 @@ class Config extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: 2)
|
||||
int get proxiesColumns => _proxiesColumns;
|
||||
|
||||
set proxiesColumns(int value) {
|
||||
if (_proxiesColumns != value) {
|
||||
_proxiesColumns = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(name: "test-url", defaultValue: defaultTestUrl)
|
||||
String get testUrl => _testUrl;
|
||||
|
||||
@@ -512,6 +550,53 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
ScaleProps get scaleProps => _scaleProps;
|
||||
|
||||
set scaleProps(ScaleProps value) {
|
||||
if (_scaleProps != value) {
|
||||
_scaleProps = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool get showLabel => _showLabel;
|
||||
|
||||
set showLabel(bool value) {
|
||||
if (_showLabel != value) {
|
||||
_showLabel = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool get overrideDns => _overrideDns;
|
||||
|
||||
set overrideDns(bool value) {
|
||||
if (_overrideDns != value) {
|
||||
_overrideDns = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
update([
|
||||
Config? config,
|
||||
RecoveryOption recoveryOptions = RecoveryOption.all,
|
||||
@@ -530,13 +615,13 @@ class Config extends ChangeNotifier {
|
||||
_isCloseConnections = config._isCloseConnections;
|
||||
_isCompatible = config._isCompatible;
|
||||
_autoLaunch = config._autoLaunch;
|
||||
_dav = config._dav;
|
||||
_silentLaunch = config._silentLaunch;
|
||||
_autoRun = config._autoRun;
|
||||
_proxiesType = config._proxiesType;
|
||||
_openLog = config._openLog;
|
||||
_themeMode = config._themeMode;
|
||||
_locale = config._locale;
|
||||
_allowBypass = config._allowBypass;
|
||||
_primaryColor = config._primaryColor;
|
||||
_proxiesSortType = config._proxiesSortType;
|
||||
_isMinimizeOnExit = config._isMinimizeOnExit;
|
||||
@@ -548,6 +633,9 @@ class Config extends ChangeNotifier {
|
||||
_testUrl = config._testUrl;
|
||||
_isExclude = config._isExclude;
|
||||
_windowProps = config._windowProps;
|
||||
_vpnProps = config._vpnProps;
|
||||
_overrideDns = config._overrideDns;
|
||||
_desktopProps = config._desktopProps;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -4,14 +4,16 @@ part 'generated/dav.g.dart';
|
||||
|
||||
part 'generated/dav.freezed.dart';
|
||||
|
||||
const defaultDavFileName = "backup.zip";
|
||||
|
||||
@freezed
|
||||
class DAV with _$DAV{
|
||||
class DAV with _$DAV {
|
||||
const factory DAV({
|
||||
required String uri,
|
||||
required String user,
|
||||
required String password,
|
||||
@Default(defaultDavFileName) String fileName,
|
||||
}) = _DAV;
|
||||
|
||||
factory DAV.fromJson(Map<String, Object?> json) =>
|
||||
_$DAVFromJson(json);
|
||||
}
|
||||
factory DAV.fromJson(Map<String, Object?> json) => _$DAVFromJson(json);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ class ConfigExtendedParams with _$ConfigExtendedParams {
|
||||
@JsonKey(name: "is-patch") required bool isPatch,
|
||||
@JsonKey(name: "is-compatible") required bool isCompatible,
|
||||
@JsonKey(name: "selected-map") required SelectedMap selectedMap,
|
||||
@JsonKey(name: "override-dns") required bool overrideDns,
|
||||
@JsonKey(name: "test-url") required String testUrl,
|
||||
}) = _ConfigExtendedParams;
|
||||
|
||||
|
||||
@@ -220,3 +220,818 @@ abstract class _Tun implements Tun {
|
||||
_$$TunImplCopyWith<_$TunImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
FallbackFilter _$FallbackFilterFromJson(Map<String, dynamic> json) {
|
||||
return _FallbackFilter.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$FallbackFilter {
|
||||
bool get geoip => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "geoip-code")
|
||||
String get geoipCode => throw _privateConstructorUsedError;
|
||||
List<String> get geosite => throw _privateConstructorUsedError;
|
||||
List<String> get ipcidr => throw _privateConstructorUsedError;
|
||||
List<String> get domain => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$FallbackFilterCopyWith<FallbackFilter> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $FallbackFilterCopyWith<$Res> {
|
||||
factory $FallbackFilterCopyWith(
|
||||
FallbackFilter value, $Res Function(FallbackFilter) then) =
|
||||
_$FallbackFilterCopyWithImpl<$Res, FallbackFilter>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{bool geoip,
|
||||
@JsonKey(name: "geoip-code") String geoipCode,
|
||||
List<String> geosite,
|
||||
List<String> ipcidr,
|
||||
List<String> domain});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$FallbackFilterCopyWithImpl<$Res, $Val extends FallbackFilter>
|
||||
implements $FallbackFilterCopyWith<$Res> {
|
||||
_$FallbackFilterCopyWithImpl(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? geoip = null,
|
||||
Object? geoipCode = null,
|
||||
Object? geosite = null,
|
||||
Object? ipcidr = null,
|
||||
Object? domain = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
geoip: null == geoip
|
||||
? _value.geoip
|
||||
: geoip // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
geoipCode: null == geoipCode
|
||||
? _value.geoipCode
|
||||
: geoipCode // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
geosite: null == geosite
|
||||
? _value.geosite
|
||||
: geosite // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
ipcidr: null == ipcidr
|
||||
? _value.ipcidr
|
||||
: ipcidr // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
domain: null == domain
|
||||
? _value.domain
|
||||
: domain // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$FallbackFilterImplCopyWith<$Res>
|
||||
implements $FallbackFilterCopyWith<$Res> {
|
||||
factory _$$FallbackFilterImplCopyWith(_$FallbackFilterImpl value,
|
||||
$Res Function(_$FallbackFilterImpl) then) =
|
||||
__$$FallbackFilterImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{bool geoip,
|
||||
@JsonKey(name: "geoip-code") String geoipCode,
|
||||
List<String> geosite,
|
||||
List<String> ipcidr,
|
||||
List<String> domain});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$FallbackFilterImplCopyWithImpl<$Res>
|
||||
extends _$FallbackFilterCopyWithImpl<$Res, _$FallbackFilterImpl>
|
||||
implements _$$FallbackFilterImplCopyWith<$Res> {
|
||||
__$$FallbackFilterImplCopyWithImpl(
|
||||
_$FallbackFilterImpl _value, $Res Function(_$FallbackFilterImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? geoip = null,
|
||||
Object? geoipCode = null,
|
||||
Object? geosite = null,
|
||||
Object? ipcidr = null,
|
||||
Object? domain = null,
|
||||
}) {
|
||||
return _then(_$FallbackFilterImpl(
|
||||
geoip: null == geoip
|
||||
? _value.geoip
|
||||
: geoip // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
geoipCode: null == geoipCode
|
||||
? _value.geoipCode
|
||||
: geoipCode // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
geosite: null == geosite
|
||||
? _value._geosite
|
||||
: geosite // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
ipcidr: null == ipcidr
|
||||
? _value._ipcidr
|
||||
: ipcidr // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
domain: null == domain
|
||||
? _value._domain
|
||||
: domain // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$FallbackFilterImpl implements _FallbackFilter {
|
||||
const _$FallbackFilterImpl(
|
||||
{this.geoip = true,
|
||||
@JsonKey(name: "geoip-code") this.geoipCode = "CN",
|
||||
final List<String> geosite = const ["gfw"],
|
||||
final List<String> ipcidr = const ["240.0.0.0/4"],
|
||||
final List<String> domain = const [
|
||||
"+.google.com",
|
||||
"+.facebook.com",
|
||||
"+.youtube.com"
|
||||
]})
|
||||
: _geosite = geosite,
|
||||
_ipcidr = ipcidr,
|
||||
_domain = domain;
|
||||
|
||||
factory _$FallbackFilterImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$FallbackFilterImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool geoip;
|
||||
@override
|
||||
@JsonKey(name: "geoip-code")
|
||||
final String geoipCode;
|
||||
final List<String> _geosite;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get geosite {
|
||||
if (_geosite is EqualUnmodifiableListView) return _geosite;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_geosite);
|
||||
}
|
||||
|
||||
final List<String> _ipcidr;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get ipcidr {
|
||||
if (_ipcidr is EqualUnmodifiableListView) return _ipcidr;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_ipcidr);
|
||||
}
|
||||
|
||||
final List<String> _domain;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get domain {
|
||||
if (_domain is EqualUnmodifiableListView) return _domain;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_domain);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'FallbackFilter(geoip: $geoip, geoipCode: $geoipCode, geosite: $geosite, ipcidr: $ipcidr, domain: $domain)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$FallbackFilterImpl &&
|
||||
(identical(other.geoip, geoip) || other.geoip == geoip) &&
|
||||
(identical(other.geoipCode, geoipCode) ||
|
||||
other.geoipCode == geoipCode) &&
|
||||
const DeepCollectionEquality().equals(other._geosite, _geosite) &&
|
||||
const DeepCollectionEquality().equals(other._ipcidr, _ipcidr) &&
|
||||
const DeepCollectionEquality().equals(other._domain, _domain));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
geoip,
|
||||
geoipCode,
|
||||
const DeepCollectionEquality().hash(_geosite),
|
||||
const DeepCollectionEquality().hash(_ipcidr),
|
||||
const DeepCollectionEquality().hash(_domain));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$FallbackFilterImplCopyWith<_$FallbackFilterImpl> get copyWith =>
|
||||
__$$FallbackFilterImplCopyWithImpl<_$FallbackFilterImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$FallbackFilterImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _FallbackFilter implements FallbackFilter {
|
||||
const factory _FallbackFilter(
|
||||
{final bool geoip,
|
||||
@JsonKey(name: "geoip-code") final String geoipCode,
|
||||
final List<String> geosite,
|
||||
final List<String> ipcidr,
|
||||
final List<String> domain}) = _$FallbackFilterImpl;
|
||||
|
||||
factory _FallbackFilter.fromJson(Map<String, dynamic> json) =
|
||||
_$FallbackFilterImpl.fromJson;
|
||||
|
||||
@override
|
||||
bool get geoip;
|
||||
@override
|
||||
@JsonKey(name: "geoip-code")
|
||||
String get geoipCode;
|
||||
@override
|
||||
List<String> get geosite;
|
||||
@override
|
||||
List<String> get ipcidr;
|
||||
@override
|
||||
List<String> get domain;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$FallbackFilterImplCopyWith<_$FallbackFilterImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
Dns _$DnsFromJson(Map<String, dynamic> json) {
|
||||
return _Dns.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Dns {
|
||||
bool get enable => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "prefer-h3")
|
||||
bool get preferH3 => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "use-hosts")
|
||||
bool get useHosts => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "use-system-hosts")
|
||||
bool get useSystemHosts => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "respect-rules")
|
||||
bool get respectRules => throw _privateConstructorUsedError;
|
||||
bool get ipv6 => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "default-nameserver")
|
||||
List<String> get defaultNameserver => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "enhanced-mode")
|
||||
DnsMode get enhancedMode => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "fake-ip-range")
|
||||
String get fakeIpRange => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "fake-ip-filter")
|
||||
List<String> get fakeIpFilter => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "nameserver-policy")
|
||||
Map<String, String> get nameserverPolicy =>
|
||||
throw _privateConstructorUsedError;
|
||||
List<String> get nameserver => throw _privateConstructorUsedError;
|
||||
List<String> get fallback => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "proxy-server-nameserver")
|
||||
List<String> get proxyServerNameserver => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "fallback-filter")
|
||||
FallbackFilter get fallbackFilter => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$DnsCopyWith<Dns> get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $DnsCopyWith<$Res> {
|
||||
factory $DnsCopyWith(Dns value, $Res Function(Dns) then) =
|
||||
_$DnsCopyWithImpl<$Res, Dns>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{bool enable,
|
||||
@JsonKey(name: "prefer-h3") bool preferH3,
|
||||
@JsonKey(name: "use-hosts") bool useHosts,
|
||||
@JsonKey(name: "use-system-hosts") bool useSystemHosts,
|
||||
@JsonKey(name: "respect-rules") bool respectRules,
|
||||
bool ipv6,
|
||||
@JsonKey(name: "default-nameserver") List<String> defaultNameserver,
|
||||
@JsonKey(name: "enhanced-mode") DnsMode enhancedMode,
|
||||
@JsonKey(name: "fake-ip-range") String fakeIpRange,
|
||||
@JsonKey(name: "fake-ip-filter") List<String> fakeIpFilter,
|
||||
@JsonKey(name: "nameserver-policy") Map<String, String> nameserverPolicy,
|
||||
List<String> nameserver,
|
||||
List<String> fallback,
|
||||
@JsonKey(name: "proxy-server-nameserver")
|
||||
List<String> proxyServerNameserver,
|
||||
@JsonKey(name: "fallback-filter") FallbackFilter fallbackFilter});
|
||||
|
||||
$FallbackFilterCopyWith<$Res> get fallbackFilter;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$DnsCopyWithImpl<$Res, $Val extends Dns> implements $DnsCopyWith<$Res> {
|
||||
_$DnsCopyWithImpl(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? preferH3 = null,
|
||||
Object? useHosts = null,
|
||||
Object? useSystemHosts = null,
|
||||
Object? respectRules = null,
|
||||
Object? ipv6 = null,
|
||||
Object? defaultNameserver = null,
|
||||
Object? enhancedMode = null,
|
||||
Object? fakeIpRange = null,
|
||||
Object? fakeIpFilter = null,
|
||||
Object? nameserverPolicy = null,
|
||||
Object? nameserver = null,
|
||||
Object? fallback = null,
|
||||
Object? proxyServerNameserver = null,
|
||||
Object? fallbackFilter = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
enable: null == enable
|
||||
? _value.enable
|
||||
: enable // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
preferH3: null == preferH3
|
||||
? _value.preferH3
|
||||
: preferH3 // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
useHosts: null == useHosts
|
||||
? _value.useHosts
|
||||
: useHosts // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
useSystemHosts: null == useSystemHosts
|
||||
? _value.useSystemHosts
|
||||
: useSystemHosts // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
respectRules: null == respectRules
|
||||
? _value.respectRules
|
||||
: respectRules // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
ipv6: null == ipv6
|
||||
? _value.ipv6
|
||||
: ipv6 // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
defaultNameserver: null == defaultNameserver
|
||||
? _value.defaultNameserver
|
||||
: defaultNameserver // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
enhancedMode: null == enhancedMode
|
||||
? _value.enhancedMode
|
||||
: enhancedMode // ignore: cast_nullable_to_non_nullable
|
||||
as DnsMode,
|
||||
fakeIpRange: null == fakeIpRange
|
||||
? _value.fakeIpRange
|
||||
: fakeIpRange // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
fakeIpFilter: null == fakeIpFilter
|
||||
? _value.fakeIpFilter
|
||||
: fakeIpFilter // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
nameserverPolicy: null == nameserverPolicy
|
||||
? _value.nameserverPolicy
|
||||
: nameserverPolicy // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>,
|
||||
nameserver: null == nameserver
|
||||
? _value.nameserver
|
||||
: nameserver // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
fallback: null == fallback
|
||||
? _value.fallback
|
||||
: fallback // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
proxyServerNameserver: null == proxyServerNameserver
|
||||
? _value.proxyServerNameserver
|
||||
: proxyServerNameserver // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
fallbackFilter: null == fallbackFilter
|
||||
? _value.fallbackFilter
|
||||
: fallbackFilter // ignore: cast_nullable_to_non_nullable
|
||||
as FallbackFilter,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$FallbackFilterCopyWith<$Res> get fallbackFilter {
|
||||
return $FallbackFilterCopyWith<$Res>(_value.fallbackFilter, (value) {
|
||||
return _then(_value.copyWith(fallbackFilter: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$DnsImplCopyWith<$Res> implements $DnsCopyWith<$Res> {
|
||||
factory _$$DnsImplCopyWith(_$DnsImpl value, $Res Function(_$DnsImpl) then) =
|
||||
__$$DnsImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{bool enable,
|
||||
@JsonKey(name: "prefer-h3") bool preferH3,
|
||||
@JsonKey(name: "use-hosts") bool useHosts,
|
||||
@JsonKey(name: "use-system-hosts") bool useSystemHosts,
|
||||
@JsonKey(name: "respect-rules") bool respectRules,
|
||||
bool ipv6,
|
||||
@JsonKey(name: "default-nameserver") List<String> defaultNameserver,
|
||||
@JsonKey(name: "enhanced-mode") DnsMode enhancedMode,
|
||||
@JsonKey(name: "fake-ip-range") String fakeIpRange,
|
||||
@JsonKey(name: "fake-ip-filter") List<String> fakeIpFilter,
|
||||
@JsonKey(name: "nameserver-policy") Map<String, String> nameserverPolicy,
|
||||
List<String> nameserver,
|
||||
List<String> fallback,
|
||||
@JsonKey(name: "proxy-server-nameserver")
|
||||
List<String> proxyServerNameserver,
|
||||
@JsonKey(name: "fallback-filter") FallbackFilter fallbackFilter});
|
||||
|
||||
@override
|
||||
$FallbackFilterCopyWith<$Res> get fallbackFilter;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$DnsImplCopyWithImpl<$Res> extends _$DnsCopyWithImpl<$Res, _$DnsImpl>
|
||||
implements _$$DnsImplCopyWith<$Res> {
|
||||
__$$DnsImplCopyWithImpl(_$DnsImpl _value, $Res Function(_$DnsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? enable = null,
|
||||
Object? preferH3 = null,
|
||||
Object? useHosts = null,
|
||||
Object? useSystemHosts = null,
|
||||
Object? respectRules = null,
|
||||
Object? ipv6 = null,
|
||||
Object? defaultNameserver = null,
|
||||
Object? enhancedMode = null,
|
||||
Object? fakeIpRange = null,
|
||||
Object? fakeIpFilter = null,
|
||||
Object? nameserverPolicy = null,
|
||||
Object? nameserver = null,
|
||||
Object? fallback = null,
|
||||
Object? proxyServerNameserver = null,
|
||||
Object? fallbackFilter = null,
|
||||
}) {
|
||||
return _then(_$DnsImpl(
|
||||
enable: null == enable
|
||||
? _value.enable
|
||||
: enable // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
preferH3: null == preferH3
|
||||
? _value.preferH3
|
||||
: preferH3 // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
useHosts: null == useHosts
|
||||
? _value.useHosts
|
||||
: useHosts // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
useSystemHosts: null == useSystemHosts
|
||||
? _value.useSystemHosts
|
||||
: useSystemHosts // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
respectRules: null == respectRules
|
||||
? _value.respectRules
|
||||
: respectRules // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
ipv6: null == ipv6
|
||||
? _value.ipv6
|
||||
: ipv6 // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
defaultNameserver: null == defaultNameserver
|
||||
? _value._defaultNameserver
|
||||
: defaultNameserver // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
enhancedMode: null == enhancedMode
|
||||
? _value.enhancedMode
|
||||
: enhancedMode // ignore: cast_nullable_to_non_nullable
|
||||
as DnsMode,
|
||||
fakeIpRange: null == fakeIpRange
|
||||
? _value.fakeIpRange
|
||||
: fakeIpRange // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
fakeIpFilter: null == fakeIpFilter
|
||||
? _value._fakeIpFilter
|
||||
: fakeIpFilter // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
nameserverPolicy: null == nameserverPolicy
|
||||
? _value._nameserverPolicy
|
||||
: nameserverPolicy // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>,
|
||||
nameserver: null == nameserver
|
||||
? _value._nameserver
|
||||
: nameserver // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
fallback: null == fallback
|
||||
? _value._fallback
|
||||
: fallback // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
proxyServerNameserver: null == proxyServerNameserver
|
||||
? _value._proxyServerNameserver
|
||||
: proxyServerNameserver // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
fallbackFilter: null == fallbackFilter
|
||||
? _value.fallbackFilter
|
||||
: fallbackFilter // ignore: cast_nullable_to_non_nullable
|
||||
as FallbackFilter,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$DnsImpl implements _Dns {
|
||||
const _$DnsImpl(
|
||||
{this.enable = true,
|
||||
@JsonKey(name: "prefer-h3") this.preferH3 = false,
|
||||
@JsonKey(name: "use-hosts") this.useHosts = true,
|
||||
@JsonKey(name: "use-system-hosts") this.useSystemHosts = true,
|
||||
@JsonKey(name: "respect-rules") this.respectRules = true,
|
||||
this.ipv6 = false,
|
||||
@JsonKey(name: "default-nameserver")
|
||||
final List<String> defaultNameserver = const ["223.5.5.5"],
|
||||
@JsonKey(name: "enhanced-mode") this.enhancedMode = DnsMode.fakeIp,
|
||||
@JsonKey(name: "fake-ip-range") this.fakeIpRange = "198.18.0.1/16",
|
||||
@JsonKey(name: "fake-ip-filter") final List<String> fakeIpFilter = const [
|
||||
"*.lan",
|
||||
"localhost.ptlogin2.qq.com"
|
||||
],
|
||||
@JsonKey(name: "nameserver-policy")
|
||||
final Map<String, String> nameserverPolicy = const {
|
||||
"www.baidu.com": "114.114.114.114",
|
||||
"+.internal.crop.com": "10.0.0.1",
|
||||
"geosite:cn": "https://doh.pub/dns-query"
|
||||
},
|
||||
final List<String> nameserver = const [
|
||||
"https://doh.pub/dns-query",
|
||||
"https://dns.alidns.com/dns-query"
|
||||
],
|
||||
final List<String> fallback = const ["tls://8.8.4.4", "tls://1.1.1.1"],
|
||||
@JsonKey(name: "proxy-server-nameserver")
|
||||
final List<String> proxyServerNameserver = const [
|
||||
"https://doh.pub/dns-query"
|
||||
],
|
||||
@JsonKey(name: "fallback-filter")
|
||||
this.fallbackFilter = const FallbackFilter()})
|
||||
: _defaultNameserver = defaultNameserver,
|
||||
_fakeIpFilter = fakeIpFilter,
|
||||
_nameserverPolicy = nameserverPolicy,
|
||||
_nameserver = nameserver,
|
||||
_fallback = fallback,
|
||||
_proxyServerNameserver = proxyServerNameserver;
|
||||
|
||||
factory _$DnsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$DnsImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool enable;
|
||||
@override
|
||||
@JsonKey(name: "prefer-h3")
|
||||
final bool preferH3;
|
||||
@override
|
||||
@JsonKey(name: "use-hosts")
|
||||
final bool useHosts;
|
||||
@override
|
||||
@JsonKey(name: "use-system-hosts")
|
||||
final bool useSystemHosts;
|
||||
@override
|
||||
@JsonKey(name: "respect-rules")
|
||||
final bool respectRules;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool ipv6;
|
||||
final List<String> _defaultNameserver;
|
||||
@override
|
||||
@JsonKey(name: "default-nameserver")
|
||||
List<String> get defaultNameserver {
|
||||
if (_defaultNameserver is EqualUnmodifiableListView)
|
||||
return _defaultNameserver;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_defaultNameserver);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey(name: "enhanced-mode")
|
||||
final DnsMode enhancedMode;
|
||||
@override
|
||||
@JsonKey(name: "fake-ip-range")
|
||||
final String fakeIpRange;
|
||||
final List<String> _fakeIpFilter;
|
||||
@override
|
||||
@JsonKey(name: "fake-ip-filter")
|
||||
List<String> get fakeIpFilter {
|
||||
if (_fakeIpFilter is EqualUnmodifiableListView) return _fakeIpFilter;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_fakeIpFilter);
|
||||
}
|
||||
|
||||
final Map<String, String> _nameserverPolicy;
|
||||
@override
|
||||
@JsonKey(name: "nameserver-policy")
|
||||
Map<String, String> get nameserverPolicy {
|
||||
if (_nameserverPolicy is EqualUnmodifiableMapView) return _nameserverPolicy;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_nameserverPolicy);
|
||||
}
|
||||
|
||||
final List<String> _nameserver;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get nameserver {
|
||||
if (_nameserver is EqualUnmodifiableListView) return _nameserver;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_nameserver);
|
||||
}
|
||||
|
||||
final List<String> _fallback;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get fallback {
|
||||
if (_fallback is EqualUnmodifiableListView) return _fallback;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_fallback);
|
||||
}
|
||||
|
||||
final List<String> _proxyServerNameserver;
|
||||
@override
|
||||
@JsonKey(name: "proxy-server-nameserver")
|
||||
List<String> get proxyServerNameserver {
|
||||
if (_proxyServerNameserver is EqualUnmodifiableListView)
|
||||
return _proxyServerNameserver;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_proxyServerNameserver);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey(name: "fallback-filter")
|
||||
final FallbackFilter fallbackFilter;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Dns(enable: $enable, preferH3: $preferH3, useHosts: $useHosts, useSystemHosts: $useSystemHosts, respectRules: $respectRules, ipv6: $ipv6, defaultNameserver: $defaultNameserver, enhancedMode: $enhancedMode, fakeIpRange: $fakeIpRange, fakeIpFilter: $fakeIpFilter, nameserverPolicy: $nameserverPolicy, nameserver: $nameserver, fallback: $fallback, proxyServerNameserver: $proxyServerNameserver, fallbackFilter: $fallbackFilter)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$DnsImpl &&
|
||||
(identical(other.enable, enable) || other.enable == enable) &&
|
||||
(identical(other.preferH3, preferH3) ||
|
||||
other.preferH3 == preferH3) &&
|
||||
(identical(other.useHosts, useHosts) ||
|
||||
other.useHosts == useHosts) &&
|
||||
(identical(other.useSystemHosts, useSystemHosts) ||
|
||||
other.useSystemHosts == useSystemHosts) &&
|
||||
(identical(other.respectRules, respectRules) ||
|
||||
other.respectRules == respectRules) &&
|
||||
(identical(other.ipv6, ipv6) || other.ipv6 == ipv6) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._defaultNameserver, _defaultNameserver) &&
|
||||
(identical(other.enhancedMode, enhancedMode) ||
|
||||
other.enhancedMode == enhancedMode) &&
|
||||
(identical(other.fakeIpRange, fakeIpRange) ||
|
||||
other.fakeIpRange == fakeIpRange) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._fakeIpFilter, _fakeIpFilter) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._nameserverPolicy, _nameserverPolicy) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._nameserver, _nameserver) &&
|
||||
const DeepCollectionEquality().equals(other._fallback, _fallback) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._proxyServerNameserver, _proxyServerNameserver) &&
|
||||
(identical(other.fallbackFilter, fallbackFilter) ||
|
||||
other.fallbackFilter == fallbackFilter));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
enable,
|
||||
preferH3,
|
||||
useHosts,
|
||||
useSystemHosts,
|
||||
respectRules,
|
||||
ipv6,
|
||||
const DeepCollectionEquality().hash(_defaultNameserver),
|
||||
enhancedMode,
|
||||
fakeIpRange,
|
||||
const DeepCollectionEquality().hash(_fakeIpFilter),
|
||||
const DeepCollectionEquality().hash(_nameserverPolicy),
|
||||
const DeepCollectionEquality().hash(_nameserver),
|
||||
const DeepCollectionEquality().hash(_fallback),
|
||||
const DeepCollectionEquality().hash(_proxyServerNameserver),
|
||||
fallbackFilter);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$DnsImplCopyWith<_$DnsImpl> get copyWith =>
|
||||
__$$DnsImplCopyWithImpl<_$DnsImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$DnsImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Dns implements Dns {
|
||||
const factory _Dns(
|
||||
{final bool enable,
|
||||
@JsonKey(name: "prefer-h3") final bool preferH3,
|
||||
@JsonKey(name: "use-hosts") final bool useHosts,
|
||||
@JsonKey(name: "use-system-hosts") final bool useSystemHosts,
|
||||
@JsonKey(name: "respect-rules") final bool respectRules,
|
||||
final bool ipv6,
|
||||
@JsonKey(name: "default-nameserver") final List<String> defaultNameserver,
|
||||
@JsonKey(name: "enhanced-mode") final DnsMode enhancedMode,
|
||||
@JsonKey(name: "fake-ip-range") final String fakeIpRange,
|
||||
@JsonKey(name: "fake-ip-filter") final List<String> fakeIpFilter,
|
||||
@JsonKey(name: "nameserver-policy")
|
||||
final Map<String, String> nameserverPolicy,
|
||||
final List<String> nameserver,
|
||||
final List<String> fallback,
|
||||
@JsonKey(name: "proxy-server-nameserver")
|
||||
final List<String> proxyServerNameserver,
|
||||
@JsonKey(name: "fallback-filter")
|
||||
final FallbackFilter fallbackFilter}) = _$DnsImpl;
|
||||
|
||||
factory _Dns.fromJson(Map<String, dynamic> json) = _$DnsImpl.fromJson;
|
||||
|
||||
@override
|
||||
bool get enable;
|
||||
@override
|
||||
@JsonKey(name: "prefer-h3")
|
||||
bool get preferH3;
|
||||
@override
|
||||
@JsonKey(name: "use-hosts")
|
||||
bool get useHosts;
|
||||
@override
|
||||
@JsonKey(name: "use-system-hosts")
|
||||
bool get useSystemHosts;
|
||||
@override
|
||||
@JsonKey(name: "respect-rules")
|
||||
bool get respectRules;
|
||||
@override
|
||||
bool get ipv6;
|
||||
@override
|
||||
@JsonKey(name: "default-nameserver")
|
||||
List<String> get defaultNameserver;
|
||||
@override
|
||||
@JsonKey(name: "enhanced-mode")
|
||||
DnsMode get enhancedMode;
|
||||
@override
|
||||
@JsonKey(name: "fake-ip-range")
|
||||
String get fakeIpRange;
|
||||
@override
|
||||
@JsonKey(name: "fake-ip-filter")
|
||||
List<String> get fakeIpFilter;
|
||||
@override
|
||||
@JsonKey(name: "nameserver-policy")
|
||||
Map<String, String> get nameserverPolicy;
|
||||
@override
|
||||
List<String> get nameserver;
|
||||
@override
|
||||
List<String> get fallback;
|
||||
@override
|
||||
@JsonKey(name: "proxy-server-nameserver")
|
||||
List<String> get proxyServerNameserver;
|
||||
@override
|
||||
@JsonKey(name: "fallback-filter")
|
||||
FallbackFilter get fallbackFilter;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$DnsImplCopyWith<_$DnsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
@@ -6,35 +6,6 @@ part of '../clash_config.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Dns _$DnsFromJson(Map<String, dynamic> json) => Dns()
|
||||
..enable = json['enable'] as bool
|
||||
..ipv6 = json['ipv6'] as bool
|
||||
..defaultNameserver = (json['default-nameserver'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList()
|
||||
..enhancedMode = json['enhanced-mode'] as String
|
||||
..fakeIpRange = json['fake-ip-range'] as String
|
||||
..useHosts = json['use-hosts'] as bool
|
||||
..nameserver =
|
||||
(json['nameserver'] as List<dynamic>).map((e) => e as String).toList()
|
||||
..fallback =
|
||||
(json['fallback'] as List<dynamic>).map((e) => e as String).toList()
|
||||
..fakeIpFilter = (json['fake-ip-filter'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList();
|
||||
|
||||
Map<String, dynamic> _$DnsToJson(Dns instance) => <String, dynamic>{
|
||||
'enable': instance.enable,
|
||||
'ipv6': instance.ipv6,
|
||||
'default-nameserver': instance.defaultNameserver,
|
||||
'enhanced-mode': instance.enhancedMode,
|
||||
'fake-ip-range': instance.fakeIpRange,
|
||||
'use-hosts': instance.useHosts,
|
||||
'nameserver': instance.nameserver,
|
||||
'fallback': instance.fallback,
|
||||
'fake-ip-filter': instance.fakeIpFilter,
|
||||
};
|
||||
|
||||
ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig()
|
||||
..mixedPort = (json['mixed-port'] as num?)?.toInt() ?? 7890
|
||||
..mode = $enumDecodeNullable(_$ModeEnumMap, json['mode']) ?? Mode.rule
|
||||
@@ -51,7 +22,7 @@ ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig()
|
||||
..unifiedDelay = json['unified-delay'] as bool? ?? false
|
||||
..tcpConcurrent = json['tcp-concurrent'] as bool? ?? false
|
||||
..tun = Tun.fromJson(json['tun'] as Map<String, dynamic>)
|
||||
..dns = Dns.fromJson(json['dns'] as Map<String, dynamic>)
|
||||
..dns = Dns.safeDnsFromJson(json['dns'] as Map<String, Object?>)
|
||||
..rules = (json['rules'] as List<dynamic>).map((e) => e as String).toList()
|
||||
..globalRealUa = json['global-real-ua'] as String?
|
||||
..geoXUrl = (json['geox-url'] as Map<String, dynamic>?)?.map(
|
||||
@@ -66,7 +37,11 @@ ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig()
|
||||
'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'
|
||||
};
|
||||
}
|
||||
..hosts = (json['hosts'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
) ??
|
||||
{};
|
||||
|
||||
Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -87,6 +62,7 @@ Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) =>
|
||||
'global-ua': instance.globalUa,
|
||||
'global-real-ua': instance.globalRealUa,
|
||||
'geox-url': instance.geoXUrl,
|
||||
'hosts': instance.hosts,
|
||||
};
|
||||
|
||||
const _$ModeEnumMap = {
|
||||
@@ -131,3 +107,105 @@ const _$TunStackEnumMap = {
|
||||
TunStack.system: 'system',
|
||||
TunStack.mixed: 'mixed',
|
||||
};
|
||||
|
||||
_$FallbackFilterImpl _$$FallbackFilterImplFromJson(Map<String, dynamic> json) =>
|
||||
_$FallbackFilterImpl(
|
||||
geoip: json['geoip'] as bool? ?? true,
|
||||
geoipCode: json['geoip-code'] as String? ?? "CN",
|
||||
geosite: (json['geosite'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const ["gfw"],
|
||||
ipcidr: (json['ipcidr'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const ["240.0.0.0/4"],
|
||||
domain: (json['domain'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const ["+.google.com", "+.facebook.com", "+.youtube.com"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$FallbackFilterImplToJson(
|
||||
_$FallbackFilterImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'geoip': instance.geoip,
|
||||
'geoip-code': instance.geoipCode,
|
||||
'geosite': instance.geosite,
|
||||
'ipcidr': instance.ipcidr,
|
||||
'domain': instance.domain,
|
||||
};
|
||||
|
||||
_$DnsImpl _$$DnsImplFromJson(Map<String, dynamic> json) => _$DnsImpl(
|
||||
enable: json['enable'] as bool? ?? true,
|
||||
preferH3: json['prefer-h3'] as bool? ?? false,
|
||||
useHosts: json['use-hosts'] as bool? ?? true,
|
||||
useSystemHosts: json['use-system-hosts'] as bool? ?? true,
|
||||
respectRules: json['respect-rules'] as bool? ?? true,
|
||||
ipv6: json['ipv6'] as bool? ?? false,
|
||||
defaultNameserver: (json['default-nameserver'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const ["223.5.5.5"],
|
||||
enhancedMode:
|
||||
$enumDecodeNullable(_$DnsModeEnumMap, json['enhanced-mode']) ??
|
||||
DnsMode.fakeIp,
|
||||
fakeIpRange: json['fake-ip-range'] as String? ?? "198.18.0.1/16",
|
||||
fakeIpFilter: (json['fake-ip-filter'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const ["*.lan", "localhost.ptlogin2.qq.com"],
|
||||
nameserverPolicy:
|
||||
(json['nameserver-policy'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
) ??
|
||||
const {
|
||||
"www.baidu.com": "114.114.114.114",
|
||||
"+.internal.crop.com": "10.0.0.1",
|
||||
"geosite:cn": "https://doh.pub/dns-query"
|
||||
},
|
||||
nameserver: (json['nameserver'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const [
|
||||
"https://doh.pub/dns-query",
|
||||
"https://dns.alidns.com/dns-query"
|
||||
],
|
||||
fallback: (json['fallback'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const ["tls://8.8.4.4", "tls://1.1.1.1"],
|
||||
proxyServerNameserver: (json['proxy-server-nameserver'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const ["https://doh.pub/dns-query"],
|
||||
fallbackFilter: json['fallback-filter'] == null
|
||||
? const FallbackFilter()
|
||||
: FallbackFilter.fromJson(
|
||||
json['fallback-filter'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$DnsImplToJson(_$DnsImpl instance) => <String, dynamic>{
|
||||
'enable': instance.enable,
|
||||
'prefer-h3': instance.preferH3,
|
||||
'use-hosts': instance.useHosts,
|
||||
'use-system-hosts': instance.useSystemHosts,
|
||||
'respect-rules': instance.respectRules,
|
||||
'ipv6': instance.ipv6,
|
||||
'default-nameserver': instance.defaultNameserver,
|
||||
'enhanced-mode': _$DnsModeEnumMap[instance.enhancedMode]!,
|
||||
'fake-ip-range': instance.fakeIpRange,
|
||||
'fake-ip-filter': instance.fakeIpFilter,
|
||||
'nameserver-policy': instance.nameserverPolicy,
|
||||
'nameserver': instance.nameserver,
|
||||
'fallback': instance.fallback,
|
||||
'proxy-server-nameserver': instance.proxyServerNameserver,
|
||||
'fallback-filter': instance.fallbackFilter,
|
||||
};
|
||||
|
||||
const _$DnsModeEnumMap = {
|
||||
DnsMode.normal: 'normal',
|
||||
DnsMode.fakeIp: 'fake-ip',
|
||||
DnsMode.redirHost: 'redir-host',
|
||||
DnsMode.hosts: 'hosts',
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ mixin _$AccessControl {
|
||||
AccessControlMode get mode => throw _privateConstructorUsedError;
|
||||
List<String> get acceptList => throw _privateConstructorUsedError;
|
||||
List<String> get rejectList => throw _privateConstructorUsedError;
|
||||
AccessSortType get sort => throw _privateConstructorUsedError;
|
||||
bool get isFilterSystemApp => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -41,6 +42,7 @@ abstract class $AccessControlCopyWith<$Res> {
|
||||
{AccessControlMode mode,
|
||||
List<String> acceptList,
|
||||
List<String> rejectList,
|
||||
AccessSortType sort,
|
||||
bool isFilterSystemApp});
|
||||
}
|
||||
|
||||
@@ -60,6 +62,7 @@ class _$AccessControlCopyWithImpl<$Res, $Val extends AccessControl>
|
||||
Object? mode = null,
|
||||
Object? acceptList = null,
|
||||
Object? rejectList = null,
|
||||
Object? sort = null,
|
||||
Object? isFilterSystemApp = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
@@ -75,6 +78,10 @@ class _$AccessControlCopyWithImpl<$Res, $Val extends AccessControl>
|
||||
? _value.rejectList
|
||||
: rejectList // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
sort: null == sort
|
||||
? _value.sort
|
||||
: sort // ignore: cast_nullable_to_non_nullable
|
||||
as AccessSortType,
|
||||
isFilterSystemApp: null == isFilterSystemApp
|
||||
? _value.isFilterSystemApp
|
||||
: isFilterSystemApp // ignore: cast_nullable_to_non_nullable
|
||||
@@ -95,6 +102,7 @@ abstract class _$$AccessControlImplCopyWith<$Res>
|
||||
{AccessControlMode mode,
|
||||
List<String> acceptList,
|
||||
List<String> rejectList,
|
||||
AccessSortType sort,
|
||||
bool isFilterSystemApp});
|
||||
}
|
||||
|
||||
@@ -112,6 +120,7 @@ class __$$AccessControlImplCopyWithImpl<$Res>
|
||||
Object? mode = null,
|
||||
Object? acceptList = null,
|
||||
Object? rejectList = null,
|
||||
Object? sort = null,
|
||||
Object? isFilterSystemApp = null,
|
||||
}) {
|
||||
return _then(_$AccessControlImpl(
|
||||
@@ -127,6 +136,10 @@ class __$$AccessControlImplCopyWithImpl<$Res>
|
||||
? _value._rejectList
|
||||
: rejectList // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
sort: null == sort
|
||||
? _value.sort
|
||||
: sort // ignore: cast_nullable_to_non_nullable
|
||||
as AccessSortType,
|
||||
isFilterSystemApp: null == isFilterSystemApp
|
||||
? _value.isFilterSystemApp
|
||||
: isFilterSystemApp // ignore: cast_nullable_to_non_nullable
|
||||
@@ -142,6 +155,7 @@ class _$AccessControlImpl implements _AccessControl {
|
||||
{this.mode = AccessControlMode.rejectSelected,
|
||||
final List<String> acceptList = const [],
|
||||
final List<String> rejectList = const [],
|
||||
this.sort = AccessSortType.none,
|
||||
this.isFilterSystemApp = true})
|
||||
: _acceptList = acceptList,
|
||||
_rejectList = rejectList;
|
||||
@@ -170,13 +184,16 @@ class _$AccessControlImpl implements _AccessControl {
|
||||
return EqualUnmodifiableListView(_rejectList);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final AccessSortType sort;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFilterSystemApp;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AccessControl(mode: $mode, acceptList: $acceptList, rejectList: $rejectList, isFilterSystemApp: $isFilterSystemApp)';
|
||||
return 'AccessControl(mode: $mode, acceptList: $acceptList, rejectList: $rejectList, sort: $sort, isFilterSystemApp: $isFilterSystemApp)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -189,6 +206,7 @@ class _$AccessControlImpl implements _AccessControl {
|
||||
.equals(other._acceptList, _acceptList) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._rejectList, _rejectList) &&
|
||||
(identical(other.sort, sort) || other.sort == sort) &&
|
||||
(identical(other.isFilterSystemApp, isFilterSystemApp) ||
|
||||
other.isFilterSystemApp == isFilterSystemApp));
|
||||
}
|
||||
@@ -200,6 +218,7 @@ class _$AccessControlImpl implements _AccessControl {
|
||||
mode,
|
||||
const DeepCollectionEquality().hash(_acceptList),
|
||||
const DeepCollectionEquality().hash(_rejectList),
|
||||
sort,
|
||||
isFilterSystemApp);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@@ -221,6 +240,7 @@ abstract class _AccessControl implements AccessControl {
|
||||
{final AccessControlMode mode,
|
||||
final List<String> acceptList,
|
||||
final List<String> rejectList,
|
||||
final AccessSortType sort,
|
||||
final bool isFilterSystemApp}) = _$AccessControlImpl;
|
||||
|
||||
factory _AccessControl.fromJson(Map<String, dynamic> json) =
|
||||
@@ -233,6 +253,8 @@ abstract class _AccessControl implements AccessControl {
|
||||
@override
|
||||
List<String> get rejectList;
|
||||
@override
|
||||
AccessSortType get sort;
|
||||
@override
|
||||
bool get isFilterSystemApp;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@@ -248,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;
|
||||
@@ -267,6 +290,7 @@ abstract class $CoreStateCopyWith<$Res> {
|
||||
$Res call(
|
||||
{AccessControl? accessControl,
|
||||
String currentProfileName,
|
||||
bool enable,
|
||||
bool allowBypass,
|
||||
bool systemProxy,
|
||||
int mixedPort,
|
||||
@@ -290,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,
|
||||
@@ -304,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
|
||||
@@ -347,6 +376,7 @@ abstract class _$$CoreStateImplCopyWith<$Res>
|
||||
$Res call(
|
||||
{AccessControl? accessControl,
|
||||
String currentProfileName,
|
||||
bool enable,
|
||||
bool allowBypass,
|
||||
bool systemProxy,
|
||||
int mixedPort,
|
||||
@@ -369,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,
|
||||
@@ -383,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
|
||||
@@ -409,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,
|
||||
@@ -422,6 +458,8 @@ class _$CoreStateImpl implements _CoreState {
|
||||
@override
|
||||
final String currentProfileName;
|
||||
@override
|
||||
final bool enable;
|
||||
@override
|
||||
final bool allowBypass;
|
||||
@override
|
||||
final bool systemProxy;
|
||||
@@ -432,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
|
||||
@@ -444,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) ||
|
||||
@@ -456,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
|
||||
@@ -477,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,
|
||||
@@ -490,6 +537,8 @@ abstract class _CoreState implements CoreState {
|
||||
@override
|
||||
String get currentProfileName;
|
||||
@override
|
||||
bool get enable;
|
||||
@override
|
||||
bool get allowBypass;
|
||||
@override
|
||||
bool get systemProxy;
|
||||
@@ -503,6 +552,189 @@ abstract class _CoreState implements CoreState {
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
VPNState _$VPNStateFromJson(Map<String, dynamic> json) {
|
||||
return _VPNState.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$VPNState {
|
||||
AccessControl? get accessControl => throw _privateConstructorUsedError;
|
||||
VpnProps get vpnProps => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$VPNStateCopyWith<VPNState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $VPNStateCopyWith<$Res> {
|
||||
factory $VPNStateCopyWith(VPNState value, $Res Function(VPNState) then) =
|
||||
_$VPNStateCopyWithImpl<$Res, VPNState>;
|
||||
@useResult
|
||||
$Res call({AccessControl? accessControl, VpnProps vpnProps});
|
||||
|
||||
$AccessControlCopyWith<$Res>? get accessControl;
|
||||
$VpnPropsCopyWith<$Res> get vpnProps;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$VPNStateCopyWithImpl<$Res, $Val extends VPNState>
|
||||
implements $VPNStateCopyWith<$Res> {
|
||||
_$VPNStateCopyWithImpl(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? accessControl = freezed,
|
||||
Object? vpnProps = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
accessControl: freezed == accessControl
|
||||
? _value.accessControl
|
||||
: accessControl // ignore: cast_nullable_to_non_nullable
|
||||
as AccessControl?,
|
||||
vpnProps: null == vpnProps
|
||||
? _value.vpnProps
|
||||
: vpnProps // ignore: cast_nullable_to_non_nullable
|
||||
as VpnProps,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$AccessControlCopyWith<$Res>? get accessControl {
|
||||
if (_value.accessControl == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $AccessControlCopyWith<$Res>(_value.accessControl!, (value) {
|
||||
return _then(_value.copyWith(accessControl: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$VpnPropsCopyWith<$Res> get vpnProps {
|
||||
return $VpnPropsCopyWith<$Res>(_value.vpnProps, (value) {
|
||||
return _then(_value.copyWith(vpnProps: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$VPNStateImplCopyWith<$Res>
|
||||
implements $VPNStateCopyWith<$Res> {
|
||||
factory _$$VPNStateImplCopyWith(
|
||||
_$VPNStateImpl value, $Res Function(_$VPNStateImpl) then) =
|
||||
__$$VPNStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({AccessControl? accessControl, VpnProps vpnProps});
|
||||
|
||||
@override
|
||||
$AccessControlCopyWith<$Res>? get accessControl;
|
||||
@override
|
||||
$VpnPropsCopyWith<$Res> get vpnProps;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$VPNStateImplCopyWithImpl<$Res>
|
||||
extends _$VPNStateCopyWithImpl<$Res, _$VPNStateImpl>
|
||||
implements _$$VPNStateImplCopyWith<$Res> {
|
||||
__$$VPNStateImplCopyWithImpl(
|
||||
_$VPNStateImpl _value, $Res Function(_$VPNStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? accessControl = freezed,
|
||||
Object? vpnProps = null,
|
||||
}) {
|
||||
return _then(_$VPNStateImpl(
|
||||
accessControl: freezed == accessControl
|
||||
? _value.accessControl
|
||||
: accessControl // ignore: cast_nullable_to_non_nullable
|
||||
as AccessControl?,
|
||||
vpnProps: null == vpnProps
|
||||
? _value.vpnProps
|
||||
: vpnProps // ignore: cast_nullable_to_non_nullable
|
||||
as VpnProps,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$VPNStateImpl implements _VPNState {
|
||||
const _$VPNStateImpl({required this.accessControl, required this.vpnProps});
|
||||
|
||||
factory _$VPNStateImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$VPNStateImplFromJson(json);
|
||||
|
||||
@override
|
||||
final AccessControl? accessControl;
|
||||
@override
|
||||
final VpnProps vpnProps;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VPNState(accessControl: $accessControl, vpnProps: $vpnProps)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$VPNStateImpl &&
|
||||
(identical(other.accessControl, accessControl) ||
|
||||
other.accessControl == accessControl) &&
|
||||
(identical(other.vpnProps, vpnProps) ||
|
||||
other.vpnProps == vpnProps));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, accessControl, vpnProps);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$VPNStateImplCopyWith<_$VPNStateImpl> get copyWith =>
|
||||
__$$VPNStateImplCopyWithImpl<_$VPNStateImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$VPNStateImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _VPNState implements VPNState {
|
||||
const factory _VPNState(
|
||||
{required final AccessControl? accessControl,
|
||||
required final VpnProps vpnProps}) = _$VPNStateImpl;
|
||||
|
||||
factory _VPNState.fromJson(Map<String, dynamic> json) =
|
||||
_$VPNStateImpl.fromJson;
|
||||
|
||||
@override
|
||||
AccessControl? get accessControl;
|
||||
@override
|
||||
VpnProps get vpnProps;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$VPNStateImplCopyWith<_$VPNStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
WindowProps _$WindowPropsFromJson(Map<String, dynamic> json) {
|
||||
return _WindowProps.fromJson(json);
|
||||
}
|
||||
@@ -693,3 +925,474 @@ 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;
|
||||
}
|
||||
|
||||
ScaleProps _$ScalePropsFromJson(Map<String, dynamic> json) {
|
||||
return _ScaleProps.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ScaleProps {
|
||||
bool get custom => throw _privateConstructorUsedError;
|
||||
double get scale => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$ScalePropsCopyWith<ScaleProps> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ScalePropsCopyWith<$Res> {
|
||||
factory $ScalePropsCopyWith(
|
||||
ScaleProps value, $Res Function(ScaleProps) then) =
|
||||
_$ScalePropsCopyWithImpl<$Res, ScaleProps>;
|
||||
@useResult
|
||||
$Res call({bool custom, double scale});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ScalePropsCopyWithImpl<$Res, $Val extends ScaleProps>
|
||||
implements $ScalePropsCopyWith<$Res> {
|
||||
_$ScalePropsCopyWithImpl(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? custom = null,
|
||||
Object? scale = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
custom: null == custom
|
||||
? _value.custom
|
||||
: custom // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
scale: null == scale
|
||||
? _value.scale
|
||||
: scale // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ScalePropsImplCopyWith<$Res>
|
||||
implements $ScalePropsCopyWith<$Res> {
|
||||
factory _$$ScalePropsImplCopyWith(
|
||||
_$ScalePropsImpl value, $Res Function(_$ScalePropsImpl) then) =
|
||||
__$$ScalePropsImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({bool custom, double scale});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ScalePropsImplCopyWithImpl<$Res>
|
||||
extends _$ScalePropsCopyWithImpl<$Res, _$ScalePropsImpl>
|
||||
implements _$$ScalePropsImplCopyWith<$Res> {
|
||||
__$$ScalePropsImplCopyWithImpl(
|
||||
_$ScalePropsImpl _value, $Res Function(_$ScalePropsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? custom = null,
|
||||
Object? scale = null,
|
||||
}) {
|
||||
return _then(_$ScalePropsImpl(
|
||||
custom: null == custom
|
||||
? _value.custom
|
||||
: custom // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
scale: null == scale
|
||||
? _value.scale
|
||||
: scale // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ScalePropsImpl implements _ScaleProps {
|
||||
const _$ScalePropsImpl(
|
||||
{this.custom = false, this.scale = defaultCustomFontSizeScale});
|
||||
|
||||
factory _$ScalePropsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ScalePropsImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool custom;
|
||||
@override
|
||||
@JsonKey()
|
||||
final double scale;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ScaleProps(custom: $custom, scale: $scale)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ScalePropsImpl &&
|
||||
(identical(other.custom, custom) || other.custom == custom) &&
|
||||
(identical(other.scale, scale) || other.scale == scale));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, custom, scale);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ScalePropsImplCopyWith<_$ScalePropsImpl> get copyWith =>
|
||||
__$$ScalePropsImplCopyWithImpl<_$ScalePropsImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ScalePropsImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ScaleProps implements ScaleProps {
|
||||
const factory _ScaleProps({final bool custom, final double scale}) =
|
||||
_$ScalePropsImpl;
|
||||
|
||||
factory _ScaleProps.fromJson(Map<String, dynamic> json) =
|
||||
_$ScalePropsImpl.fromJson;
|
||||
|
||||
@override
|
||||
bool get custom;
|
||||
@override
|
||||
double get scale;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ScalePropsImplCopyWith<_$ScalePropsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
|
||||
..proxiesSortType =
|
||||
$enumDecodeNullable(_$ProxiesSortTypeEnumMap, json['proxiesSortType']) ??
|
||||
ProxiesSortType.none
|
||||
..proxiesLayout =
|
||||
$enumDecodeNullable(_$ProxiesLayoutEnumMap, json['proxiesLayout']) ??
|
||||
ProxiesLayout.standard
|
||||
..isMinimizeOnExit = json['isMinimizeOnExit'] as bool? ?? true
|
||||
..isAccessControl = json['isAccessControl'] as bool? ?? false
|
||||
..accessControl =
|
||||
@@ -33,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
|
||||
@@ -44,12 +45,18 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
|
||||
..proxyCardType =
|
||||
$enumDecodeNullable(_$ProxyCardTypeEnumMap, json['proxyCardType']) ??
|
||||
ProxyCardType.expand
|
||||
..proxiesColumns = (json['proxiesColumns'] as num?)?.toInt() ?? 2
|
||||
..testUrl =
|
||||
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>?)
|
||||
..scaleProps =
|
||||
ScaleProps.fromJson(json['scaleProps'] as Map<String, dynamic>?)
|
||||
..showLabel = json['showLabel'] as bool? ?? false
|
||||
..overrideDns = json['overrideDns'] as bool? ?? false;
|
||||
|
||||
Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
||||
'profiles': instance.profiles,
|
||||
@@ -62,6 +69,7 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
||||
'locale': instance.locale,
|
||||
'primaryColor': instance.primaryColor,
|
||||
'proxiesSortType': _$ProxiesSortTypeEnumMap[instance.proxiesSortType]!,
|
||||
'proxiesLayout': _$ProxiesLayoutEnumMap[instance.proxiesLayout]!,
|
||||
'isMinimizeOnExit': instance.isMinimizeOnExit,
|
||||
'isAccessControl': instance.isAccessControl,
|
||||
'accessControl': instance.accessControl,
|
||||
@@ -69,17 +77,19 @@ 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,
|
||||
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
|
||||
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
|
||||
'proxiesColumns': instance.proxiesColumns,
|
||||
'test-url': instance.testUrl,
|
||||
'isExclude': instance.isExclude,
|
||||
'windowProps': instance.windowProps,
|
||||
'vpnProps': instance.vpnProps,
|
||||
'desktopProps': instance.desktopProps,
|
||||
'scaleProps': instance.scaleProps,
|
||||
'showLabel': instance.showLabel,
|
||||
'overrideDns': instance.overrideDns,
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
@@ -94,6 +104,12 @@ const _$ProxiesSortTypeEnumMap = {
|
||||
ProxiesSortType.name: 'name',
|
||||
};
|
||||
|
||||
const _$ProxiesLayoutEnumMap = {
|
||||
ProxiesLayout.loose: 'loose',
|
||||
ProxiesLayout.standard: 'standard',
|
||||
ProxiesLayout.tight: 'tight',
|
||||
};
|
||||
|
||||
const _$ProxiesTypeEnumMap = {
|
||||
ProxiesType.tab: 'tab',
|
||||
ProxiesType.list: 'list',
|
||||
@@ -117,6 +133,8 @@ _$AccessControlImpl _$$AccessControlImplFromJson(Map<String, dynamic> json) =>
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const [],
|
||||
sort: $enumDecodeNullable(_$AccessSortTypeEnumMap, json['sort']) ??
|
||||
AccessSortType.none,
|
||||
isFilterSystemApp: json['isFilterSystemApp'] as bool? ?? true,
|
||||
);
|
||||
|
||||
@@ -125,6 +143,7 @@ Map<String, dynamic> _$$AccessControlImplToJson(_$AccessControlImpl instance) =>
|
||||
'mode': _$AccessControlModeEnumMap[instance.mode]!,
|
||||
'acceptList': instance.acceptList,
|
||||
'rejectList': instance.rejectList,
|
||||
'sort': _$AccessSortTypeEnumMap[instance.sort]!,
|
||||
'isFilterSystemApp': instance.isFilterSystemApp,
|
||||
};
|
||||
|
||||
@@ -133,6 +152,12 @@ const _$AccessControlModeEnumMap = {
|
||||
AccessControlMode.rejectSelected: 'rejectSelected',
|
||||
};
|
||||
|
||||
const _$AccessSortTypeEnumMap = {
|
||||
AccessSortType.none: 'none',
|
||||
AccessSortType.name: 'name',
|
||||
AccessSortType.time: 'time',
|
||||
};
|
||||
|
||||
_$CoreStateImpl _$$CoreStateImplFromJson(Map<String, dynamic> json) =>
|
||||
_$CoreStateImpl(
|
||||
accessControl: json['accessControl'] == null
|
||||
@@ -140,6 +165,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(),
|
||||
@@ -150,12 +176,28 @@ 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,
|
||||
'onlyProxy': instance.onlyProxy,
|
||||
};
|
||||
|
||||
_$VPNStateImpl _$$VPNStateImplFromJson(Map<String, dynamic> json) =>
|
||||
_$VPNStateImpl(
|
||||
accessControl: json['accessControl'] == null
|
||||
? null
|
||||
: AccessControl.fromJson(
|
||||
json['accessControl'] as Map<String, dynamic>),
|
||||
vpnProps: VpnProps.fromJson(json['vpnProps'] as Map<String, dynamic>?),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$VPNStateImplToJson(_$VPNStateImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'accessControl': instance.accessControl,
|
||||
'vpnProps': instance.vpnProps,
|
||||
};
|
||||
|
||||
_$WindowPropsImpl _$$WindowPropsImplFromJson(Map<String, dynamic> json) =>
|
||||
_$WindowPropsImpl(
|
||||
width: (json['width'] as num?)?.toDouble() ?? 1000,
|
||||
@@ -171,3 +213,39 @@ 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,
|
||||
};
|
||||
|
||||
_$ScalePropsImpl _$$ScalePropsImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ScalePropsImpl(
|
||||
custom: json['custom'] as bool? ?? false,
|
||||
scale: (json['scale'] as num?)?.toDouble() ?? defaultCustomFontSizeScale,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ScalePropsImplToJson(_$ScalePropsImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'custom': instance.custom,
|
||||
'scale': instance.scale,
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ mixin _$DAV {
|
||||
String get uri => throw _privateConstructorUsedError;
|
||||
String get user => throw _privateConstructorUsedError;
|
||||
String get password => throw _privateConstructorUsedError;
|
||||
String get fileName => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
@@ -34,7 +35,7 @@ abstract class $DAVCopyWith<$Res> {
|
||||
factory $DAVCopyWith(DAV value, $Res Function(DAV) then) =
|
||||
_$DAVCopyWithImpl<$Res, DAV>;
|
||||
@useResult
|
||||
$Res call({String uri, String user, String password});
|
||||
$Res call({String uri, String user, String password, String fileName});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -52,6 +53,7 @@ class _$DAVCopyWithImpl<$Res, $Val extends DAV> implements $DAVCopyWith<$Res> {
|
||||
Object? uri = null,
|
||||
Object? user = null,
|
||||
Object? password = null,
|
||||
Object? fileName = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
uri: null == uri
|
||||
@@ -66,6 +68,10 @@ class _$DAVCopyWithImpl<$Res, $Val extends DAV> implements $DAVCopyWith<$Res> {
|
||||
? _value.password
|
||||
: password // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
fileName: null == fileName
|
||||
? _value.fileName
|
||||
: fileName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -76,7 +82,7 @@ abstract class _$$DAVImplCopyWith<$Res> implements $DAVCopyWith<$Res> {
|
||||
__$$DAVImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String uri, String user, String password});
|
||||
$Res call({String uri, String user, String password, String fileName});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -91,6 +97,7 @@ class __$$DAVImplCopyWithImpl<$Res> extends _$DAVCopyWithImpl<$Res, _$DAVImpl>
|
||||
Object? uri = null,
|
||||
Object? user = null,
|
||||
Object? password = null,
|
||||
Object? fileName = null,
|
||||
}) {
|
||||
return _then(_$DAVImpl(
|
||||
uri: null == uri
|
||||
@@ -105,6 +112,10 @@ class __$$DAVImplCopyWithImpl<$Res> extends _$DAVCopyWithImpl<$Res, _$DAVImpl>
|
||||
? _value.password
|
||||
: password // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
fileName: null == fileName
|
||||
? _value.fileName
|
||||
: fileName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -113,7 +124,10 @@ class __$$DAVImplCopyWithImpl<$Res> extends _$DAVCopyWithImpl<$Res, _$DAVImpl>
|
||||
@JsonSerializable()
|
||||
class _$DAVImpl implements _DAV {
|
||||
const _$DAVImpl(
|
||||
{required this.uri, required this.user, required this.password});
|
||||
{required this.uri,
|
||||
required this.user,
|
||||
required this.password,
|
||||
this.fileName = defaultDavFileName});
|
||||
|
||||
factory _$DAVImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$DAVImplFromJson(json);
|
||||
@@ -124,10 +138,13 @@ class _$DAVImpl implements _DAV {
|
||||
final String user;
|
||||
@override
|
||||
final String password;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String fileName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DAV(uri: $uri, user: $user, password: $password)';
|
||||
return 'DAV(uri: $uri, user: $user, password: $password, fileName: $fileName)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -138,12 +155,14 @@ class _$DAVImpl implements _DAV {
|
||||
(identical(other.uri, uri) || other.uri == uri) &&
|
||||
(identical(other.user, user) || other.user == user) &&
|
||||
(identical(other.password, password) ||
|
||||
other.password == password));
|
||||
other.password == password) &&
|
||||
(identical(other.fileName, fileName) ||
|
||||
other.fileName == fileName));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, uri, user, password);
|
||||
int get hashCode => Object.hash(runtimeType, uri, user, password, fileName);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -163,7 +182,8 @@ abstract class _DAV implements DAV {
|
||||
const factory _DAV(
|
||||
{required final String uri,
|
||||
required final String user,
|
||||
required final String password}) = _$DAVImpl;
|
||||
required final String password,
|
||||
final String fileName}) = _$DAVImpl;
|
||||
|
||||
factory _DAV.fromJson(Map<String, dynamic> json) = _$DAVImpl.fromJson;
|
||||
|
||||
@@ -174,6 +194,8 @@ abstract class _DAV implements DAV {
|
||||
@override
|
||||
String get password;
|
||||
@override
|
||||
String get fileName;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$DAVImplCopyWith<_$DAVImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
@@ -10,10 +10,12 @@ _$DAVImpl _$$DAVImplFromJson(Map<String, dynamic> json) => _$DAVImpl(
|
||||
uri: json['uri'] as String,
|
||||
user: json['user'] as String,
|
||||
password: json['password'] as String,
|
||||
fileName: json['fileName'] as String? ?? defaultDavFileName,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$DAVImplToJson(_$DAVImpl instance) => <String, dynamic>{
|
||||
'uri': instance.uri,
|
||||
'user': instance.user,
|
||||
'password': instance.password,
|
||||
'fileName': instance.fileName,
|
||||
};
|
||||
|
||||
@@ -26,6 +26,8 @@ mixin _$ConfigExtendedParams {
|
||||
bool get isCompatible => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "selected-map")
|
||||
Map<String, String> get selectedMap => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "override-dns")
|
||||
bool get overrideDns => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "test-url")
|
||||
String get testUrl => throw _privateConstructorUsedError;
|
||||
|
||||
@@ -45,6 +47,7 @@ abstract class $ConfigExtendedParamsCopyWith<$Res> {
|
||||
{@JsonKey(name: "is-patch") bool isPatch,
|
||||
@JsonKey(name: "is-compatible") bool isCompatible,
|
||||
@JsonKey(name: "selected-map") Map<String, String> selectedMap,
|
||||
@JsonKey(name: "override-dns") bool overrideDns,
|
||||
@JsonKey(name: "test-url") String testUrl});
|
||||
}
|
||||
|
||||
@@ -65,6 +68,7 @@ class _$ConfigExtendedParamsCopyWithImpl<$Res,
|
||||
Object? isPatch = null,
|
||||
Object? isCompatible = null,
|
||||
Object? selectedMap = null,
|
||||
Object? overrideDns = null,
|
||||
Object? testUrl = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
@@ -80,6 +84,10 @@ class _$ConfigExtendedParamsCopyWithImpl<$Res,
|
||||
? _value.selectedMap
|
||||
: selectedMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>,
|
||||
overrideDns: null == overrideDns
|
||||
? _value.overrideDns
|
||||
: overrideDns // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
testUrl: null == testUrl
|
||||
? _value.testUrl
|
||||
: testUrl // ignore: cast_nullable_to_non_nullable
|
||||
@@ -100,6 +108,7 @@ abstract class _$$ConfigExtendedParamsImplCopyWith<$Res>
|
||||
{@JsonKey(name: "is-patch") bool isPatch,
|
||||
@JsonKey(name: "is-compatible") bool isCompatible,
|
||||
@JsonKey(name: "selected-map") Map<String, String> selectedMap,
|
||||
@JsonKey(name: "override-dns") bool overrideDns,
|
||||
@JsonKey(name: "test-url") String testUrl});
|
||||
}
|
||||
|
||||
@@ -117,6 +126,7 @@ class __$$ConfigExtendedParamsImplCopyWithImpl<$Res>
|
||||
Object? isPatch = null,
|
||||
Object? isCompatible = null,
|
||||
Object? selectedMap = null,
|
||||
Object? overrideDns = null,
|
||||
Object? testUrl = null,
|
||||
}) {
|
||||
return _then(_$ConfigExtendedParamsImpl(
|
||||
@@ -132,6 +142,10 @@ class __$$ConfigExtendedParamsImplCopyWithImpl<$Res>
|
||||
? _value._selectedMap
|
||||
: selectedMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>,
|
||||
overrideDns: null == overrideDns
|
||||
? _value.overrideDns
|
||||
: overrideDns // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
testUrl: null == testUrl
|
||||
? _value.testUrl
|
||||
: testUrl // ignore: cast_nullable_to_non_nullable
|
||||
@@ -148,6 +162,7 @@ class _$ConfigExtendedParamsImpl implements _ConfigExtendedParams {
|
||||
@JsonKey(name: "is-compatible") required this.isCompatible,
|
||||
@JsonKey(name: "selected-map")
|
||||
required final Map<String, String> selectedMap,
|
||||
@JsonKey(name: "override-dns") required this.overrideDns,
|
||||
@JsonKey(name: "test-url") required this.testUrl})
|
||||
: _selectedMap = selectedMap;
|
||||
|
||||
@@ -169,13 +184,16 @@ class _$ConfigExtendedParamsImpl implements _ConfigExtendedParams {
|
||||
return EqualUnmodifiableMapView(_selectedMap);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey(name: "override-dns")
|
||||
final bool overrideDns;
|
||||
@override
|
||||
@JsonKey(name: "test-url")
|
||||
final String testUrl;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ConfigExtendedParams(isPatch: $isPatch, isCompatible: $isCompatible, selectedMap: $selectedMap, testUrl: $testUrl)';
|
||||
return 'ConfigExtendedParams(isPatch: $isPatch, isCompatible: $isCompatible, selectedMap: $selectedMap, overrideDns: $overrideDns, testUrl: $testUrl)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -188,13 +206,15 @@ class _$ConfigExtendedParamsImpl implements _ConfigExtendedParams {
|
||||
other.isCompatible == isCompatible) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._selectedMap, _selectedMap) &&
|
||||
(identical(other.overrideDns, overrideDns) ||
|
||||
other.overrideDns == overrideDns) &&
|
||||
(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, isPatch, isCompatible,
|
||||
const DeepCollectionEquality().hash(_selectedMap), testUrl);
|
||||
const DeepCollectionEquality().hash(_selectedMap), overrideDns, testUrl);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -218,6 +238,7 @@ abstract class _ConfigExtendedParams implements ConfigExtendedParams {
|
||||
@JsonKey(name: "is-compatible") required final bool isCompatible,
|
||||
@JsonKey(name: "selected-map")
|
||||
required final Map<String, String> selectedMap,
|
||||
@JsonKey(name: "override-dns") required final bool overrideDns,
|
||||
@JsonKey(name: "test-url") required final String testUrl}) =
|
||||
_$ConfigExtendedParamsImpl;
|
||||
|
||||
@@ -234,6 +255,9 @@ abstract class _ConfigExtendedParams implements ConfigExtendedParams {
|
||||
@JsonKey(name: "selected-map")
|
||||
Map<String, String> get selectedMap;
|
||||
@override
|
||||
@JsonKey(name: "override-dns")
|
||||
bool get overrideDns;
|
||||
@override
|
||||
@JsonKey(name: "test-url")
|
||||
String get testUrl;
|
||||
@override
|
||||
|
||||
@@ -12,6 +12,7 @@ _$ConfigExtendedParamsImpl _$$ConfigExtendedParamsImplFromJson(
|
||||
isPatch: json['is-patch'] as bool,
|
||||
isCompatible: json['is-compatible'] as bool,
|
||||
selectedMap: Map<String, String>.from(json['selected-map'] as Map),
|
||||
overrideDns: json['override-dns'] as bool,
|
||||
testUrl: json['test-url'] as String,
|
||||
);
|
||||
|
||||
@@ -21,6 +22,7 @@ Map<String, dynamic> _$$ConfigExtendedParamsImplToJson(
|
||||
'is-patch': instance.isPatch,
|
||||
'is-compatible': instance.isCompatible,
|
||||
'selected-map': instance.selectedMap,
|
||||
'override-dns': instance.overrideDns,
|
||||
'test-url': instance.testUrl,
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ mixin _$Package {
|
||||
String get packageName => throw _privateConstructorUsedError;
|
||||
String get label => throw _privateConstructorUsedError;
|
||||
bool get isSystem => throw _privateConstructorUsedError;
|
||||
int get firstInstallTime => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
@@ -34,7 +35,8 @@ abstract class $PackageCopyWith<$Res> {
|
||||
factory $PackageCopyWith(Package value, $Res Function(Package) then) =
|
||||
_$PackageCopyWithImpl<$Res, Package>;
|
||||
@useResult
|
||||
$Res call({String packageName, String label, bool isSystem});
|
||||
$Res call(
|
||||
{String packageName, String label, bool isSystem, int firstInstallTime});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -53,6 +55,7 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
|
||||
Object? packageName = null,
|
||||
Object? label = null,
|
||||
Object? isSystem = null,
|
||||
Object? firstInstallTime = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
packageName: null == packageName
|
||||
@@ -67,6 +70,10 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
|
||||
? _value.isSystem
|
||||
: isSystem // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
firstInstallTime: null == firstInstallTime
|
||||
? _value.firstInstallTime
|
||||
: firstInstallTime // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -78,7 +85,8 @@ abstract class _$$PackageImplCopyWith<$Res> implements $PackageCopyWith<$Res> {
|
||||
__$$PackageImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String packageName, String label, bool isSystem});
|
||||
$Res call(
|
||||
{String packageName, String label, bool isSystem, int firstInstallTime});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -95,6 +103,7 @@ class __$$PackageImplCopyWithImpl<$Res>
|
||||
Object? packageName = null,
|
||||
Object? label = null,
|
||||
Object? isSystem = null,
|
||||
Object? firstInstallTime = null,
|
||||
}) {
|
||||
return _then(_$PackageImpl(
|
||||
packageName: null == packageName
|
||||
@@ -109,6 +118,10 @@ class __$$PackageImplCopyWithImpl<$Res>
|
||||
? _value.isSystem
|
||||
: isSystem // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
firstInstallTime: null == firstInstallTime
|
||||
? _value.firstInstallTime
|
||||
: firstInstallTime // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -117,7 +130,10 @@ class __$$PackageImplCopyWithImpl<$Res>
|
||||
@JsonSerializable()
|
||||
class _$PackageImpl implements _Package {
|
||||
const _$PackageImpl(
|
||||
{required this.packageName, required this.label, required this.isSystem});
|
||||
{required this.packageName,
|
||||
required this.label,
|
||||
required this.isSystem,
|
||||
required this.firstInstallTime});
|
||||
|
||||
factory _$PackageImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$PackageImplFromJson(json);
|
||||
@@ -128,10 +144,12 @@ class _$PackageImpl implements _Package {
|
||||
final String label;
|
||||
@override
|
||||
final bool isSystem;
|
||||
@override
|
||||
final int firstInstallTime;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Package(packageName: $packageName, label: $label, isSystem: $isSystem)';
|
||||
return 'Package(packageName: $packageName, label: $label, isSystem: $isSystem, firstInstallTime: $firstInstallTime)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -143,12 +161,15 @@ class _$PackageImpl implements _Package {
|
||||
other.packageName == packageName) &&
|
||||
(identical(other.label, label) || other.label == label) &&
|
||||
(identical(other.isSystem, isSystem) ||
|
||||
other.isSystem == isSystem));
|
||||
other.isSystem == isSystem) &&
|
||||
(identical(other.firstInstallTime, firstInstallTime) ||
|
||||
other.firstInstallTime == firstInstallTime));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, packageName, label, isSystem);
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, packageName, label, isSystem, firstInstallTime);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -168,7 +189,8 @@ abstract class _Package implements Package {
|
||||
const factory _Package(
|
||||
{required final String packageName,
|
||||
required final String label,
|
||||
required final bool isSystem}) = _$PackageImpl;
|
||||
required final bool isSystem,
|
||||
required final int firstInstallTime}) = _$PackageImpl;
|
||||
|
||||
factory _Package.fromJson(Map<String, dynamic> json) = _$PackageImpl.fromJson;
|
||||
|
||||
@@ -179,6 +201,8 @@ abstract class _Package implements Package {
|
||||
@override
|
||||
bool get isSystem;
|
||||
@override
|
||||
int get firstInstallTime;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$PackageImplCopyWith<_$PackageImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
@@ -11,6 +11,7 @@ _$PackageImpl _$$PackageImplFromJson(Map<String, dynamic> json) =>
|
||||
packageName: json['packageName'] as String,
|
||||
label: json['label'] as String,
|
||||
isSystem: json['isSystem'] as bool,
|
||||
firstInstallTime: (json['firstInstallTime'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
|
||||
@@ -18,4 +19,5 @@ Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
|
||||
'packageName': instance.packageName,
|
||||
'label': instance.label,
|
||||
'isSystem': instance.isSystem,
|
||||
'firstInstallTime': instance.firstInstallTime,
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ mixin _$Group {
|
||||
List<Proxy> get all => throw _privateConstructorUsedError;
|
||||
String? get now => throw _privateConstructorUsedError;
|
||||
bool? get hidden => throw _privateConstructorUsedError;
|
||||
String get icon => throw _privateConstructorUsedError;
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -41,6 +42,7 @@ abstract class $GroupCopyWith<$Res> {
|
||||
List<Proxy> all,
|
||||
String? now,
|
||||
bool? hidden,
|
||||
String icon,
|
||||
String name});
|
||||
}
|
||||
|
||||
@@ -61,6 +63,7 @@ class _$GroupCopyWithImpl<$Res, $Val extends Group>
|
||||
Object? all = null,
|
||||
Object? now = freezed,
|
||||
Object? hidden = freezed,
|
||||
Object? icon = null,
|
||||
Object? name = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
@@ -80,6 +83,10 @@ class _$GroupCopyWithImpl<$Res, $Val extends Group>
|
||||
? _value.hidden
|
||||
: hidden // ignore: cast_nullable_to_non_nullable
|
||||
as bool?,
|
||||
icon: null == icon
|
||||
? _value.icon
|
||||
: icon // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
@@ -100,6 +107,7 @@ abstract class _$$GroupImplCopyWith<$Res> implements $GroupCopyWith<$Res> {
|
||||
List<Proxy> all,
|
||||
String? now,
|
||||
bool? hidden,
|
||||
String icon,
|
||||
String name});
|
||||
}
|
||||
|
||||
@@ -118,6 +126,7 @@ class __$$GroupImplCopyWithImpl<$Res>
|
||||
Object? all = null,
|
||||
Object? now = freezed,
|
||||
Object? hidden = freezed,
|
||||
Object? icon = null,
|
||||
Object? name = null,
|
||||
}) {
|
||||
return _then(_$GroupImpl(
|
||||
@@ -137,6 +146,10 @@ class __$$GroupImplCopyWithImpl<$Res>
|
||||
? _value.hidden
|
||||
: hidden // ignore: cast_nullable_to_non_nullable
|
||||
as bool?,
|
||||
icon: null == icon
|
||||
? _value.icon
|
||||
: icon // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
@@ -153,6 +166,7 @@ class _$GroupImpl implements _Group {
|
||||
final List<Proxy> all = const [],
|
||||
this.now,
|
||||
this.hidden,
|
||||
this.icon = "",
|
||||
required this.name})
|
||||
: _all = all;
|
||||
|
||||
@@ -175,11 +189,14 @@ class _$GroupImpl implements _Group {
|
||||
@override
|
||||
final bool? hidden;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String icon;
|
||||
@override
|
||||
final String name;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Group(type: $type, all: $all, now: $now, hidden: $hidden, name: $name)';
|
||||
return 'Group(type: $type, all: $all, now: $now, hidden: $hidden, icon: $icon, name: $name)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -191,13 +208,14 @@ class _$GroupImpl implements _Group {
|
||||
const DeepCollectionEquality().equals(other._all, _all) &&
|
||||
(identical(other.now, now) || other.now == now) &&
|
||||
(identical(other.hidden, hidden) || other.hidden == hidden) &&
|
||||
(identical(other.icon, icon) || other.icon == icon) &&
|
||||
(identical(other.name, name) || other.name == name));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, type,
|
||||
const DeepCollectionEquality().hash(_all), now, hidden, name);
|
||||
const DeepCollectionEquality().hash(_all), now, hidden, icon, name);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -219,6 +237,7 @@ abstract class _Group implements Group {
|
||||
final List<Proxy> all,
|
||||
final String? now,
|
||||
final bool? hidden,
|
||||
final String icon,
|
||||
required final String name}) = _$GroupImpl;
|
||||
|
||||
factory _Group.fromJson(Map<String, dynamic> json) = _$GroupImpl.fromJson;
|
||||
@@ -232,6 +251,8 @@ abstract class _Group implements Group {
|
||||
@override
|
||||
bool? get hidden;
|
||||
@override
|
||||
String get icon;
|
||||
@override
|
||||
String get name;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
|
||||
@@ -14,6 +14,7 @@ _$GroupImpl _$$GroupImplFromJson(Map<String, dynamic> json) => _$GroupImpl(
|
||||
const [],
|
||||
now: json['now'] as String?,
|
||||
hidden: json['hidden'] as bool?,
|
||||
icon: json['icon'] as String? ?? "",
|
||||
name: json['name'] as String,
|
||||
);
|
||||
|
||||
@@ -23,6 +24,7 @@ Map<String, dynamic> _$$GroupImplToJson(_$GroupImpl instance) =>
|
||||
'all': instance.all,
|
||||
'now': instance.now,
|
||||
'hidden': instance.hidden,
|
||||
'icon': instance.icon,
|
||||
'name': instance.name,
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user