Compare commits

..

79 Commits

Author SHA1 Message Date
chen08209
afa1b4f424 Update version 2024-06-19 10:12:21 +08:00
chen08209
fa67940ec9 Fix the problem of excessive memory usage in traffic usage.
Add lightBlue theme color

Fix start unable to update profile issues
2024-06-19 10:10:41 +08:00
chen08209
90bb670442 Fix flashback caused by process 2024-06-17 15:49:16 +08:00
chen08209
05abf2d56d Add build version
Optimize quick start

Update system default option
2024-06-16 16:48:52 +08:00
chen08209
658727dd79 Update build.yml 2024-06-16 13:18:55 +08:00
chen08209
f7abf6446c Fix android vpn close issues
Add requests page

Fix checkUpdate dark mode style error

Fix quickStart error open app

Add memory proxies tab index

Support hidden group

Optimize logs
2024-06-16 13:06:34 +08:00
chen08209
5ab4dd0cbd Fix externalController hot load error 2024-06-13 19:22:26 +08:00
chen08209
35f7279fcb Add tcp concurrent switch
Add system proxy switch

Add geodata loader switch

Add external controller switch

Add auto gc on trim memory

Fix android notification error
2024-06-13 17:04:57 +08:00
chen08209
86572cc960 Fix ipv6 error 2024-06-12 19:07:54 +08:00
chen08209
ee22709d49 Fix android udp direct error
Add ipv6 switch

Add access all selected button

Remove android low version splash
2024-06-12 18:29:58 +08:00
chen08209
0a2ad63f38 Update version 2024-06-10 19:11:04 +08:00
chen08209
2ec12c9363 Add allowBypass
Fix Android only pick .text file issues
2024-06-10 19:09:58 +08:00
chen08209
a3c2dc786c Fix search issues 2024-06-09 21:48:17 +08:00
chen08209
7acf9c6db3 Fix LoadBalance, Relay load error 2024-06-09 20:53:36 +08:00
chen08209
8074547fb4 Fix build.yml4 2024-06-09 19:56:51 +08:00
chen08209
8a01e04871 Fix build.yml3 2024-06-09 19:49:51 +08:00
chen08209
7ddcdd9828 Fix build.yml2 2024-06-09 19:49:14 +08:00
chen08209
d89ed076fd Fix build.yml 2024-06-09 19:46:05 +08:00
chen08209
f4c3b06cd5 Add search function at access control
Fix the issues with the profile add button to cover the edit button

Adapt LoadBalance and Relay

Add arm

Fix android notification icon error
2024-06-09 19:25:14 +08:00
chen08209
c65746709d Add one-click update all profiles
Add expire show
2024-06-08 15:43:28 +08:00
chen08209
d2d9bdab02 Temp remove tun mode 2024-06-06 22:58:32 +08:00
chen08209
d0f8444b6d Remove macos in workflow 2024-06-06 22:29:26 +08:00
chen08209
fccabfbe27 Change go version 2024-06-06 22:04:29 +08:00
chen08209
e9bb97c6ce Update Version
Fix tun unable to open
2024-06-06 17:36:49 +08:00
chen08209
068fe14d89 Optimize delay test2 2024-06-06 17:13:32 +08:00
chen08209
43c397007c Optimize delay test
Add check ip
2024-06-06 16:35:09 +08:00
chen08209
5e801d5f5e add check ip request 2024-06-06 16:34:31 +08:00
chen08209
52d61b15fd Fix the problem that the download of remote resources failed after GeodataMode was turned on, which caused the application to flash back.
Fix edit profile error
2024-06-06 10:15:51 +08:00
chen08209
fea3c14608 Fix quickStart change proxy error 2024-06-05 17:59:53 +08:00
chen08209
84be01a38a Fix core version 2024-06-05 17:59:50 +08:00
chen08209
93da242148 Fix core version 2024-06-05 14:13:54 +08:00
chen08209
bb7e44da30 Update file_picker
Add resources page

Optimize more detail
2024-06-05 13:44:11 +08:00
chen08209
01f1b2d72f Add access selected sorted 2024-06-05 13:43:44 +08:00
chen08209
3074b1299e Fix notification duplicate creation issue
Fix AccessControl click issue
2024-06-03 11:48:50 +08:00
chen08209
bd5470b863 Fix Workflow 2024-05-31 22:34:46 +08:00
chen08209
83fafa4b68 Fix Linux unable to open 2024-05-31 22:12:41 +08:00
chen08209
b7fb969301 Update README.md 3 2024-05-31 15:41:08 +08:00
chen08209
3ef3190785 Create LICENSE 2024-05-31 15:11:15 +08:00
chen08209
b1c763fcfa Update README.md 2 2024-05-31 15:08:51 +08:00
chen08209
9452ffa514 Update README.md 2024-05-31 15:02:18 +08:00
chen08209
c9291e5027 Optimize workFlow 2024-05-31 14:25:15 +08:00
chen08209
3ba86dc9c2 optimize checkUpdate 2024-05-31 10:07:51 +08:00
chen08209
fd3040283c Fix submit error 2024-05-30 17:22:23 +08:00
chen08209
91d30c0f0e add WebDAV
add Auto check updates

Optimize more details
2024-05-30 17:11:15 +08:00
chen08209
c4b470ffaf optimize delayTest 2024-05-17 19:54:57 +08:00
chen08209
9a07c785f2 upgrade flutter version 2024-05-15 20:34:59 +08:00
chen08209
a134c32493 Update kernel
Add import profile via QR code image
2024-05-15 20:21:02 +08:00
chen08209
472cea9037 Add compatibility mode and adapt clash scheme. 2024-05-11 14:10:06 +08:00
chen08209
08d07498b9 update Version 2024-05-07 18:32:21 +08:00
chen08209
d5aa09949a Reconstruction application proxy logic 2024-05-07 18:31:14 +08:00
chen08209
fd1dfe5c60 Fix Tab destroy error 2024-05-06 19:05:27 +08:00
chen08209
9f89fe8b29 Optimize repeat healthcheck 2024-05-06 17:17:26 +08:00
chen08209
78081a12e8 Optimize Direct mode ui 2024-05-06 15:27:37 +08:00
chen08209
6896837f28 Optimize Healthcheck 2024-05-06 14:32:23 +08:00
chen08209
85eb903402 Remove proxies position animation, improve performance
Add Telegram Link
2024-05-06 14:32:22 +08:00
chen08209
9aa9180f1f Update healthcheck policy 2024-05-06 14:32:21 +08:00
chen08209
feb9688a29 New Check URLTest 2024-05-06 14:32:21 +08:00
chen08209
5c71992174 Fix the problem of invalid auto-selection 2024-05-05 16:14:34 +08:00
chen08209
74c3d0ae25 New Async UpdateConfig 2024-05-05 03:13:52 +08:00
chen08209
ecd1bcafd5 add changeProfileDebounce 2024-05-05 03:13:51 +08:00
chen08209
184d2d117a Update Workflow 2024-05-05 03:13:50 +08:00
chen08209
89e6f17794 Fix ChangeProfile block 2024-05-05 03:13:49 +08:00
chen08209
aef50fe0e3 Fix Release Message Error 2024-05-04 16:14:03 +08:00
chen08209
fc0767ed25 Update Selector 2 2024-05-04 01:14:56 +08:00
chen08209
dbf1724cca Update Version 2024-05-04 00:14:34 +08:00
chen08209
909aa4038e Fix Proxies Select Error 2024-05-04 00:14:07 +08:00
chen08209
2d0a7d8d46 Fix the problem that the proxy group is empty in global mode. 2024-05-03 23:08:06 +08:00
chen08209
ca96cd1d82 Fix the problem that the proxy group is empty in global mode. 2024-05-03 23:07:38 +08:00
chen08209
91ab1e5dac Add ProxyProvider2 2024-05-03 21:48:22 +08:00
chen08209
b3a5f74df8 Add ProxyProvider 2024-05-03 21:28:22 +08:00
chen08209
1f98be8ad8 Update Version 2024-05-03 15:33:46 +08:00
chen08209
453c7c98d0 Update ProxyGroup Sort 2024-05-03 15:33:45 +08:00
chen08209
91faed35c0 Fix Android quickStart VpnService some problems 2024-05-02 00:32:11 +08:00
chen08209
07bbaf6b6f Update version 2024-05-01 23:40:04 +08:00
chen08209
e8feb7c431 Set Android notification low importance 2024-05-01 23:40:03 +08:00
chen08209
4d16820526 Fix the issue that VpnService can't be closed correctly in special cases 2024-05-01 23:40:00 +08:00
chen08209
92294b49c6 Fix the problem that TileService is not destroyed correctly in some cases
Adjust tab animation defaults
2024-05-01 23:39:59 +08:00
chen08209
8a188a37c9 Add Telegram in README_zh_CN.md 2024-05-01 21:52:07 +08:00
chen08209
48af16c265 Add Telegram 2024-05-01 21:50:26 +08:00
92 changed files with 2920 additions and 873 deletions

View File

@@ -3,7 +3,7 @@ name: build
on: on:
push: push:
tags: tags:
- '*' - 'v*'
jobs: jobs:
build: build:
@@ -21,6 +21,12 @@ jobs:
os: macos-13 os: macos-13
steps: steps:
- name: Check Matrix
run: |
echo "Running on ${{ matrix.os }}"
echo "Arch: ${{ runner.arch }}"
gcc --version
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
@@ -82,7 +88,7 @@ jobs:
upload-release: upload-release:
if: ${{ !endsWith(github.ref, '-debug') }} if: ${{ !contains(github.ref, '+') }}
permissions: write-all permissions: write-all
needs: [ build ] needs: [ build ]
runs-on: ubuntu-latest runs-on: ubuntu-latest

2
.gitmodules vendored
View File

@@ -5,4 +5,4 @@
[submodule "plugins/flutter_distributor"] [submodule "plugins/flutter_distributor"]
path = plugins/flutter_distributor path = plugins/flutter_distributor
url = git@github.com:chen08209/flutter_distributor.git url = git@github.com:chen08209/flutter_distributor.git
branch = main branch = FlClash

View File

@@ -34,6 +34,8 @@ on Mobile:
💡 Based on Material You Design, [Surfboard](https://github.com/getsurfboard/surfboard)-like UI 💡 Based on Material You Design, [Surfboard](https://github.com/getsurfboard/surfboard)-like UI
☁️ Supports data sync via WebDAV
✨ Support subscription link, Dark mode ✨ Support subscription link, Dark mode
## Contact ## Contact

View File

@@ -34,6 +34,8 @@ on Mobile:
💡 基本 Material You 设计, 类[Surfboard](https://github.com/getsurfboard/surfboard)用户界面 💡 基本 Material You 设计, 类[Surfboard](https://github.com/getsurfboard/surfboard)用户界面
☁️ 支持通过WebDAV同步数据
✨ 支持一键导入订阅, 深色模式 ✨ 支持一键导入订阅, 深色模式
## Contact ## Contact

View File

@@ -62,7 +62,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.follow.clash" applicationId "com.follow.clash"
minSdkVersion 21 minSdkVersion 24
targetSdkVersion 34 targetSdkVersion 34
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName

View File

@@ -1,9 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature <uses-feature
android:name="android.hardware.touchscreen" android:name="android.hardware.touchscreen"
android:required="false" /> android:required="false" />
@@ -17,12 +14,15 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_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"
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission"/> tools:ignore="SystemPermissionTypo" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application <application
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:extractNativeLibs="true"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:label="FlClash"> android:label="FlClash">
<activity <activity
@@ -73,7 +73,8 @@
<service <service
android:name=".services.FlClashTileService" android:name=".services.FlClashTileService"
android:exported="true" android:exported="true"
android:icon="@drawable/tile_icon" android:icon="@drawable/icon"
android:foregroundServiceType="specialUse"
android:label="FlClash" android:label="FlClash"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter> <intent-filter>

View File

@@ -1,6 +1,7 @@
package com.follow.clash package com.follow.clash
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.TilePlugin import com.follow.clash.plugins.TilePlugin
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import java.util.Date import java.util.Date
@@ -18,6 +19,9 @@ class GlobalState {
var flutterEngine: FlutterEngine? = null var flutterEngine: FlutterEngine? = null
fun getCurrentTilePlugin(): TilePlugin? = fun getCurrentTilePlugin(): TilePlugin? =
flutterEngine?.plugins?.get(TilePlugin::class.java) as TilePlugin? flutterEngine?.plugins?.get(TilePlugin::class.java) as TilePlugin?
fun getCurrentAppPlugin(): AppPlugin? =
flutterEngine?.plugins?.get(AppPlugin::class.java) as AppPlugin?
} }
} }

View File

@@ -1,17 +1,18 @@
package com.follow.clash.extensions package com.follow.clash.extensions
import java.net.InetSocketAddress
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.system.OsConstants.IPPROTO_TCP import android.system.OsConstants.IPPROTO_TCP
import android.system.OsConstants.IPPROTO_UDP import android.system.OsConstants.IPPROTO_UDP
import android.util.Base64 import android.util.Base64
import java.net.URL
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import com.follow.clash.models.Metadata import com.follow.clash.models.Metadata
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.net.InetAddress
import java.net.InetSocketAddress
suspend fun Drawable.getBase64(): String { suspend fun Drawable.getBase64(): String {
@@ -29,3 +30,8 @@ fun Metadata.getProtocol(): Int? {
if (network.startsWith("udp")) return IPPROTO_UDP if (network.startsWith("udp")) return IPPROTO_UDP
return null return null
} }
fun String.getInetSocketAddress(): InetSocketAddress {
val url = URL("https://$this")
return InetSocketAddress(InetAddress.getByName(url.host), url.port)
}

View File

@@ -1,11 +1,15 @@
package com.follow.clash.models package com.follow.clash.models
data class Process(
val id: Int,
val metadata: Metadata,
)
data class Metadata( data class Metadata(
val network: String, val network: String,
val sourceIP: String, val sourceIP: String,
val sourcePort: Int, val sourcePort: Int,
val destinationIP: String, val destinationIP: String,
val destinationPort: Int, val destinationPort: Int,
val remoteDestination: String,
val host: String val host: String
) )

View File

@@ -10,3 +10,9 @@ data class AccessControl(
val acceptList: List<String>, val acceptList: List<String>,
val rejectList: List<String>, val rejectList: List<String>,
) )
data class Props(
val accessControl: AccessControl?,
val allowBypass: Boolean?,
val systemProxy: Boolean?,
)

View File

@@ -6,13 +6,16 @@ import android.content.Context
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.Uri
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.toBitmap
import com.follow.clash.extensions.getBase64 import com.follow.clash.extensions.getBase64
import com.follow.clash.extensions.getInetSocketAddress
import com.follow.clash.extensions.getProtocol import com.follow.clash.extensions.getProtocol
import com.follow.clash.models.Metadata import com.follow.clash.models.Process
import com.follow.clash.models.Package import com.follow.clash.models.Package
import com.google.gson.Gson import com.google.gson.Gson
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
@@ -78,21 +81,34 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
"getPackageIcon" -> { "getPackageIcon" -> {
scope.launch { scope.launch {
val packageName = call.argument<String>("packageName") val packageName = call.argument<String>("packageName")
if (packageName != null) { if (packageName == null) {
result.success(getPackageIcon(packageName))
} else {
result.success(null) result.success(null)
return@launch
}
val packageIcon = getPackageIcon(packageName)
packageIcon.let {
if (it != null) {
result.success(it)
return@launch
}
if (iconMap["default"] == null) {
iconMap["default"] =
context?.packageManager?.defaultActivityIcon?.getBase64()
}
result.success(iconMap["default"])
return@launch
} }
} }
} }
"getPackageName" -> { "resolverProcess" -> {
val data = call.argument<String>("data") val data = call.argument<String>("data")
val metadata = val process =
if (data != null) Gson().fromJson( if (data != null) Gson().fromJson(
data, data,
Metadata::class.java Process::class.java
) else null ) else null
val metadata = process?.metadata
val protocol = metadata?.getProtocol() val protocol = metadata?.getProtocol()
if (protocol == null) { if (protocol == null) {
result.success(null) result.success(null)
@@ -100,17 +116,27 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
} }
scope.launch { scope.launch {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
if (context == null) result.success(null) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){
val source = InetSocketAddress(metadata.sourceIP, metadata.sourcePort) result.success(null)
val target = InetSocketAddress( return@withContext
metadata.host.ifEmpty { metadata.destinationIP }, }
metadata.destinationPort if (context == null) {
) result.success(null)
return@withContext
}
if (connectivity == null) { if (connectivity == null) {
connectivity = context!!.getSystemService<ConnectivityManager>() connectivity = context!!.getSystemService<ConnectivityManager>()
} }
val uid = val src = InetSocketAddress(metadata.sourceIP, metadata.sourcePort)
connectivity?.getConnectionOwnerUid(protocol, source, target) val dst = InetSocketAddress(
metadata.destinationIP.ifEmpty { metadata.host },
metadata.destinationPort
)
val uid = try {
connectivity?.getConnectionOwnerUid(protocol, src, dst)
} catch (_: Exception) {
null
}
if (uid == null || uid == -1) { if (uid == null || uid == -1) {
result.success(null) result.success(null)
return@withContext return@withContext
@@ -137,13 +163,18 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
private suspend fun getPackageIcon(packageName: String): String? { private suspend fun getPackageIcon(packageName: String): String? {
val packageManager = context?.packageManager val packageManager = context?.packageManager
if (iconMap[packageName] == null) { if (iconMap[packageName] == null) {
iconMap[packageName] = packageManager?.getApplicationIcon(packageName)?.getBase64() iconMap[packageName] = try {
packageManager?.getApplicationIcon(packageName)?.getBase64()
} catch (_: Exception) {
null
}
} }
return iconMap[packageName] return iconMap[packageName]
} }
private suspend fun getPackages(): String { private suspend fun getPackages(): String {
return withContext(Dispatchers.Default){ return withContext(Dispatchers.Default) {
val packageManager = context?.packageManager val packageManager = context?.packageManager
val packages: List<Package>? = val packages: List<Package>? =
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter { packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter {
@@ -162,6 +193,10 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
} }
} }
fun requestGc() {
channel.invokeMethod("gc", null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) { override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity; activity = binding.activity;
scope = CoroutineScope(Dispatchers.Default) scope = CoroutineScope(Dispatchers.Default)

View File

@@ -16,7 +16,7 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.follow.clash.GlobalState import com.follow.clash.GlobalState
import com.follow.clash.RunState import com.follow.clash.RunState
import com.follow.clash.models.AccessControl import com.follow.clash.models.Props
import com.follow.clash.services.FlClashVpnService import com.follow.clash.services.FlClashVpnService
import com.google.gson.Gson import com.google.gson.Gson
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
@@ -41,7 +41,7 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
private var flClashVpnService: FlClashVpnService? = null private var flClashVpnService: FlClashVpnService? = null
private var isBound = false private var isBound = false
private var port: Int? = null private var port: Int? = null
private var accessControl: AccessControl? = null private var props: Props? = null
private lateinit var title: String private lateinit var title: String
private lateinit var content: String private lateinit var content: String
@@ -73,8 +73,8 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
"StartProxy" -> { "StartProxy" -> {
port = call.argument<Int>("port") port = call.argument<Int>("port")
val args = call.argument<String>("args") val args = call.argument<String>("args")
accessControl = props =
if (args != null) Gson().fromJson(args, AccessControl::class.java) else null if (args != null) Gson().fromJson(args, Props::class.java) else null
handleStartVpn() handleStartVpn()
result.success(true) result.success(true)
} }
@@ -121,7 +121,7 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
private fun startVpn(port: Int) { private fun startVpn(port: Int) {
if (GlobalState.runState.value == RunState.START) return; if (GlobalState.runState.value == RunState.START) return;
flClashVpnService?.start(port, accessControl) flClashVpnService?.start(port, props)
GlobalState.runState.value = RunState.START GlobalState.runState.value = RunState.START
GlobalState.runTime = Date() GlobalState.runTime = Date()
startAfter() startAfter()

View File

@@ -19,7 +19,6 @@ import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor import io.flutter.embedding.engine.dart.DartExecutor
@RequiresApi(Build.VERSION_CODES.N)
class FlClashTileService : TileService() { class FlClashTileService : TileService() {
private val observer = Observer<RunState> { runState -> private val observer = Observer<RunState> { runState ->
@@ -43,19 +42,27 @@ class FlClashTileService : TileService() {
GlobalState.runState.observeForever(observer) GlobalState.runState.observeForever(observer)
} }
@SuppressLint("StartActivityAndCollapseDeprecated")
private fun activityTransfer() { private fun activityTransfer() {
val intent = Intent(this, TempActivity::class.java) val intent = Intent(this, TempActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
if (Build.VERSION.SDK_INT >= 34) { val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
val pendingIntent = PendingIntent.getActivity( PendingIntent.getActivity(
this, this,
0, 0,
intent, intent,
PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
) )
startActivityAndCollapse(pendingIntent)
} else { } else {
PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startActivityAndCollapse(pendingIntent)
}else{
startActivityAndCollapse(intent) startActivityAndCollapse(intent)
} }
} }

View File

@@ -1,10 +1,9 @@
package com.follow.clash.services package com.follow.clash.services
import android.annotation.SuppressLint import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.Service
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
import android.net.ProxyInfo import android.net.ProxyInfo
@@ -13,15 +12,13 @@ import android.os.Binder
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
import androidx.core.graphics.drawable.IconCompat
import com.follow.clash.GlobalState import com.follow.clash.GlobalState
import com.follow.clash.MainActivity import com.follow.clash.MainActivity
import com.follow.clash.models.AccessControl import com.follow.clash.R
import com.follow.clash.models.AccessControlMode import com.follow.clash.models.AccessControlMode
import com.follow.clash.models.Props
@SuppressLint("WrongConstant")
class FlClashVpnService : VpnService() { class FlClashVpnService : VpnService() {
@@ -54,12 +51,12 @@ class FlClashVpnService : VpnService() {
return START_STICKY return START_STICKY
} }
fun start(port: Int, accessControl: AccessControl?) { fun start(port: Int, props: Props?) {
fd = with(Builder()) { fd = with(Builder()) {
addAddress("172.16.0.1", 30) addAddress("172.16.0.1", 30)
setMtu(9000) setMtu(9000)
addRoute("0.0.0.0", 0) addRoute("0.0.0.0", 0)
if (accessControl != null) { props?.accessControl?.let { accessControl ->
when (accessControl.mode) { when (accessControl.mode) {
AccessControlMode.acceptSelected -> { AccessControlMode.acceptSelected -> {
(accessControl.acceptList + packageName).forEach { (accessControl.acceptList + packageName).forEach {
@@ -80,8 +77,10 @@ class FlClashVpnService : VpnService() {
if (Build.VERSION.SDK_INT >= 29) { if (Build.VERSION.SDK_INT >= 29) {
setMetered(false) setMetered(false)
} }
allowBypass() if (props?.allowBypass == true) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { allowBypass()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && props?.systemProxy == true) {
setHttpProxy( setHttpProxy(
ProxyInfo.buildDirectProxy( ProxyInfo.buildDirectProxy(
"127.0.0.1", "127.0.0.1",
@@ -100,10 +99,10 @@ class FlClashVpnService : VpnService() {
} }
private val notificationBuilder by lazy { private val notificationBuilder: NotificationCompat.Builder by lazy {
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)
val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
PendingIntent.getActivity( PendingIntent.getActivity(
this, this,
0, 0,
@@ -119,34 +118,40 @@ class FlClashVpnService : VpnService() {
) )
} }
val icon = IconCompat.createWithResource(this, this.applicationInfo.icon)
with(NotificationCompat.Builder(this, CHANNEL)) { with(NotificationCompat.Builder(this, CHANNEL)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { setSmallIcon(R.drawable.ic_stat_name)
setSmallIcon(icon)
}
setContentTitle("FlClash") setContentTitle("FlClash")
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
setContentIntent(pendingIntent) setContentIntent(pendingIntent)
setCategory(NotificationCompat.CATEGORY_SERVICE) setCategory(NotificationCompat.CATEGORY_SERVICE)
priority = NotificationCompat.PRIORITY_LOW priority = NotificationCompat.PRIORITY_MIN
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
}
setOngoing(true) setOngoing(true)
setShowWhen(false) setShowWhen(false)
setOnlyAlertOnce(true) setOnlyAlertOnce(true)
setAutoCancel(true);
} }
} }
@SuppressLint("ForegroundServiceType", "WrongConstant") override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
GlobalState.getCurrentAppPlugin()?.requestGc()
}
fun startForeground(title: String, content: String) { fun startForeground(title: String, content: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
val manager = getSystemService(NotificationManager::class.java) val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel) var channel = manager?.getNotificationChannel(CHANNEL)
if (channel == null) {
channel =
NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
manager?.createNotificationChannel(channel)
}
} }
val notification = val notification =
notificationBuilder.setContentTitle(title).setContentText(content).build() notificationBuilder.setContentTitle(title).setContentText(content).build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE) startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else { } else {
startForeground(notificationId, notification) startForeground(notificationId, notification)
@@ -155,7 +160,7 @@ class FlClashVpnService : VpnService() {
private fun stopForeground() { private fun stopForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
stopForeground(Service.STOP_FOREGROUND_REMOVE) stopForeground(STOP_FOREGROUND_REMOVE)
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -4,7 +4,6 @@
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@mipmap/ic_launcher_foreground</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your

View File

@@ -4,7 +4,6 @@
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@mipmap/ic_launcher_foreground</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your

View File

@@ -2,25 +2,24 @@ package main
import "C" import "C"
import ( import (
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
ap "github.com/metacubex/mihomo/adapter/provider" ap "github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/dns" "github.com/metacubex/mihomo/hub"
"github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/hub/route"
"github.com/metacubex/mihomo/listener" "github.com/metacubex/mihomo/listener"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel" "github.com/metacubex/mihomo/tunnel"
"math"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"sync"
"syscall" "syscall"
"time" "time"
) )
@@ -83,10 +82,13 @@ type Delay struct {
} }
type Process struct { type Process struct {
Uid uint32 `json:"uid"` Id int64 `json:"id"`
Network string `json:"network"` Metadata *constant.Metadata `json:"metadata"`
Source string `json:"source"` }
Target string `json:"target"`
type ProcessMapItem struct {
Id int64 `json:"id"`
Value string `json:"value"`
} }
type Now struct { type Now struct {
@@ -321,33 +323,35 @@ func generateProxyGroupAndRule(proxyGroup *[]map[string]any, rule *[]string) {
} }
func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig, compatible bool) { func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig, compatible bool) {
targetConfig.ExternalController = "" targetConfig.ExternalController = patchConfig.ExternalController
targetConfig.ExternalUI = "" targetConfig.ExternalUI = ""
targetConfig.Interface = "" targetConfig.Interface = ""
targetConfig.ExternalUIURL = "" targetConfig.ExternalUIURL = ""
targetConfig.TCPConcurrent = patchConfig.TCPConcurrent
targetConfig.UnifiedDelay = patchConfig.UnifiedDelay
targetConfig.GeodataMode = false targetConfig.GeodataMode = false
//targetConfig.IPv6 = patchConfig.IPv6 targetConfig.IPv6 = patchConfig.IPv6
targetConfig.LogLevel = patchConfig.LogLevel targetConfig.LogLevel = patchConfig.LogLevel
targetConfig.Port = 0 targetConfig.Port = 0
targetConfig.SocksPort = 0 targetConfig.SocksPort = 0
targetConfig.MixedPort = patchConfig.MixedPort targetConfig.MixedPort = patchConfig.MixedPort
targetConfig.FindProcessMode = process.FindProcessAlways targetConfig.FindProcessMode = patchConfig.FindProcessMode
targetConfig.AllowLan = patchConfig.AllowLan targetConfig.AllowLan = patchConfig.AllowLan
targetConfig.Mode = patchConfig.Mode targetConfig.Mode = patchConfig.Mode
targetConfig.Tun.Enable = patchConfig.Tun.Enable targetConfig.Tun.Enable = patchConfig.Tun.Enable
targetConfig.Tun.Device = patchConfig.Tun.Device targetConfig.Tun.Device = patchConfig.Tun.Device
targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack //targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
targetConfig.Tun.Stack = patchConfig.Tun.Stack //targetConfig.Tun.Stack = patchConfig.Tun.Stack
targetConfig.GeodataLoader = "standard" targetConfig.GeodataLoader = patchConfig.GeodataLoader
targetConfig.Profile.StoreSelected = false targetConfig.Profile.StoreSelected = false
if targetConfig.DNS.Enable == false { if targetConfig.DNS.Enable == false {
targetConfig.DNS = patchConfig.DNS targetConfig.DNS = patchConfig.DNS
} }
if runtime.GOOS == "android" { //if runtime.GOOS == "android" {
targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, "dhcp://"+dns.SystemDNSPlaceholder) // targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, "dhcp://"+dns.SystemDNSPlaceholder)
} else if runtime.GOOS == "windows" { //} else if runtime.GOOS == "windows" {
targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder) // targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder)
} //}
if compatible == false { if compatible == false {
targetConfig.ProxyProvider = make(map[string]map[string]any) targetConfig.ProxyProvider = make(map[string]map[string]any)
targetConfig.RuleProvider = make(map[string]map[string]any) targetConfig.RuleProvider = make(map[string]map[string]any)
@@ -357,14 +361,17 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
func patchConfig(general *config.General) { func patchConfig(general *config.General) {
log.Infoln("[Apply] patch") log.Infoln("[Apply] patch")
route.ReStartServer(general.ExternalController)
listener.SetAllowLan(general.AllowLan) listener.SetAllowLan(general.AllowLan)
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes) inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
inbound.SetAllowedIPs(general.LanAllowedIPs) inbound.SetAllowedIPs(general.LanAllowedIPs)
inbound.SetDisAllowedIPs(general.LanDisAllowedIPs) inbound.SetDisAllowedIPs(general.LanDisAllowedIPs)
listener.SetBindAddress(general.BindAddress) listener.SetBindAddress(general.BindAddress)
tunnel.SetSniffing(general.Sniffing) tunnel.SetSniffing(general.Sniffing)
tunnel.SetFindProcessMode(general.FindProcessMode)
dialer.SetTcpConcurrent(general.TCPConcurrent) dialer.SetTcpConcurrent(general.TCPConcurrent)
dialer.DefaultInterface.Store(general.Interface) dialer.DefaultInterface.Store(general.Interface)
adapter.UnifiedDelay.Store(general.UnifiedDelay)
listener.ReCreateHTTP(general.Port, tunnel.Tunnel) listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel) listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel) listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
@@ -377,31 +384,10 @@ func patchConfig(general *config.General) {
listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel) listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
tunnel.SetMode(general.Mode) tunnel.SetMode(general.Mode)
log.SetLevel(general.LogLevel) log.SetLevel(general.LogLevel)
resolver.DisableIPv6 = !general.IPv6 resolver.DisableIPv6 = !general.IPv6
} }
const concurrentCount = math.MaxInt
func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) {
wg := sync.WaitGroup{}
ch := make(chan struct{}, concurrentCount)
for _, proxyProvider := range proxyProviders {
proxyProvider := proxyProvider
if proxyProvider.VehicleType() == provider.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)
}
}()
}
}
}
func applyConfig(isPatch bool) { func applyConfig(isPatch bool) {
cfg, err := config.ParseRawConfig(currentConfig) cfg, err := config.ParseRawConfig(currentConfig)
if err != nil { if err != nil {
@@ -410,7 +396,6 @@ func applyConfig(isPatch bool) {
if isPatch { if isPatch {
patchConfig(cfg.General) patchConfig(cfg.General)
} else { } else {
executor.ApplyConfig(cfg, true) hub.UltraApplyConfig(cfg, true)
hcCompatibleProvider(tunnel.Providers())
} }
} }

View File

@@ -12,6 +12,8 @@ const (
Delay MessageType = "delay" Delay MessageType = "delay"
Now MessageType = "now" Now MessageType = "now"
Process MessageType = "process" Process MessageType = "process"
Request MessageType = "request"
Run MessageType = "run"
) )
type Message struct { type Message struct {
@@ -19,11 +21,18 @@ type Message struct {
Data interface{} `json:"data"` Data interface{} `json:"data"`
} }
func (message *Message) Json() string { func (message *Message) Json() (string, error) {
data, _ := json.Marshal(message) data, err := json.Marshal(message)
return string(data) return string(data), err
} }
func SendMessage(message Message) { func SendMessage(message Message) {
SendToPort(*Port, message.Json()) if Port == nil {
return
}
s, err := message.Json()
if err != nil {
return
}
SendToPort(*Port, s)
} }

View File

@@ -1,6 +1,6 @@
module core module core
go 1.20 go 1.21.0
replace github.com/metacubex/mihomo => ./Clash.Meta replace github.com/metacubex/mihomo => ./Clash.Meta
@@ -17,6 +17,7 @@ require (
github.com/RyuaNerin/go-krypto v1.2.4 // indirect github.com/RyuaNerin/go-krypto v1.2.4 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect github.com/andybalholm/brotli v1.0.6 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect
@@ -30,6 +31,9 @@ require (
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-chi/chi/v5 v5.0.12 // 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 github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect

View File

@@ -9,6 +9,8 @@ github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
@@ -35,16 +37,25 @@ github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIF
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg= github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c/go.mod h1:ETASDWf/FmEb6Ysrtd1QhjNedUU/ZQxBCRLh60bQ/UI=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY= github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4= 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 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok= 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 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 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 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 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/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=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@@ -60,6 +71,7 @@ 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.2.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.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 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -69,6 +81,7 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I= github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7spovjlY=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= 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/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -83,7 +96,9 @@ github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6K
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 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 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 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 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= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
@@ -125,6 +140,7 @@ github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:U
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0= github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo= github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0= github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
@@ -146,6 +162,7 @@ github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= 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/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 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 h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
@@ -249,6 +266,7 @@ golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
@@ -263,6 +281,7 @@ google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFW
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -60,6 +60,14 @@ func shutdownClash() bool {
return true return true
} }
//export forceGc
func forceGc() {
go func() {
log.Infoln("[APP] request force GC")
runtime.GC()
}()
}
//export validateConfig //export validateConfig
func validateConfig(s *C.char, port C.longlong) { func validateConfig(s *C.char, port C.longlong) {
i := int64(port) i := int64(port)
@@ -180,6 +188,21 @@ func getTraffic() *C.char {
return C.CString(string(data)) return C.CString(string(data))
} }
//export getTotalTraffic
func getTotalTraffic() *C.char {
up, down := statistic.DefaultManager.Total()
traffic := map[string]int64{
"up": up,
"down": down,
}
data, err := json.Marshal(traffic)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
}
return C.CString(string(data))
}
//export asyncTestDelay //export asyncTestDelay
func asyncTestDelay(s *C.char, port C.longlong) { func asyncTestDelay(s *C.char, port C.longlong) {
i := int64(port) i := int64(port)
@@ -396,4 +419,10 @@ func init() {
Data: delayData, Data: delayData,
}) })
} }
statistic.DefaultRequestNotify = func(c statistic.Tracker) {
bridge.SendMessage(bridge.Message{
Type: bridge.Request,
Data: c,
})
}
} }

View File

@@ -18,6 +18,9 @@ func startLog() {
logSubscriber = log.Subscribe() logSubscriber = log.Subscribe()
go func() { go func() {
for logData := range logSubscriber { for logData := range logSubscriber {
if logData.LogLevel < log.Level() {
continue
}
message := &bridge.Message{ message := &bridge.Message{
Type: bridge.Log, Type: bridge.Log,
Data: logData, Data: logData,

View File

@@ -1,3 +1,82 @@
//go:build android //go:build android
package main package main
import "C"
import (
bridge "core/dart-bridge"
"encoding/json"
"errors"
"github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/constant"
"sync"
"sync/atomic"
"time"
)
type ProcessMap struct {
m sync.Map
}
func (cm *ProcessMap) Store(key int64, value string) {
cm.m.Store(key, value)
}
func (cm *ProcessMap) Load(key int64) (string, bool) {
value, ok := cm.m.Load(key)
if !ok || value == nil {
return "", false
}
return value.(string), true
}
var counter int64 = 0
var processMap ProcessMap
func init() {
process.DefaultPackageNameResolver = func(metadata *constant.Metadata) (string, error) {
if metadata == nil {
return "", process.ErrInvalidNetwork
}
id := atomic.AddInt64(&counter, 1)
timeout := time.After(200 * time.Millisecond)
bridge.SendMessage(bridge.Message{
Type: bridge.Process,
Data: Process{
Id: id,
Metadata: metadata,
},
})
for {
select {
case <-timeout:
return "", errors.New("package resolver timeout")
default:
value, exists := processMap.Load(id)
if exists {
return value, nil
}
time.Sleep(20 * time.Millisecond)
}
}
}
}
//export setProcessMap
func setProcessMap(s *C.char) {
if s == nil {
return
}
go func() {
paramsString := C.GoString(s)
var processMapItem = &ProcessMapItem{}
err := json.Unmarshal([]byte(paramsString), processMapItem)
if err == nil {
processMap.Store(processMapItem.Id, processMapItem.Value)
}
}()
}

View File

@@ -35,8 +35,6 @@ func startTUN(fd C.int) {
closer, err := t.Start(f, gateway, portal, dns) closer, err := t.Start(f, gateway, portal, dns)
applyConfig(true)
if err != nil { if err != nil {
log.Errorln("startTUN error: %v", err) log.Errorln("startTUN error: %v", err)
tempTun.Close() tempTun.Close()
@@ -48,16 +46,6 @@ func startTUN(fd C.int) {
}() }()
} }
//export updateMarkSocketPort
func updateMarkSocketPort(markSocketPort C.longlong) bool {
tunLock.Lock()
defer tunLock.Unlock()
//if tun != nil {
// tun.MarkSocketPort = int64(markSocketPort)
//}
return true
}
//export stopTun //export stopTun
func stopTun() { func stopTun() {
go func() { go func() {
@@ -66,7 +54,6 @@ func stopTun() {
if tun != nil { if tun != nil {
tun.Close() tun.Close()
applyConfig(true)
tun = nil tun = nil
} }
}() }()

View File

@@ -82,7 +82,11 @@ class ApplicationState extends State<Application> {
super.initState(); super.initState();
globalState.appController = AppController(context); globalState.appController = AppController(context);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
globalState.appController.afterInit(); final currentContext = globalState.navigatorKey.currentContext;
if (currentContext != null) {
globalState.appController = AppController(currentContext);
}
await globalState.appController.init();
globalState.appController.initLink(); globalState.appController.initLink();
_updateGroups(); _updateGroups();
}); });

View File

@@ -46,8 +46,8 @@ class ClashCore {
bool init(String homeDir) { bool init(String homeDir) {
return clashFFI.initClash( return clashFFI.initClash(
homeDir.toNativeUtf8().cast(), homeDir.toNativeUtf8().cast(),
) == ) ==
1; 1;
} }
@@ -99,8 +99,9 @@ class ClashCore {
final groupNames = [ final groupNames = [
UsedProxy.GLOBAL.name, UsedProxy.GLOBAL.name,
...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) { ...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) {
final proxy = proxies[e]; final proxy = proxies[e] ?? {};
return GroupTypeExtension.valueList.contains(proxy['type']); return GroupTypeExtension.valueList.contains(proxy['type']) &&
proxy['hidden'] != true;
}) })
]; ];
final groupsRaw = groupNames.map((groupName) { final groupsRaw = groupNames.map((groupName) {
@@ -108,7 +109,7 @@ class ClashCore {
group["all"] = ((group["all"] ?? []) as List) group["all"] = ((group["all"] ?? []) as List)
.map( .map(
(name) => proxies[name], (name) => proxies[name],
) )
.toList(); .toList();
return group; return group;
}).toList(); }).toList();
@@ -119,14 +120,14 @@ class ClashCore {
Future<List<ExternalProvider>> getExternalProviders() { Future<List<ExternalProvider>> getExternalProviders() {
final externalProvidersRaw = clashFFI.getExternalProviders(); final externalProvidersRaw = clashFFI.getExternalProviders();
final externalProvidersRawString = final externalProvidersRawString =
externalProvidersRaw.cast<Utf8>().toDartString(); externalProvidersRaw.cast<Utf8>().toDartString();
return Isolate.run<List<ExternalProvider>>(() { return Isolate.run<List<ExternalProvider>>(() {
final externalProviders = final externalProviders =
(json.decode(externalProvidersRawString) as List<dynamic>) (json.decode(externalProvidersRawString) as List<dynamic>)
.map( .map(
(item) => ExternalProvider.fromJson(item), (item) => ExternalProvider.fromJson(item),
) )
.toList(); .toList();
return externalProviders; return externalProviders;
}); });
} }
@@ -198,6 +199,13 @@ class ClashCore {
return Traffic.fromMap(trafficMap); return Traffic.fromMap(trafficMap);
} }
Traffic getTotalTraffic() {
final trafficRaw = clashFFI.getTotalTraffic();
final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString());
return Traffic.fromMap(trafficMap);
}
void startLog() { void startLog() {
clashFFI.startLog(); clashFFI.startLog();
} }
@@ -210,14 +218,28 @@ class ClashCore {
clashFFI.startTUN(fd); clashFFI.startTUN(fd);
} }
requestGc() {
clashFFI.forceGc();
}
void stopTun() { void stopTun() {
clashFFI.stopTun(); clashFFI.stopTun();
} }
void setProcessMap(ProcessMapItem processMapItem) {
clashFFI.setProcessMap(json.encode(processMapItem).toNativeUtf8().cast());
}
// DateTime? getRunTime() {
// final runTimeString = clashFFI.getRunTime().cast<Utf8>().toDartString();
// if (runTimeString.isEmpty) return null;
// return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
// }
List<Connection> getConnections() { List<Connection> getConnections() {
final connectionsDataRaw = clashFFI.getConnections(); final connectionsDataRaw = clashFFI.getConnections();
final connectionsData = final connectionsData =
json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map; json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map;
final connectionsRaw = connectionsData['connections'] as List? ?? []; final connectionsRaw = connectionsData['connections'] as List? ?? [];
return connectionsRaw.map((e) => Connection.fromJson(e)).toList(); return connectionsRaw.map((e) => Connection.fromJson(e)).toList();
} }

View File

@@ -893,6 +893,14 @@ class ClashFFI {
_lookup<ffi.NativeFunction<GoUint8 Function()>>('shutdownClash'); _lookup<ffi.NativeFunction<GoUint8 Function()>>('shutdownClash');
late final _shutdownClash = _shutdownClashPtr.asFunction<int Function()>(); late final _shutdownClash = _shutdownClashPtr.asFunction<int Function()>();
void forceGc() {
return _forceGc();
}
late final _forceGcPtr =
_lookup<ffi.NativeFunction<ffi.Void Function()>>('forceGc');
late final _forceGc = _forceGcPtr.asFunction<void Function()>();
void validateConfig( void validateConfig(
ffi.Pointer<ffi.Char> s, ffi.Pointer<ffi.Char> s,
int port, int port,
@@ -975,6 +983,16 @@ class ClashFFI {
late final _getTraffic = late final _getTraffic =
_getTrafficPtr.asFunction<ffi.Pointer<ffi.Char> Function()>(); _getTrafficPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
ffi.Pointer<ffi.Char> getTotalTraffic() {
return _getTotalTraffic();
}
late final _getTotalTrafficPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
'getTotalTraffic');
late final _getTotalTraffic =
_getTotalTrafficPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
void asyncTestDelay( void asyncTestDelay(
ffi.Pointer<ffi.Char> s, ffi.Pointer<ffi.Char> s,
int port, int port,
@@ -1122,7 +1140,21 @@ class ClashFFI {
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stopLog'); _lookup<ffi.NativeFunction<ffi.Void Function()>>('stopLog');
late final _stopLog = _stopLogPtr.asFunction<void Function()>(); late final _stopLog = _stopLogPtr.asFunction<void Function()>();
int startTUN( void setProcessMap(
ffi.Pointer<ffi.Char> s,
) {
return _setProcessMap(
s,
);
}
late final _setProcessMapPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
'setProcessMap');
late final _setProcessMap =
_setProcessMapPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
void startTUN(
int fd, int fd,
) { ) {
return _startTUN( return _startTUN(
@@ -1131,22 +1163,8 @@ class ClashFFI {
} }
late final _startTUNPtr = late final _startTUNPtr =
_lookup<ffi.NativeFunction<GoUint8 Function(ffi.Int)>>('startTUN'); _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int)>>('startTUN');
late final _startTUN = _startTUNPtr.asFunction<int Function(int)>(); late final _startTUN = _startTUNPtr.asFunction<void Function(int)>();
int updateMarkSocketPort(
int markSocketPort,
) {
return _updateMarkSocketPort(
markSocketPort,
);
}
late final _updateMarkSocketPortPtr =
_lookup<ffi.NativeFunction<GoUint8 Function(ffi.LongLong)>>(
'updateMarkSocketPort');
late final _updateMarkSocketPort =
_updateMarkSocketPortPtr.asFunction<int Function(int)>();
void stopTun() { void stopTun() {
return _stopTun(); return _stopTun();

View File

@@ -14,9 +14,13 @@ abstract mixin class ClashMessageListener {
void onDelay(Delay delay) {} void onDelay(Delay delay) {}
void onProcess(Metadata metadata) {} void onProcess(Process process) {}
void onRequest(Connection connection) {}
void onNow(Now now) {} void onNow(Now now) {}
void onRun(String runTime) {}
} }
class ClashMessage { class ClashMessage {
@@ -41,11 +45,17 @@ class ClashMessage {
listener.onDelay(Delay.fromJson(m.data)); listener.onDelay(Delay.fromJson(m.data));
break; break;
case MessageType.process: case MessageType.process:
listener.onProcess(Metadata.fromJson(m.data)); listener.onProcess(Process.fromJson(m.data));
break; break;
case MessageType.now: case MessageType.now:
listener.onNow(Now.fromJson(m.data)); listener.onNow(Now.fromJson(m.data));
break; break;
case MessageType.request:
listener.onRequest(Connection.fromJson(m.data));
break;
case MessageType.run:
listener.onRun(m.data);
break;
} }
} }
}); });

View File

@@ -7,6 +7,7 @@ class Android {
init() async { init() async {
app?.onExit = () { app?.onExit = () {
clashCore.shutdown(); clashCore.shutdown();
print("adsadda==>");
exit(0); exit(0);
}; };
} }

View File

@@ -18,8 +18,11 @@ const configKey = "config";
const listItemPadding = EdgeInsets.symmetric(horizontal: 16); const listItemPadding = EdgeInsets.symmetric(horizontal: 16);
const double dialogCommonWidth = 300; const double dialogCommonWidth = 300;
const repository = "chen08209/FlClash"; const repository = "chen08209/FlClash";
const defaultExternalController = "127.0.0.1:9090";
const maxMobileWidth = 600; const maxMobileWidth = 600;
const maxLaptopWidth = 840; const maxLaptopWidth = 840;
const geodataLoaderMemconservative = "memconservative";
const geodataLoaderStandard = "standard";
final filter = ImageFilter.blur( final filter = ImageFilter.blur(
sigmaX: 5, sigmaX: 5,
sigmaY: 5, sigmaY: 5,

View File

@@ -29,6 +29,13 @@ class Navigation {
label: "profiles", label: "profiles",
fragment: ProfilesFragment(), fragment: ProfilesFragment(),
), ),
const NavigationItem(
icon: Icon(Icons.ballot),
label: "requests",
fragment: RequestFragment(),
description: "requestsDesc",
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
),
const NavigationItem( const NavigationItem(
icon: Icon(Icons.swap_vert_circle), icon: Icon(Icons.swap_vert_circle),
label: "resources", label: "resources",

View File

@@ -10,8 +10,7 @@ class Picker {
if (Platform.isAndroid) { if (Platform.isAndroid) {
filePickerResult = await FilePicker.platform.pickFiles( filePickerResult = await FilePicker.platform.pickFiles(
withData: true, withData: true,
type: FileType.custom, allowMultiple: false,
allowedExtensions: ['txt', 'conf'],
); );
} else { } else {
filePickerResult = await FilePicker.platform.pickFiles( filePickerResult = await FilePicker.platform.pickFiles(

View File

@@ -31,6 +31,7 @@ class AppController {
Future<void> updateSystemProxy(bool isStart) async { Future<void> updateSystemProxy(bool isStart) async {
if (isStart) { if (isStart) {
await globalState.startSystemProxy( await globalState.startSystemProxy(
appState: appState,
config: config, config: config,
clashConfig: clashConfig, clashConfig: clashConfig,
); );
@@ -145,7 +146,16 @@ class AppController {
if (isNotNeedUpdate == false || profile.type == ProfileType.file) { if (isNotNeedUpdate == false || profile.type == ProfileType.file) {
continue; continue;
} }
await updateProfile(profile.id); try {
await updateProfile(profile.id);
} catch (e) {
appState.addLog(
Log(
logLevel: LogLevel.info,
payload: e.toString(),
),
);
}
} }
} }
@@ -222,20 +232,21 @@ class AppController {
final tagName = data['tag_name']; final tagName = data['tag_name'];
final body = data['body']; final body = data['body'];
final submits = other.parseReleaseBody(body); final submits = other.parseReleaseBody(body);
final textTheme = context.textTheme;
globalState.showMessage( globalState.showMessage(
title: appLocalizations.discoverNewVersion, title: appLocalizations.discoverNewVersion,
message: TextSpan( message: TextSpan(
text: "$tagName \n", text: "$tagName \n",
style: context.textTheme.headlineSmall, style: textTheme.headlineSmall,
children: [ children: [
TextSpan( TextSpan(
text: "\n", text: "\n",
style: context.textTheme.bodyMedium, style: textTheme.bodyMedium,
), ),
for (final submit in submits) for (final submit in submits)
TextSpan( TextSpan(
text: "- $submit \n", text: "- $submit \n",
style: context.textTheme.bodyMedium, style: textTheme.bodyMedium,
), ),
], ],
), ),
@@ -256,6 +267,29 @@ class AppController {
} }
} }
init() async {
if (!config.silentLaunch) {
window?.show();
}
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted == true) {
await commonScaffoldState?.loadingRun(() async {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
},title: appLocalizations.init);
} else {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
}
await afterInit();
}
afterInit() async { afterInit() async {
if (config.autoRun) { if (config.autoRun) {
await updateSystemProxy(true); await updateSystemProxy(true);
@@ -265,9 +299,6 @@ class AppController {
} }
autoUpdateProfiles(); autoUpdateProfiles();
updateLogStatus(); updateLogStatus();
if (!config.silentLaunch) {
window?.show();
}
autoCheckUpdate(); autoCheckUpdate();
} }

View File

@@ -1,6 +1,6 @@
// ignore_for_file: constant_identifier_names // ignore_for_file: constant_identifier_names
enum GroupType { Selector, URLTest, Fallback } enum GroupType { Selector, URLTest, Fallback, LoadBalance, Relay }
enum GroupName { GLOBAL, Proxy, Auto, Fallback } enum GroupName { GLOBAL, Proxy, Auto, Fallback }
@@ -56,9 +56,11 @@ enum ProfileType { file, url }
enum ResultType { success, error } enum ResultType { success, error }
enum MessageType { log, tun, delay, process, now } enum MessageType { log, tun, delay, process, now, request, run }
enum FindProcessMode { always, off }
enum RecoveryOption { enum RecoveryOption {
all, all,
onlyProfiles, onlyProfiles,
} }

View File

@@ -8,6 +8,13 @@ import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.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 { class AccessFragment extends StatefulWidget {
const AccessFragment({super.key}); const AccessFragment({super.key});
@@ -83,136 +90,70 @@ class _AccessFragmentState extends State<AccessFragment> {
); );
} }
Widget _buildSelectedAllButton({ Widget _buildSearchButton(List<Package> packages) {
required bool isSelectedAll, return IconButton(
required List<String> allValueList, tooltip: appLocalizations.search,
}) { onPressed: () {
return Builder( showSearch(
builder: (context) { context: context,
final tooltip = isSelectedAll delegate: AccessControlSearchDelegate(
? appLocalizations.cancelSelectAll packages: packages,
: appLocalizations.selectAll; ),
return IconButton( ).then((_) => {setState(() {})});
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),
);
}, },
icon: const Icon(Icons.search),
); );
} }
Widget _actionHeader({ _buildSelectedAllButton({
required bool isAccessControl, required bool isAccessControl,
required List<String> valueList, required bool isSelectedAll,
required String describe, required List<String> allValueList,
required List<String> packageNameList,
}) { }) {
return AbsorbPointer( WidgetsBinding.instance.addPostFrameCallback((_) {
absorbing: !isAccessControl, final tooltip = isSelectedAll
child: Padding( ? appLocalizations.cancelSelectAll
padding: const EdgeInsets.only( : appLocalizations.selectAll;
top: 4, final commonScaffoldState =
bottom: 4, context.findAncestorStateOfType<CommonScaffoldState>();
left: 16, commonScaffoldState?.floatingActionButton = DisabledMask(
right: 8, status: !isAccessControl,
), child: AbsorbPointer(
child: Row( absorbing: !isAccessControl,
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: FloatingActionButton (
mainAxisSize: MainAxisSize.max, tooltip: tooltip,
children: [ onPressed: () {
Expanded( final config = globalState.appController.config;
child: IntrinsicHeight( final isAccept =
child: Column( config.accessControl.mode == AccessControlMode.acceptSelected;
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start, if (isSelectedAll) {
children: [ config.accessControl = switch (isAccept) {
Expanded( true => config.accessControl.copyWith(
child: Row( acceptList: [],
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( false => config.accessControl.copyWith(
child: Text(describe), rejectList: [],
) ),
], };
), } else {
), config.accessControl = switch (isAccept) {
), true => config.accessControl.copyWith(
Row( acceptList: allValueList,
mainAxisSize: MainAxisSize.min, ),
mainAxisAlignment: MainAxisAlignment.end, false => config.accessControl.copyWith(
children: [ rejectList: allValueList,
Flexible( ),
child: _buildSelectedAllButton( };
isSelectedAll: const ListEquality<String>() }
.equals(valueList, packageNameList), },
allValueList: packageNameList, child: isSelectedAll
), ? const Icon(Icons.deselect)
), : const Icon(Icons.select_all),
Flexible(child: _buildFilterSystemAppButton()), ),
Flexible(child: _buildAppProxyModePopup()),
],
),
],
), ),
), );
); });
} }
Widget _buildPackageList() { Widget _buildPackageList() {
@@ -252,14 +193,11 @@ class _AccessFragmentState extends State<AccessFragment> {
accessControlMode == AccessControlMode.acceptSelected accessControlMode == AccessControlMode.acceptSelected
? acceptPackages ? acceptPackages
: rejectPackages; : rejectPackages;
final currentList = final currentList = accessControl.currentList;
accessControlMode == AccessControlMode.acceptSelected
? accessControl.acceptList
: accessControl.rejectList;
final currentPackages = isFilterSystemApp final currentPackages = isFilterSystemApp
? packages ? packages
.where((element) => element.isSystem == false) .where((element) => element.isSystem == false)
.toList() .toList()
: packages; : packages;
final packageNameList = final packageNameList =
currentPackages.map((e) => e.packageName).toList(); currentPackages.map((e) => e.packageName).toList();
@@ -268,15 +206,91 @@ class _AccessFragmentState extends State<AccessFragment> {
accessControlMode == AccessControlMode.acceptSelected accessControlMode == AccessControlMode.acceptSelected
? appLocalizations.accessControlAllowDesc ? appLocalizations.accessControlAllowDesc
: appLocalizations.accessControlNotAllowDesc; : appLocalizations.accessControlNotAllowDesc;
_buildSelectedAllButton(
isAccessControl: isAccessControl,
isSelectedAll: valueList.length == packageNameList.length,
allValueList: packageNameList,
);
return DisabledMask( return DisabledMask(
status: !isAccessControl, status: !isAccessControl,
child: Column( child: Column(
children: [ children: [
_actionHeader( AbsorbPointer(
isAccessControl: isAccessControl, absorbing: !isAccessControl,
valueList: valueList, child: Padding(
describe: describe, padding: const EdgeInsets.only(
packageNameList: packageNameList, 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( Expanded(
flex: 1, flex: 1,
@@ -429,3 +443,111 @@ class PackageListItem extends StatelessWidget {
); );
} }
} }
class AccessControlSearchDelegate extends SearchDelegate {
final List<Package> packages;
AccessControlSearchDelegate({
required this.packages,
});
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 [
IconButton(
onPressed: () {
if (query.isEmpty) {
close(context, null);
return;
}
query = '';
},
icon: const Icon(Icons.clear),
),
const SizedBox(
width: 8,
)
];
}
@override
Widget? buildLeading(BuildContext context) {
return IconButton(
onPressed: () {
close(context, null);
},
icon: const Icon(Icons.arrow_back),
);
}
Widget _packageList(List<Package> packages) {
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 accessControlMode = accessControl.mode;
final currentList = accessControl.currentList;
final packageNameList =
this.packages.map((e) => e.packageName).toList();
final valueList = currentList.intersection(packageNameList);
return DisabledMask(
status: !isAccessControl,
child: 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,
);
}
},
);
},
),
);
},
);
}
@override
Widget buildResults(BuildContext context) {
return buildSuggestions(context);
}
@override
Widget buildSuggestions(BuildContext context) {
return _packageList(_results);
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
@@ -37,16 +39,132 @@ class _ConfigFragmentState extends State<ConfigFragment> {
} }
} }
_updateLoglevel(LogLevel? logLevel) { Widget _buildAppSection() {
if (logLevel == null || final items = [
logLevel == globalState.appController.clashConfig.logLevel) return; if (Platform.isAndroid)
globalState.appController.clashConfig.logLevel = logLevel; Selector<Config, bool>(
globalState.appController.updateClashConfigDebounce(); 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;
},
),
);
},
),
if (Platform.isAndroid)
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.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.updateClashConfig(isPatch: false);
await appController.updateGroups();
appController.changeProxy();
},
),
);
},
),
];
return Section(
title: appLocalizations.app,
child: Column(
children: [
for (final item in items) ...[
item,
if (items.last != item)
const Divider(
height: 0,
)
]
],
),
);
} }
@override _showLogLevelDialog(LogLevel value) {
Widget build(BuildContext context) { globalState.showCommonDialog(
List<Widget> items = [ 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),
)
],
),
),
),
);
}
Widget _buildGeneralSection() {
final 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),
onTab: () {
_showLogLevelDialog(value);
},
);
},
),
Selector<ClashConfig, int>( Selector<ClashConfig, int>(
selector: (_, clashConfig) => clashConfig.mixedPort, selector: (_, clashConfig) => clashConfig.mixedPort,
builder: (_, mixedPort, __) { builder: (_, mixedPort, __) {
@@ -54,9 +172,9 @@ class _ConfigFragmentState extends State<ConfigFragment> {
onTab: () { onTab: () {
_modifyMixedPort(mixedPort); _modifyMixedPort(mixedPort);
}, },
padding: const EdgeInsets.symmetric(horizontal: 16,vertical: 4), leading: const Icon(Icons.adjust_outlined),
leading: const Icon(Icons.adjust),
title: Text(appLocalizations.proxyPort), title: Text(appLocalizations.proxyPort),
subtitle: Text(appLocalizations.proxyPortDesc),
trailing: FilledButton.tonal( trailing: FilledButton.tonal(
onPressed: () { onPressed: () {
_modifyMixedPort(mixedPort); _modifyMixedPort(mixedPort);
@@ -68,6 +186,24 @@ class _ConfigFragmentState extends State<ConfigFragment> {
); );
}, },
), ),
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, bool>(
selector: (_, clashConfig) => clashConfig.allowLan, selector: (_, clashConfig) => clashConfig.allowLan,
builder: (_, allowLan, __) { builder: (_, allowLan, __) {
@@ -86,92 +222,175 @@ class _ConfigFragmentState extends State<ConfigFragment> {
); );
}, },
), ),
// if (system.isDesktop) Selector<ClashConfig, bool>(
// Selector<ClashConfig, bool>( selector: (_, clashConfig) => clashConfig.unifiedDelay,
// selector: (_, clashConfig) => clashConfig.tun.enable, builder: (_, unifiedDelay, __) {
// builder: (_, tunEnable, __) {
// return ListItem.switchItem(
// leading: const Icon(Icons.support),
// 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();
// },
// ),
// );
// },
// ),
Selector<Config, bool>(
selector: (_, config) => config.isCompatible,
builder: (_, isCompatible, __) {
return ListItem.switchItem( return ListItem.switchItem(
leading: const Icon(Icons.expand), leading: const Icon(Icons.compress_outlined),
title: Text(appLocalizations.compatible), title: Text(appLocalizations.unifiedDelay),
subtitle: Text(appLocalizations.compatibleDesc), subtitle: Text(appLocalizations.unifiedDelayDesc),
delegate: SwitchDelegate( delegate: SwitchDelegate(
value: isCompatible, value: unifiedDelay,
onChanged: (bool value) async { onChanged: (bool value) async {
final appController = globalState.appController; final appController = globalState.appController;
appController.config.isCompatible = value; appController.clashConfig.unifiedDelay = value;
await appController.updateClashConfig(isPatch: false); appController.updateClashConfigDebounce();
await appController.updateGroups();
appController.changeProxy();
}, },
), ),
); );
}, },
), ),
Padding( Selector<ClashConfig, bool>(
padding: kMaterialListPadding, selector: (_, clashConfig) =>
child: Selector<ClashConfig, LogLevel>( clashConfig.findProcessMode == FindProcessMode.always,
selector: (_, clashConfig) => clashConfig.logLevel, builder: (_, findProcess, __) {
builder: (_, value, __) { return ListItem.switchItem(
return ListItem( leading: const Icon(Icons.polymer_outlined),
leading: const Icon(Icons.feedback), title: Text(appLocalizations.findProcessMode),
title: Text(appLocalizations.logLevel), subtitle: Text(appLocalizations.findProcessModeDesc),
trailing: SizedBox( delegate: SwitchDelegate(
height: 48, value: findProcess,
child: DropdownMenu<LogLevel>( onChanged: (bool value) async {
width: 124, final appController = globalState.appController;
inputDecorationTheme: const InputDecorationTheme( appController.clashConfig.findProcessMode =
filled: true, value ? FindProcessMode.always : FindProcessMode.off;
contentPadding: EdgeInsets.symmetric( appController.updateClashConfigDebounce();
vertical: 5, },
horizontal: 16, ),
), );
), },
initialSelection: value, ),
dropdownMenuEntries: [ Selector<ClashConfig, bool>(
for (final logLevel in LogLevel.values) selector: (_, clashConfig) => clashConfig.tcpConcurrent,
DropdownMenuEntry<LogLevel>( builder: (_, tcpConcurrent, __) {
value: logLevel, return ListItem.switchItem(
label: logLevel.name, leading: const Icon(Icons.double_arrow_outlined),
) title: Text(appLocalizations.tcpConcurrent),
], subtitle: Text(appLocalizations.tcpConcurrentDesc),
onSelected: _updateLoglevel, 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();
},
),
);
},
),
];
return Section(
title: appLocalizations.general,
child: Column(
children: [
for (final item in items) ...[
item,
if (items.last != item)
const Divider(
height: 0,
)
]
],
),
);
}
Widget _buildMoreSection() {
final items = [
if (false)
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();
},
), ),
); );
}, },
), ),
),
]; ];
return ListView.separated( if(items.isEmpty) return Container();
return Section(
title: appLocalizations.general,
child: Column(
children: [
for (final item in items) ...[
item,
if (items.last != item)
const Divider(
height: 0,
)
]
],
),
);
}
@override
Widget build(BuildContext context) {
List<Widget> items = [
_buildAppSection(),
_buildGeneralSection(),
_buildMoreSection(),
];
return ListView.builder(
padding: const EdgeInsets.only(bottom: 32),
itemBuilder: (_, index) { itemBuilder: (_, index) {
return Container( return Container(
alignment: Alignment.center, alignment: Alignment.center,
child: items[index], child: items[index],
); );
}, },
separatorBuilder: (_, __) {
return const Divider(
height: 0,
);
},
itemCount: items.length, itemCount: items.length,
); );
} }

View File

@@ -1,140 +1,140 @@
import 'dart:async'; // import 'dart:async';
//
import 'package:fl_clash/clash/core.dart'; // import 'package:fl_clash/clash/core.dart';
import 'package:fl_clash/models/models.dart'; // import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart'; // import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart'; // import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart'; // import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; // import 'package:flutter/material.dart';
//
class ConnectionsFragment extends StatefulWidget { // class ConnectionsFragment extends StatefulWidget {
const ConnectionsFragment({super.key}); // const ConnectionsFragment({super.key});
//
@override // @override
State<ConnectionsFragment> createState() => _ConnectionsFragmentState(); // State<ConnectionsFragment> createState() => _ConnectionsFragmentState();
} // }
//
class _ConnectionsFragmentState extends State<ConnectionsFragment> { // class _ConnectionsFragmentState extends State<ConnectionsFragment> {
final connectionsNotifier = ValueNotifier<List<Connection>>([]); // final connectionsNotifier = ValueNotifier<List<Connection>>([]);
Map<String, String?> idPackageNameMap = {}; // Map<String, String?> idPackageNameMap = {};
//
Timer? timer; // Timer? timer;
//
@override // @override
void initState() { // void initState() {
super.initState(); // super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { // WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_getConnections(); // _getConnections();
if (timer != null) { // if (timer != null) {
timer?.cancel(); // timer?.cancel();
timer = null; // timer = null;
} // }
timer = Timer.periodic(const Duration(seconds: 3), (timer) { // timer = Timer.periodic(const Duration(seconds: 3), (timer) {
if (mounted) { // if (mounted) {
_getConnections(); // _getConnections();
} // }
}); // });
}); // });
} // }
//
_getConnections() { // _getConnections() {
connectionsNotifier.value = clashCore // connectionsNotifier.value = clashCore
.getConnections(); // .getConnections();
} // }
//
@override // @override
void dispose() { // void dispose() {
super.dispose(); // super.dispose();
timer?.cancel(); // timer?.cancel();
timer = null; // timer = null;
} // }
//
Future<ImageProvider?> _getPackageIconWithConnection( // Future<ImageProvider?> _getPackageIconWithConnection(
Connection connection) async { // Connection connection) async {
final uid = connection.metadata.uid; // final uid = connection.metadata.uid;
// if(globalState.packageNameMap[uid] == null){ // // if(globalState.packageNameMap[uid] == null){
// globalState.packageNameMap[uid] = await app?.getPackageName(connection.metadata); // // globalState.packageNameMap[uid] = await app?.getPackageName(connection.metadata);
// } // // }
final packageName = globalState.packageNameMap[uid]; // final packageName = globalState.packageNameMap[uid];
if(packageName == null) return null; // if(packageName == null) return null;
return await app?.getPackageIcon(packageName); // return await app?.getPackageIcon(packageName);
} // }
//
@override // @override
Widget build(BuildContext context) { // Widget build(BuildContext context) {
return ValueListenableBuilder<List<Connection>>( // return ValueListenableBuilder<List<Connection>>(
valueListenable: connectionsNotifier, // valueListenable: connectionsNotifier,
builder: (_, List<Connection> connections, __) { // builder: (_, List<Connection> connections, __) {
if (connections.isEmpty) { // if (connections.isEmpty) {
return const NullStatus( // return const NullStatus(
label: "未开启代理,或者没有连接数据", // label: "未开启代理,或者没有连接数据",
); // );
} // }
return ListView.separated( // return ListView.separated(
physics: const AlwaysScrollableScrollPhysics(), // physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (_, index) { // itemBuilder: (_, index) {
final connection = connections[index]; // final connection = connections[index];
return ListTile( // return ListTile(
titleAlignment: ListTileTitleAlignment.top, // titleAlignment: ListTileTitleAlignment.top,
leading: Container( // leading: Container(
margin: const EdgeInsets.only(top: 4), // margin: const EdgeInsets.only(top: 4),
width: 48, // width: 48,
height: 48, // height: 48,
child: FutureBuilder<ImageProvider?>( // child: FutureBuilder<ImageProvider?>(
future: _getPackageIconWithConnection(connection), // future: _getPackageIconWithConnection(connection),
builder: (_, snapshot) { // builder: (_, snapshot) {
if (!snapshot.hasData && snapshot.data == null) { // if (!snapshot.hasData && snapshot.data == null) {
return Container(); // return Container();
} else { // } else {
return Image( // return Image(
image: snapshot.data!, // image: snapshot.data!,
gaplessPlayback: true, // gaplessPlayback: true,
width: 48, // width: 48,
height: 48, // height: 48,
); // );
} // }
}, // },
), // ),
), // ),
contentPadding: // contentPadding:
const EdgeInsets.symmetric(vertical: 12, horizontal: 16), // const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
title: Column( // title: Column(
crossAxisAlignment: CrossAxisAlignment.start, // crossAxisAlignment: CrossAxisAlignment.start,
children: [ // children: [
Text(connection.metadata.host.isNotEmpty // Text(connection.metadata.host.isNotEmpty
? connection.metadata.host // ? connection.metadata.host
: connection.metadata.destinationIP), // : connection.metadata.destinationIP),
Padding( // Padding(
padding: const EdgeInsets.only( // padding: const EdgeInsets.only(
top: 12, // top: 12,
), // ),
child: Wrap( // child: Wrap(
runSpacing: 8, // runSpacing: 8,
spacing: 8, // spacing: 8,
children: [ // children: [
for (final chain in connection.chains) // for (final chain in connection.chains)
CommonChip( // CommonChip(
label: chain, // label: chain,
), // ),
], // ],
), // ),
), // ),
], // ],
), // ),
trailing: IconButton( // trailing: IconButton(
icon: const Icon(Icons.block), // icon: const Icon(Icons.block),
onPressed: () {}, // onPressed: () {},
), // ),
); // );
}, // },
separatorBuilder: (BuildContext context, int index) { // separatorBuilder: (BuildContext context, int index) {
return const Divider( // return const Divider(
height: 0, // height: 0,
); // );
}, // },
itemCount: connections.length, // itemCount: connections.length,
); // );
}, // },
); // );
} // }
} // }

View File

@@ -25,6 +25,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
bool isStart, bool isStart,
) async { ) async {
if (!isInit) return; if (!isInit) return;
timeoutNotifier.value = false;
if (_preIsStart == false && _preIsStart == isStart) return; if (_preIsStart == false && _preIsStart == isStart) return;
if (cancelToken != null) { if (cancelToken != null) {
cancelToken!.cancel(); cancelToken!.cancel();
@@ -38,6 +39,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
} else { } else {
timeoutNotifier.value = false; timeoutNotifier.value = false;
} }
_preIsStart = isStart;
ipInfoNotifier.value = ipInfo; ipInfoNotifier.value = ipInfo;
} }
@@ -150,9 +152,10 @@ class _NetworkDetectionState extends State<NetworkDetection> {
builder: (_, timeout, __) { builder: (_, timeout, __) {
if (timeout) { if (timeout) {
return Text( return Text(
appLocalizations.ipCheckTimeout, "timeout",
style: context.textTheme.titleLarge style: context.textTheme.titleMedium
?.toSoftBold(), ?.copyWith(color: Colors.red)
.toSoftBold(),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
); );

View File

@@ -21,26 +21,17 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
.asMap() .asMap()
.map( .map(
(index, e) => MapEntry( (index, e) => MapEntry(
index, index,
Point( Point(
(index + initPoints.length).toDouble(), (index + initPoints.length).toDouble(),
e.speed.toDouble(), e.speed.toDouble(),
), ),
), ),
) )
.values .values
.toList(); .toList();
var pointsRaw = [...initPoints, ...trafficPoints];
List<Point> points;
if (pointsRaw.length > 60) {
points = pointsRaw
.getRange(pointsRaw.length - 61, pointsRaw.length - 1)
.toList();
} else {
points = pointsRaw;
}
return points; return [...initPoints, ...trafficPoints];
} }
Traffic _getLastTraffic(List<Traffic> traffics) { Traffic _getLastTraffic(List<Traffic> traffics) {
@@ -53,11 +44,10 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
required IconData iconData, required IconData iconData,
required TrafficValue value, required TrafficValue value,
}) { }) {
final showValue = value.showValue; final showValue = value.showValue;
final showUnit = "${value.showUnit}/s"; final showUnit = "${value.showUnit}/s";
final titleLargeSoftBold = final titleLargeSoftBold =
Theme.of(context).textTheme.titleLarge?.toSoftBold(); Theme.of(context).textTheme.titleLarge?.toSoftBold();
final bodyMedium = Theme.of(context).textTheme.bodySmall?.toLight(); final bodyMedium = Theme.of(context).textTheme.bodySmall?.toLight();
final valueText = Text( final valueText = Text(
showValue, showValue,
@@ -121,7 +111,7 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CommonCard( return CommonCard(
info: Info( info: Info(
label: appLocalizations.networkSpeed, label: appLocalizations.networkSpeed,
iconData: Icons.speed, iconData: Icons.speed,
), ),
@@ -172,4 +162,4 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
), ),
); );
} }
} }

View File

@@ -55,21 +55,11 @@ class TrafficUsage extends StatelessWidget {
label: appLocalizations.trafficUsage, label: appLocalizations.trafficUsage,
iconData: Icons.data_saver_off, iconData: Icons.data_saver_off,
), ),
child: Selector<AppState, List<Traffic>>( child: Selector<AppState, Traffic>(
selector: (_, appState) => appState.traffics, selector: (_, appState) => appState.totalTraffic,
builder: (_, traffics, __) { builder: (_, totalTraffic, __) {
final trafficTotal = traffics.isNotEmpty final upTotalTrafficValue = totalTraffic.up;
? traffics.reduce( final downTotalTrafficValue = totalTraffic.down;
(value, element) {
return Traffic(
up: element.up.value + value.up.value,
down: element.down.value + value.down.value,
);
},
)
: Traffic();
final upTrafficValue = trafficTotal.up;
final downTrafficValue = trafficTotal.down;
return Padding( return Padding(
padding: const EdgeInsets.all(16).copyWith(top: 0), padding: const EdgeInsets.all(16).copyWith(top: 0),
child: Column( child: Column(
@@ -80,7 +70,7 @@ class TrafficUsage extends StatelessWidget {
child: getTrafficDataItem( child: getTrafficDataItem(
context, context,
Icons.arrow_upward, Icons.arrow_upward,
upTrafficValue, upTotalTrafficValue,
), ),
), ),
const SizedBox( const SizedBox(
@@ -91,7 +81,7 @@ class TrafficUsage extends StatelessWidget {
child: getTrafficDataItem( child: getTrafficDataItem(
context, context,
Icons.arrow_downward, Icons.arrow_downward,
downTrafficValue, downTotalTrafficValue,
), ),
), ),
], ],

View File

@@ -9,4 +9,5 @@ export 'config.dart';
export 'application_setting.dart'; export 'application_setting.dart';
export 'about.dart'; export 'about.dart';
export 'backup_and_recovery.dart'; export 'backup_and_recovery.dart';
export 'resources.dart'; export 'resources.dart';
export 'requests.dart';

View File

@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
@@ -22,14 +23,21 @@ class _LogsFragmentState extends State<LogsFragment> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { WidgetsBinding.instance.addPostFrameCallback((_) {
logsNotifier.value = context.read<AppState>().logs; final appState = globalState.appController.appState;
logsNotifier.value = List<Log>.from(appState.logs);
if (timer != null) { if (timer != null) {
timer?.cancel(); timer?.cancel();
timer = null; timer = null;
} }
timer = Timer.periodic(const Duration(milliseconds: 200), (timer) { timer = Timer.periodic(const Duration(milliseconds: 200), (timer) {
logsNotifier.value = globalState.appController.appState.logs; final logs = List<Log>.from(appState.logs);
if (!const ListEquality<Log>().equals(
logsNotifier.value,
logs,
)) {
logsNotifier.value = logs;
}
}); });
}); });
} }

View File

@@ -152,6 +152,7 @@ class _EditProfileState extends State<EditProfile> {
vertical: 16, vertical: 16,
), ),
child: ListView.separated( child: ListView.separated(
primary: true,
itemBuilder: (_, index) { itemBuilder: (_, index) {
return items[index]; return items[index];
}, },

View File

@@ -1,5 +1,6 @@
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/fragments/profiles/edit_profile.dart'; import 'package:fl_clash/fragments/profiles/edit_profile.dart';
import 'package:fl_clash/fragments/profiles/view_profile.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
@@ -15,11 +16,19 @@ enum ProfileActions {
edit, edit,
update, update,
delete, delete,
view,
} }
class ProfilesFragment extends StatelessWidget { class ProfilesFragment extends StatefulWidget {
const ProfilesFragment({super.key}); const ProfilesFragment({super.key});
@override
State<ProfilesFragment> createState() => _ProfilesFragmentState();
}
class _ProfilesFragmentState extends State<ProfilesFragment> {
final hasPadding = ValueNotifier<bool>(false);
_handleShowAddExtendPage() { _handleShowAddExtendPage() {
showExtendPage( showExtendPage(
globalState.navigatorKey.currentState!.context, globalState.navigatorKey.currentState!.context,
@@ -41,7 +50,7 @@ class ProfilesFragment extends StatelessWidget {
} }
} }
_initScaffoldState(BuildContext context) { _initScaffoldState() {
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance.addPostFrameCallback(
(_) { (_) {
final commonScaffoldState = final commonScaffoldState =
@@ -55,7 +64,7 @@ class ProfilesFragment extends StatelessWidget {
}, },
); );
}, },
icon: const Icon(Icons.download), icon: const Icon(Icons.sync),
), ),
const SizedBox( const SizedBox(
width: 8, width: 8,
@@ -78,7 +87,7 @@ class ProfilesFragment extends StatelessWidget {
selector: (_, appState) => appState.currentLabel == 'profiles', selector: (_, appState) => appState.currentLabel == 'profiles',
builder: (_, isCurrent, child) { builder: (_, isCurrent, child) {
if (isCurrent) { if (isCurrent) {
_initScaffoldState(context); _initScaffoldState();
} }
return child!; return child!;
}, },
@@ -98,27 +107,46 @@ class ProfilesFragment extends StatelessWidget {
final isMobile = state.viewMode == ViewMode.mobile; final isMobile = state.viewMode == ViewMode.mobile;
return Align( return Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: SingleChildScrollView( child: NotificationListener<ScrollNotification>(
padding: !isMobile onNotification: (scrollNotification) {
? const EdgeInsets.symmetric( WidgetsBinding.instance.addPostFrameCallback((_) {
horizontal: 16, hasPadding.value =
vertical: 16, scrollNotification.metrics.maxScrollExtent > 0;
) });
: EdgeInsets.zero, return true;
child: Grid( },
mainAxisSpacing: isMobile ? 8 : 16, child: ValueListenableBuilder(
crossAxisSpacing: 16, valueListenable: hasPadding,
crossAxisCount: columns, builder: (_, hasPadding, __) {
children: [ return SingleChildScrollView(
for (final profile in state.profiles) padding: !isMobile
GridItem( ? EdgeInsets.only(
child: ProfileItem( left: 16,
profile: profile, right: 16,
groupValue: state.currentProfileId, top: 16,
onChanged: globalState.appController.changeProfile, bottom: 16 + (hasPadding ? 56 : 0),
), )
: EdgeInsets.only(
bottom: 0 + (hasPadding ? 56 : 0),
),
child: Grid(
mainAxisSpacing: isMobile ? 8 : 16,
crossAxisSpacing: 16,
crossAxisCount: columns,
children: [
for (final profile in state.profiles)
GridItem(
child: ProfileItem(
profile: profile,
groupValue: state.currentProfileId,
onChanged:
globalState.appController.changeProfile,
),
),
],
), ),
], );
},
), ),
), ),
); );
@@ -172,6 +200,16 @@ class _ProfileItemState extends State<ProfileItem> {
); );
} }
_handleViewProfile(Profile profile) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ViewProfile(
profile: profile.copyWith(),
),
),
);
}
_buildTitle(Profile profile) { _buildTitle(Profile profile) {
final textTheme = context.textTheme; final textTheme = context.textTheme;
final userInfo = profile.userInfo ?? UserInfo(); final userInfo = profile.userInfo ?? UserInfo();
@@ -181,7 +219,7 @@ class _ProfileItemState extends State<ProfileItem> {
final totalShow = TrafficValue(value: total).show; final totalShow = TrafficValue(value: total).show;
final progress = total == 0 ? 0.0 : use / total; final progress = total == 0 ? 0.0 : use / total;
final expireShow = userInfo.expire == 0 final expireShow = userInfo.expire == 0
? "长期有效" ? appLocalizations.infiniteTime
: DateTime.fromMillisecondsSinceEpoch(userInfo.expire * 1000).show; : DateTime.fromMillisecondsSinceEpoch(userInfo.expire * 1000).show;
return Container( return Container(
padding: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(vertical: 4),
@@ -229,7 +267,7 @@ class _ProfileItemState extends State<ProfileItem> {
Row( Row(
children: [ children: [
Text( Text(
"到期时间:", appLocalizations.expirationTime,
style: textTheme.labelMedium?.toLighter(), style: textTheme.labelMedium?.toLighter(),
), ),
const SizedBox( const SizedBox(
@@ -290,6 +328,11 @@ class _ProfileItemState extends State<ProfileItem> {
label: appLocalizations.delete, label: appLocalizations.delete,
iconData: Icons.delete, iconData: Icons.delete,
), ),
// CommonPopupMenuItem(
// action: ProfileActions.view,
// label: "查看",
// iconData: Icons.visibility,
// ),
], ],
onSelected: (ProfileActions? action) async { onSelected: (ProfileActions? action) async {
switch (action) { switch (action) {
@@ -302,6 +345,9 @@ class _ProfileItemState extends State<ProfileItem> {
case ProfileActions.update: case ProfileActions.update:
_handleUpdateProfile(profile.id); _handleUpdateProfile(profile.id);
break; break;
case ProfileActions.view:
_handleViewProfile(profile);
break;
case null: case null:
break; break;
} }

View File

@@ -0,0 +1,171 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart';
import 'package:re_editor/re_editor.dart';
import 'package:re_highlight/languages/yaml.dart';
import 'package:re_highlight/styles/intellij-light.dart';
class ViewProfile extends StatefulWidget {
final Profile profile;
const ViewProfile({
super.key,
required this.profile,
});
@override
State<ViewProfile> createState() => _ViewProfileState();
}
class _ViewProfileState extends State<ViewProfile> {
bool readOnly = true;
final controller = CodeLineEditingController();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final profilePath = await appPath.getProfilePath(widget.profile.id);
if (profilePath == null) {
return;
}
final file = File(profilePath);
final text = await file.readAsString();
controller.text = text;
// _codeController.text = text;
});
}
@override
Widget build(BuildContext context) {
return CommonScaffold(
actions: [
IconButton(
onPressed: controller.undo,
icon: const Icon(Icons.undo),
),
IconButton(
onPressed: controller.redo,
icon: const Icon(Icons.redo),
),
const SizedBox(
width: 8,
)
],
body: CodeEditor(
autofocus: false,
readOnly: readOnly,
scrollbarBuilder: (context, child, details) {
return Scrollbar(
controller: details.controller,
thumbVisibility: true,
interactive: true,
child: child,
);
},
showCursorWhenReadOnly: false,
controller: controller,
toolbarController: const ContextMenuControllerImpl(),
shortcutsActivatorsBuilder:
const DefaultCodeShortcutsActivatorsBuilder(),
indicatorBuilder:
(context, editingController, chunkController, notifier) {
return Row(
children: [
DefaultCodeLineNumber(
controller: editingController,
notifier: notifier,
),
DefaultCodeChunkIndicator(
width: 20,
controller: chunkController,
notifier: notifier,
)
],
);
},
style: CodeEditorStyle(
fontSize: 14,
codeTheme: CodeHighlightTheme(
languages: {
'yaml': CodeHighlightThemeMode(
mode: langYaml,
)
},
theme: intellijLightTheme,
),
),
),
title: "查看",
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
readOnly = !readOnly;
});
},
child: readOnly ? const Icon(Icons.edit) : const Icon(Icons.save),
),
);
}
}
class ContextMenuItemWidget extends PopupMenuItem<void> {
ContextMenuItemWidget({
super.key,
required String text,
required VoidCallback super.onTap,
}) : super(child: Text(text));
}
class ContextMenuControllerImpl implements SelectionToolbarController {
const ContextMenuControllerImpl();
@override
void hide(BuildContext context) {}
@override
void show({
required BuildContext context,
required CodeLineEditingController controller,
required TextSelectionToolbarAnchors anchors,
Rect? renderRect,
required LayerLink layerLink,
required ValueNotifier<bool> visibility,
}) {
if (controller.selectedText.isEmpty) {
return;
}
showMenu(
context: context,
popUpAnimationStyle: AnimationStyle.noAnimation,
position: RelativeRect.fromSize(
(anchors.secondaryAnchor ?? anchors.primaryAnchor) &
const Size(150, double.infinity),
MediaQuery.of(context).size,
),
items: [
ContextMenuItemWidget(
text: 'Cut',
onTap: () {
controller.cut();
},
),
ContextMenuItemWidget(
text: 'Copy',
onTap: () {
controller.copy();
},
),
ContextMenuItemWidget(
text: 'Paste',
onTap: () {
controller.paste();
},
),
],
);
}
}

View File

@@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -59,6 +60,23 @@ class _ProxiesFragmentState extends State<ProxiesFragment>
}); });
} }
_handleTabControllerChange() {
final indexIsChanging = _tabController?.indexIsChanging ?? false;
if (indexIsChanging) return;
final index = _tabController?.index;
if (index == null) return;
final appController = globalState.appController;
final currentGroups = appController.appState.currentGroups;
if (currentGroups.length > index) {
appController.config.updateCurrentGroupName(currentGroups[index].name);
}
}
@override
void dispose() {
super.dispose();
_tabController?.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Selector<AppState, bool>( return Selector<AppState, bool>(
@@ -69,26 +87,34 @@ class _ProxiesFragmentState extends State<ProxiesFragment>
} }
return child!; return child!;
}, },
child: Selector3<AppState, Config, ClashConfig, ProxiesSelectorState>( child: Selector2<AppState, Config, ProxiesSelectorState>(
selector: (_, appState, config, clashConfig) { selector: (_, appState, config) {
final currentGroups = appState.currentGroups; final currentGroups = appState.currentGroups;
final groupNames = currentGroups.map((e) => e.name).toList(); final groupNames = currentGroups.map((e) => e.name).toList();
return ProxiesSelectorState( return ProxiesSelectorState(
groupNames: groupNames, groupNames: groupNames,
currentGroupName: config.currentGroupName,
); );
}, },
shouldRebuild: (prev, next) { shouldRebuild: (prev, next) {
if (prev.groupNames.length != next.groupNames.length) { if (!const ListEquality<String>()
.equals(prev.groupNames, next.groupNames)) {
_tabController?.removeListener(_handleTabControllerChange);
_tabController?.dispose(); _tabController?.dispose();
_tabController = null; _tabController = null;
return true;
} }
return prev != next; return false;
}, },
builder: (_, state, __) { builder: (_, state, __) {
final index = state.groupNames.indexWhere(
(item) => item == state.currentGroupName,
);
_tabController ??= TabController( _tabController ??= TabController(
length: state.groupNames.length, length: state.groupNames.length,
initialIndex: index == -1 ? 0 : index,
vsync: this, vsync: this,
); )..addListener(_handleTabControllerChange);
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -99,7 +125,8 @@ class _ProxiesFragmentState extends State<ProxiesFragment>
dividerColor: Colors.transparent, dividerColor: Colors.transparent,
isScrollable: true, isScrollable: true,
tabAlignment: TabAlignment.start, tabAlignment: TabAlignment.start,
overlayColor: const WidgetStatePropertyAll(Colors.transparent), overlayColor:
const WidgetStatePropertyAll(Colors.transparent),
tabs: [ tabs: [
for (final groupName in state.groupNames) for (final groupName in state.groupNames)
Tab( Tab(
@@ -199,10 +226,16 @@ class ProxiesTabView extends StatelessWidget {
_delayTest(List<Proxy> proxies) async { _delayTest(List<Proxy> proxies) async {
for (final proxy in proxies) { for (final proxy in proxies) {
final appController = globalState.appController;
final proxyName =
appController.appState.getRealProxyName(proxy.name) ?? proxy.name;
globalState.appController.setDelay( globalState.appController.setDelay(
Delay(name: proxy.name, value: 0), Delay(
name: proxyName,
value: 0,
),
); );
clashCore.getDelay(proxy.name).then((delay) { clashCore.getDelay(proxyName).then((delay) {
globalState.appController.setDelay(delay); globalState.appController.setDelay(delay);
}); });
} }
@@ -434,7 +467,7 @@ class _DelayTestButtonContainerState extends State<DelayTestButtonContainer>
_controller = AnimationController( _controller = AnimationController(
vsync: this, vsync: this,
duration: const Duration( duration: const Duration(
milliseconds: 600, milliseconds: 200,
), ),
); );
_scale = Tween<double>( _scale = Tween<double>(

178
lib/fragments/requests.dart Normal file
View File

@@ -0,0 +1,178 @@
import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
class RequestFragment extends StatefulWidget {
const RequestFragment({super.key});
@override
State<RequestFragment> createState() => _RequestFragmentState();
}
class _RequestFragmentState extends State<RequestFragment> {
final requestsNotifier = ValueNotifier<List<Connection>>([]);
final ScrollController _scrollController = ScrollController(
keepScrollOffset: false,
);
Timer? timer;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final appState = globalState.appController.appState;
requestsNotifier.value = List<Connection>.from(appState.requests);
if (timer != null) {
timer?.cancel();
timer = null;
}
timer = Timer.periodic(const Duration(milliseconds: 200), (timer) {
final requests = List<Connection>.from(appState.requests);
if (!const ListEquality<Connection>().equals(
requestsNotifier.value,
requests,
)) {
requestsNotifier.value = requests;
}
});
});
}
@override
void dispose() {
super.dispose();
timer?.cancel();
_scrollController.dispose();
timer = null;
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<List<Connection>>(
valueListenable: requestsNotifier,
builder: (_, List<Connection> connections, __) {
if (connections.isEmpty) {
return NullStatus(
label: appLocalizations.nullRequestsDesc,
);
}
connections = connections.reversed.toList();
return ListView.separated(
controller: _scrollController,
itemBuilder: (_, index) {
final connection = connections[index];
return RequestItem(
key: Key(connection.id),
connection: connection,
);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider(
height: 0,
);
},
itemCount: connections.length,
);
},
);
}
}
class RequestItem extends StatelessWidget {
final Connection connection;
const RequestItem({
super.key,
required this.connection,
});
Future<ImageProvider?> _getPackageIcon(Connection connection) async {
return await app?.getPackageIcon(connection.metadata.process);
}
String _getRequestText(Metadata metadata) {
var text = "${metadata.network}:://";
final ips = [
metadata.host,
metadata.destinationIP,
].where((ip) => ip.isNotEmpty);
text += ips.join("/");
text += ":${metadata.destinationPort}";
return text;
}
String _getSourceText(Connection connection) {
final metadata = connection.metadata;
if (metadata.process.isEmpty) {
return connection.start.lastUpdateTimeDesc;
}
return "${metadata.process} · ${connection.start.lastUpdateTimeDesc}";
}
@override
Widget build(BuildContext context) {
return ListTile(
titleAlignment: ListTileTitleAlignment.top,
leading: Platform.isAndroid
? Container(
margin: const EdgeInsets.only(top: 4),
width: 48,
height: 48,
child: FutureBuilder<ImageProvider?>(
future: _getPackageIcon(connection),
builder: (_, snapshot) {
if (!snapshot.hasData && snapshot.data == null) {
return Container();
} else {
return Image(
image: snapshot.data!,
gaplessPlayback: true,
width: 48,
height: 48,
);
}
},
),
)
: null,
title: Text(
_getRequestText(connection.metadata),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
Text(
_getSourceText(connection),
),
const SizedBox(
height: 12,
),
Wrap(
runSpacing: 8,
spacing: 8,
children: [
for (final chain in connection.chains)
CommonChip(
label: chain,
),
],
),
const SizedBox(
height: 12,
),
],
),
);
}
}

View File

@@ -111,9 +111,10 @@ class ThemeFragment extends StatelessWidget {
null, null,
defaultPrimaryColor, defaultPrimaryColor,
Colors.pinkAccent, Colors.pinkAccent,
Colors.lightBlue,
Colors.greenAccent, Colors.greenAccent,
Colors.yellowAccent, Colors.yellowAccent,
Colors.purple Colors.purple,
]; ];
return Column( return Column(
children: [ children: [

View File

@@ -166,6 +166,7 @@ class _ToolboxFragmentState extends State<ToolsFragment> {
delegate: OpenDelegate( delegate: OpenDelegate(
title: appLocalizations.override, title: appLocalizations.override,
widget: const ConfigFragment(), widget: const ConfigFragment(),
extendPageWidth: 360,
), ),
), ),
ListItem.open( ListItem.open(

View File

@@ -111,6 +111,7 @@
"noMoreInfoDesc": "No more info", "noMoreInfoDesc": "No more info",
"profileParseErrorDesc": "profile parse error", "profileParseErrorDesc": "profile parse error",
"proxyPort": "ProxyPort", "proxyPort": "ProxyPort",
"proxyPortDesc": "Set the clash listening port",
"port": "Port", "port": "Port",
"logLevel": "LogLevel", "logLevel": "LogLevel",
"show": "Show", "show": "Show",
@@ -160,5 +161,28 @@
"checking": "Checking...", "checking": "Checking...",
"country": "Country", "country": "Country",
"checkError": "Check error", "checkError": "Check error",
"ipCheckTimeout": "Ip check timeout" "ipCheckTimeout": "Ip check timeout",
"search": "Search",
"allowBypass": "Allow applications to bypass VPN",
"allowBypassDesc": "Some apps can bypass VPN when turned on",
"externalController": "ExternalController",
"externalControllerDesc": "Once enabled, the clash kernel can be controlled on port 9090",
"ipv6Desc": "When turned on it will be able to receive ipv6 traffic",
"app": "App",
"general": "General",
"systemProxyDesc": "Attach HTTP proxy to VpnService",
"unifiedDelay": "Unified delay",
"unifiedDelayDesc": "Remove extra delays such as handshaking",
"tcpConcurrent": "Tcp concurrent",
"tcpConcurrentDesc": "Enabling it will allow tcp concurrency",
"geodataLoader": "Geo Low Memory Mode",
"geodataLoaderDesc": "Enabling will use the Geo low memory loader",
"requests": "Requests",
"requestsDesc": "View recently requested data",
"nullRequestsDesc": "No proxy or no request",
"findProcessMode": "Find process",
"findProcessModeDesc": "There is a risk of flashback after opening",
"init": "Init",
"infiniteTime": "Long term effective",
"expirationTime": "Expiration time"
} }

View File

@@ -111,6 +111,7 @@
"noMoreInfoDesc": "暂无更多信息", "noMoreInfoDesc": "暂无更多信息",
"profileParseErrorDesc": "配置文件解析错误", "profileParseErrorDesc": "配置文件解析错误",
"proxyPort": "代理端口", "proxyPort": "代理端口",
"proxyPortDesc": "设置clash监听端口",
"port": "端口", "port": "端口",
"logLevel": "日志等级", "logLevel": "日志等级",
"show": "显示", "show": "显示",
@@ -160,5 +161,28 @@
"checking": "检测中...", "checking": "检测中...",
"country": "区域", "country": "区域",
"checkError": "检测失败", "checkError": "检测失败",
"ipCheckTimeout": "Ip检测超时" "ipCheckTimeout": "Ip检测超时",
"search": "搜索",
"allowBypass": "允许应用绕过vpn",
"allowBypassDesc": "开启后部分应用可绕过VPN",
"externalController": "外部控制器",
"externalControllerDesc": "开启后将可以通过9090端口控制clash内核",
"ipv6Desc": "开启后将可以接收ipv6流量",
"app": "应用",
"general": "基础",
"systemProxyDesc": "为VpnService附加HTTP代理",
"unifiedDelay": "统一延迟",
"unifiedDelayDesc": "去除握手等额外延迟",
"tcpConcurrent": "TCP并发",
"tcpConcurrentDesc": "开启后允许tcp并发",
"geodataLoader": "Geo低内存模式",
"geodataLoaderDesc": "开启将使用Geo低内存加载器",
"requests": "请求",
"requestsDesc": "查看最近请求数据",
"nullRequestsDesc": "未开启代理或者没有请求",
"findProcessMode": "查找进程",
"findProcessModeDesc": "开启后存在闪退风险",
"init": "初始化",
"infiniteTime": "长期有效",
"expirationTime": "到期时间"
} }

View File

@@ -40,9 +40,14 @@ class MessageLookup extends MessageLookupByLibrary {
"addressTip": MessageLookupByLibrary.simpleMessage( "addressTip": MessageLookupByLibrary.simpleMessage(
"Please enter a valid WebDAV address"), "Please enter a valid WebDAV address"),
"ago": MessageLookupByLibrary.simpleMessage(" Ago"), "ago": MessageLookupByLibrary.simpleMessage(" Ago"),
"allowBypass": MessageLookupByLibrary.simpleMessage(
"Allow applications to bypass VPN"),
"allowBypassDesc": MessageLookupByLibrary.simpleMessage(
"Some apps can bypass VPN when turned on"),
"allowLan": MessageLookupByLibrary.simpleMessage("AllowLan"), "allowLan": MessageLookupByLibrary.simpleMessage("AllowLan"),
"allowLanDesc": MessageLookupByLibrary.simpleMessage( "allowLanDesc": MessageLookupByLibrary.simpleMessage(
"Allow access proxy through the LAN"), "Allow access proxy through the LAN"),
"app": MessageLookupByLibrary.simpleMessage("App"),
"appAccessControl": "appAccessControl":
MessageLookupByLibrary.simpleMessage("App access control"), MessageLookupByLibrary.simpleMessage("App access control"),
"application": MessageLookupByLibrary.simpleMessage("Application"), "application": MessageLookupByLibrary.simpleMessage("Application"),
@@ -112,6 +117,12 @@ class MessageLookup extends MessageLookupByLibrary {
"edit": MessageLookupByLibrary.simpleMessage("Edit"), "edit": MessageLookupByLibrary.simpleMessage("Edit"),
"en": MessageLookupByLibrary.simpleMessage("English"), "en": MessageLookupByLibrary.simpleMessage("English"),
"exit": MessageLookupByLibrary.simpleMessage("Exit"), "exit": MessageLookupByLibrary.simpleMessage("Exit"),
"expirationTime":
MessageLookupByLibrary.simpleMessage("Expiration time"),
"externalController":
MessageLookupByLibrary.simpleMessage("ExternalController"),
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
"Once enabled, the clash kernel can be controlled on port 9090"),
"externalResources": "externalResources":
MessageLookupByLibrary.simpleMessage("External resources"), MessageLookupByLibrary.simpleMessage("External resources"),
"file": MessageLookupByLibrary.simpleMessage("File"), "file": MessageLookupByLibrary.simpleMessage("File"),
@@ -119,14 +130,27 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Directly upload profile"), MessageLookupByLibrary.simpleMessage("Directly upload profile"),
"filterSystemApp": "filterSystemApp":
MessageLookupByLibrary.simpleMessage("Filter system app"), MessageLookupByLibrary.simpleMessage("Filter system app"),
"findProcessMode": MessageLookupByLibrary.simpleMessage("Find process"),
"findProcessModeDesc": MessageLookupByLibrary.simpleMessage(
"There is a risk of flashback after opening"),
"general": MessageLookupByLibrary.simpleMessage("General"),
"geoData": MessageLookupByLibrary.simpleMessage("GeoData"), "geoData": MessageLookupByLibrary.simpleMessage("GeoData"),
"geodataLoader":
MessageLookupByLibrary.simpleMessage("Geo Low Memory Mode"),
"geodataLoaderDesc": MessageLookupByLibrary.simpleMessage(
"Enabling will use the Geo low memory loader"),
"global": MessageLookupByLibrary.simpleMessage("Global"), "global": MessageLookupByLibrary.simpleMessage("Global"),
"goDownload": MessageLookupByLibrary.simpleMessage("Go to download"), "goDownload": MessageLookupByLibrary.simpleMessage("Go to download"),
"hours": MessageLookupByLibrary.simpleMessage("Hours"), "hours": MessageLookupByLibrary.simpleMessage("Hours"),
"importFromURL": "importFromURL":
MessageLookupByLibrary.simpleMessage("Import from URL"), MessageLookupByLibrary.simpleMessage("Import from URL"),
"infiniteTime":
MessageLookupByLibrary.simpleMessage("Long term effective"),
"init": MessageLookupByLibrary.simpleMessage("Init"),
"ipCheckTimeout": "ipCheckTimeout":
MessageLookupByLibrary.simpleMessage("Ip check timeout"), MessageLookupByLibrary.simpleMessage("Ip check timeout"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
"When turned on it will be able to receive ipv6 traffic"),
"just": MessageLookupByLibrary.simpleMessage("Just"), "just": MessageLookupByLibrary.simpleMessage("Just"),
"language": MessageLookupByLibrary.simpleMessage("Language"), "language": MessageLookupByLibrary.simpleMessage("Language"),
"light": MessageLookupByLibrary.simpleMessage("Light"), "light": MessageLookupByLibrary.simpleMessage("Light"),
@@ -160,6 +184,8 @@ class MessageLookup extends MessageLookupByLibrary {
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("No logs"), "nullLogsDesc": MessageLookupByLibrary.simpleMessage("No logs"),
"nullProfileDesc": MessageLookupByLibrary.simpleMessage( "nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"No profile, Please add a profile"), "No profile, Please add a profile"),
"nullRequestsDesc":
MessageLookupByLibrary.simpleMessage("No proxy or no request"),
"other": MessageLookupByLibrary.simpleMessage("Other"), "other": MessageLookupByLibrary.simpleMessage("Other"),
"outboundMode": MessageLookupByLibrary.simpleMessage("Outbound mode"), "outboundMode": MessageLookupByLibrary.simpleMessage("Outbound mode"),
"override": MessageLookupByLibrary.simpleMessage("Override"), "override": MessageLookupByLibrary.simpleMessage("Override"),
@@ -195,6 +221,8 @@ class MessageLookup extends MessageLookupByLibrary {
"project": MessageLookupByLibrary.simpleMessage("Project"), "project": MessageLookupByLibrary.simpleMessage("Project"),
"proxies": MessageLookupByLibrary.simpleMessage("Proxies"), "proxies": MessageLookupByLibrary.simpleMessage("Proxies"),
"proxyPort": MessageLookupByLibrary.simpleMessage("ProxyPort"), "proxyPort": MessageLookupByLibrary.simpleMessage("ProxyPort"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage(
"Set the clash listening port"),
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"), "qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage( "qrcodeDesc": MessageLookupByLibrary.simpleMessage(
"Scan QR code to obtain profile"), "Scan QR code to obtain profile"),
@@ -207,11 +235,15 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Only recovery profiles"), MessageLookupByLibrary.simpleMessage("Only recovery profiles"),
"recoverySuccess": "recoverySuccess":
MessageLookupByLibrary.simpleMessage("Recovery success"), MessageLookupByLibrary.simpleMessage("Recovery success"),
"requests": MessageLookupByLibrary.simpleMessage("Requests"),
"requestsDesc": MessageLookupByLibrary.simpleMessage(
"View recently requested data"),
"resources": MessageLookupByLibrary.simpleMessage("Resources"), "resources": MessageLookupByLibrary.simpleMessage("Resources"),
"resourcesDesc": MessageLookupByLibrary.simpleMessage( "resourcesDesc": MessageLookupByLibrary.simpleMessage(
"External resource related info"), "External resource related info"),
"rule": MessageLookupByLibrary.simpleMessage("Rule"), "rule": MessageLookupByLibrary.simpleMessage("Rule"),
"save": MessageLookupByLibrary.simpleMessage("Save"), "save": MessageLookupByLibrary.simpleMessage("Save"),
"search": MessageLookupByLibrary.simpleMessage("Search"),
"selectAll": MessageLookupByLibrary.simpleMessage("Select all"), "selectAll": MessageLookupByLibrary.simpleMessage("Select all"),
"selected": MessageLookupByLibrary.simpleMessage("Selected"), "selected": MessageLookupByLibrary.simpleMessage("Selected"),
"settings": MessageLookupByLibrary.simpleMessage("Settings"), "settings": MessageLookupByLibrary.simpleMessage("Settings"),
@@ -223,9 +255,14 @@ class MessageLookup extends MessageLookupByLibrary {
"stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."), "stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."),
"submit": MessageLookupByLibrary.simpleMessage("Submit"), "submit": MessageLookupByLibrary.simpleMessage("Submit"),
"systemProxy": MessageLookupByLibrary.simpleMessage("SystemProxy"), "systemProxy": MessageLookupByLibrary.simpleMessage("SystemProxy"),
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(
"Attach HTTP proxy to VpnService"),
"tabAnimation": MessageLookupByLibrary.simpleMessage("Tab animation"), "tabAnimation": MessageLookupByLibrary.simpleMessage("Tab animation"),
"tabAnimationDesc": MessageLookupByLibrary.simpleMessage( "tabAnimationDesc": MessageLookupByLibrary.simpleMessage(
"When enabled, the home tab will add a toggle animation"), "When enabled, the home tab will add a toggle animation"),
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("Tcp concurrent"),
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage(
"Enabling it will allow tcp concurrency"),
"theme": MessageLookupByLibrary.simpleMessage("Theme"), "theme": MessageLookupByLibrary.simpleMessage("Theme"),
"themeColor": MessageLookupByLibrary.simpleMessage("Theme color"), "themeColor": MessageLookupByLibrary.simpleMessage("Theme color"),
"themeDesc": MessageLookupByLibrary.simpleMessage( "themeDesc": MessageLookupByLibrary.simpleMessage(
@@ -240,6 +277,9 @@ class MessageLookup extends MessageLookupByLibrary {
"unableToUpdateCurrentProfileDesc": "unableToUpdateCurrentProfileDesc":
MessageLookupByLibrary.simpleMessage( MessageLookupByLibrary.simpleMessage(
"unable to update current profile"), "unable to update current profile"),
"unifiedDelay": MessageLookupByLibrary.simpleMessage("Unified delay"),
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage(
"Remove extra delays such as handshaking"),
"unknown": MessageLookupByLibrary.simpleMessage("Unknown"), "unknown": MessageLookupByLibrary.simpleMessage("Unknown"),
"update": MessageLookupByLibrary.simpleMessage("Update"), "update": MessageLookupByLibrary.simpleMessage("Update"),
"upload": MessageLookupByLibrary.simpleMessage("Upload"), "upload": MessageLookupByLibrary.simpleMessage("Upload"),

View File

@@ -36,8 +36,12 @@ class MessageLookup extends MessageLookupByLibrary {
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"), "addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"), "addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
"ago": MessageLookupByLibrary.simpleMessage(""), "ago": MessageLookupByLibrary.simpleMessage(""),
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过vpn"),
"allowBypassDesc":
MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"),
"allowLan": MessageLookupByLibrary.simpleMessage("局域网代理"), "allowLan": MessageLookupByLibrary.simpleMessage("局域网代理"),
"allowLanDesc": MessageLookupByLibrary.simpleMessage("允许通过局域网访问代理"), "allowLanDesc": MessageLookupByLibrary.simpleMessage("允许通过局域网访问代理"),
"app": MessageLookupByLibrary.simpleMessage("应用"),
"appAccessControl": MessageLookupByLibrary.simpleMessage("应用访问控制"), "appAccessControl": MessageLookupByLibrary.simpleMessage("应用访问控制"),
"application": MessageLookupByLibrary.simpleMessage("应用程序"), "application": MessageLookupByLibrary.simpleMessage("应用程序"),
"applicationDesc": MessageLookupByLibrary.simpleMessage("修改应用程序相关设置"), "applicationDesc": MessageLookupByLibrary.simpleMessage("修改应用程序相关设置"),
@@ -93,16 +97,30 @@ class MessageLookup extends MessageLookupByLibrary {
"edit": MessageLookupByLibrary.simpleMessage("编辑"), "edit": MessageLookupByLibrary.simpleMessage("编辑"),
"en": MessageLookupByLibrary.simpleMessage("英语"), "en": MessageLookupByLibrary.simpleMessage("英语"),
"exit": MessageLookupByLibrary.simpleMessage("退出"), "exit": MessageLookupByLibrary.simpleMessage("退出"),
"expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"),
"externalController": MessageLookupByLibrary.simpleMessage("外部控制器"),
"externalControllerDesc":
MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制clash内核"),
"externalResources": MessageLookupByLibrary.simpleMessage("外部资源"), "externalResources": MessageLookupByLibrary.simpleMessage("外部资源"),
"file": MessageLookupByLibrary.simpleMessage("文件"), "file": MessageLookupByLibrary.simpleMessage("文件"),
"fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"), "fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
"filterSystemApp": MessageLookupByLibrary.simpleMessage("过滤系统应用"), "filterSystemApp": MessageLookupByLibrary.simpleMessage("过滤系统应用"),
"findProcessMode": MessageLookupByLibrary.simpleMessage("查找进程"),
"findProcessModeDesc":
MessageLookupByLibrary.simpleMessage("开启后存在闪退风险"),
"general": MessageLookupByLibrary.simpleMessage("基础"),
"geoData": MessageLookupByLibrary.simpleMessage("地理数据"), "geoData": MessageLookupByLibrary.simpleMessage("地理数据"),
"geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低内存模式"),
"geodataLoaderDesc":
MessageLookupByLibrary.simpleMessage("开启将使用Geo低内存加载器"),
"global": MessageLookupByLibrary.simpleMessage("全局"), "global": MessageLookupByLibrary.simpleMessage("全局"),
"goDownload": MessageLookupByLibrary.simpleMessage("前往下载"), "goDownload": MessageLookupByLibrary.simpleMessage("前往下载"),
"hours": MessageLookupByLibrary.simpleMessage("小时"), "hours": MessageLookupByLibrary.simpleMessage("小时"),
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"), "importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
"init": MessageLookupByLibrary.simpleMessage("初始化"),
"ipCheckTimeout": MessageLookupByLibrary.simpleMessage("Ip检测超时"), "ipCheckTimeout": MessageLookupByLibrary.simpleMessage("Ip检测超时"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收ipv6流量"),
"just": MessageLookupByLibrary.simpleMessage("刚刚"), "just": MessageLookupByLibrary.simpleMessage("刚刚"),
"language": MessageLookupByLibrary.simpleMessage("语言"), "language": MessageLookupByLibrary.simpleMessage("语言"),
"light": MessageLookupByLibrary.simpleMessage("浅色"), "light": MessageLookupByLibrary.simpleMessage("浅色"),
@@ -131,6 +149,7 @@ class MessageLookup extends MessageLookupByLibrary {
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("暂无日志"), "nullLogsDesc": MessageLookupByLibrary.simpleMessage("暂无日志"),
"nullProfileDesc": "nullProfileDesc":
MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"), MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("未开启代理或者没有请求"),
"other": MessageLookupByLibrary.simpleMessage("其他"), "other": MessageLookupByLibrary.simpleMessage("其他"),
"outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"), "outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"),
"override": MessageLookupByLibrary.simpleMessage("覆写"), "override": MessageLookupByLibrary.simpleMessage("覆写"),
@@ -160,6 +179,7 @@ class MessageLookup extends MessageLookupByLibrary {
"project": MessageLookupByLibrary.simpleMessage("项目"), "project": MessageLookupByLibrary.simpleMessage("项目"),
"proxies": MessageLookupByLibrary.simpleMessage("代理"), "proxies": MessageLookupByLibrary.simpleMessage("代理"),
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"), "proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置clash监听端口"),
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"), "qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"), "qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
"recovery": MessageLookupByLibrary.simpleMessage("恢复"), "recovery": MessageLookupByLibrary.simpleMessage("恢复"),
@@ -167,10 +187,13 @@ class MessageLookup extends MessageLookupByLibrary {
"recoveryDesc": MessageLookupByLibrary.simpleMessage("从WebDAV恢复数据"), "recoveryDesc": MessageLookupByLibrary.simpleMessage("从WebDAV恢复数据"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"), "recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"), "recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
"requests": MessageLookupByLibrary.simpleMessage("请求"),
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求数据"),
"resources": MessageLookupByLibrary.simpleMessage("资源"), "resources": MessageLookupByLibrary.simpleMessage("资源"),
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"), "resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"),
"rule": MessageLookupByLibrary.simpleMessage("规则"), "rule": MessageLookupByLibrary.simpleMessage("规则"),
"save": MessageLookupByLibrary.simpleMessage("保存"), "save": MessageLookupByLibrary.simpleMessage("保存"),
"search": MessageLookupByLibrary.simpleMessage("搜索"),
"selectAll": MessageLookupByLibrary.simpleMessage("全选"), "selectAll": MessageLookupByLibrary.simpleMessage("全选"),
"selected": MessageLookupByLibrary.simpleMessage("已选择"), "selected": MessageLookupByLibrary.simpleMessage("已选择"),
"settings": MessageLookupByLibrary.simpleMessage("设置"), "settings": MessageLookupByLibrary.simpleMessage("设置"),
@@ -181,9 +204,13 @@ class MessageLookup extends MessageLookupByLibrary {
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."), "stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
"submit": MessageLookupByLibrary.simpleMessage("提交"), "submit": MessageLookupByLibrary.simpleMessage("提交"),
"systemProxy": MessageLookupByLibrary.simpleMessage("系统代理"), "systemProxy": MessageLookupByLibrary.simpleMessage("系统代理"),
"systemProxyDesc":
MessageLookupByLibrary.simpleMessage("为VpnService附加HTTP代理"),
"tabAnimation": MessageLookupByLibrary.simpleMessage("选项卡动画"), "tabAnimation": MessageLookupByLibrary.simpleMessage("选项卡动画"),
"tabAnimationDesc": "tabAnimationDesc":
MessageLookupByLibrary.simpleMessage("开启后,主页选项卡将添加切换动画"), MessageLookupByLibrary.simpleMessage("开启后,主页选项卡将添加切换动画"),
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"),
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许tcp并发"),
"theme": MessageLookupByLibrary.simpleMessage("主题"), "theme": MessageLookupByLibrary.simpleMessage("主题"),
"themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"), "themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"),
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"), "themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
@@ -195,6 +222,8 @@ class MessageLookup extends MessageLookupByLibrary {
"tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"), "tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"),
"unableToUpdateCurrentProfileDesc": "unableToUpdateCurrentProfileDesc":
MessageLookupByLibrary.simpleMessage("无法更新当前配置文件"), MessageLookupByLibrary.simpleMessage("无法更新当前配置文件"),
"unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"),
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"),
"unknown": MessageLookupByLibrary.simpleMessage("未知"), "unknown": MessageLookupByLibrary.simpleMessage("未知"),
"update": MessageLookupByLibrary.simpleMessage("更新"), "update": MessageLookupByLibrary.simpleMessage("更新"),
"upload": MessageLookupByLibrary.simpleMessage("上传"), "upload": MessageLookupByLibrary.simpleMessage("上传"),

View File

@@ -1170,6 +1170,16 @@ class AppLocalizations {
); );
} }
/// `Set the clash listening port`
String get proxyPortDesc {
return Intl.message(
'Set the clash listening port',
name: 'proxyPortDesc',
desc: '',
args: [],
);
}
/// `Port` /// `Port`
String get port { String get port {
return Intl.message( return Intl.message(
@@ -1669,6 +1679,236 @@ class AppLocalizations {
args: [], args: [],
); );
} }
/// `Search`
String get search {
return Intl.message(
'Search',
name: 'search',
desc: '',
args: [],
);
}
/// `Allow applications to bypass VPN`
String get allowBypass {
return Intl.message(
'Allow applications to bypass VPN',
name: 'allowBypass',
desc: '',
args: [],
);
}
/// `Some apps can bypass VPN when turned on`
String get allowBypassDesc {
return Intl.message(
'Some apps can bypass VPN when turned on',
name: 'allowBypassDesc',
desc: '',
args: [],
);
}
/// `ExternalController`
String get externalController {
return Intl.message(
'ExternalController',
name: 'externalController',
desc: '',
args: [],
);
}
/// `Once enabled, the clash kernel can be controlled on port 9090`
String get externalControllerDesc {
return Intl.message(
'Once enabled, the clash kernel can be controlled on port 9090',
name: 'externalControllerDesc',
desc: '',
args: [],
);
}
/// `When turned on it will be able to receive ipv6 traffic`
String get ipv6Desc {
return Intl.message(
'When turned on it will be able to receive ipv6 traffic',
name: 'ipv6Desc',
desc: '',
args: [],
);
}
/// `App`
String get app {
return Intl.message(
'App',
name: 'app',
desc: '',
args: [],
);
}
/// `General`
String get general {
return Intl.message(
'General',
name: 'general',
desc: '',
args: [],
);
}
/// `Attach HTTP proxy to VpnService`
String get systemProxyDesc {
return Intl.message(
'Attach HTTP proxy to VpnService',
name: 'systemProxyDesc',
desc: '',
args: [],
);
}
/// `Unified delay`
String get unifiedDelay {
return Intl.message(
'Unified delay',
name: 'unifiedDelay',
desc: '',
args: [],
);
}
/// `Remove extra delays such as handshaking`
String get unifiedDelayDesc {
return Intl.message(
'Remove extra delays such as handshaking',
name: 'unifiedDelayDesc',
desc: '',
args: [],
);
}
/// `Tcp concurrent`
String get tcpConcurrent {
return Intl.message(
'Tcp concurrent',
name: 'tcpConcurrent',
desc: '',
args: [],
);
}
/// `Enabling it will allow tcp concurrency`
String get tcpConcurrentDesc {
return Intl.message(
'Enabling it will allow tcp concurrency',
name: 'tcpConcurrentDesc',
desc: '',
args: [],
);
}
/// `Geo Low Memory Mode`
String get geodataLoader {
return Intl.message(
'Geo Low Memory Mode',
name: 'geodataLoader',
desc: '',
args: [],
);
}
/// `Enabling will use the Geo low memory loader`
String get geodataLoaderDesc {
return Intl.message(
'Enabling will use the Geo low memory loader',
name: 'geodataLoaderDesc',
desc: '',
args: [],
);
}
/// `Requests`
String get requests {
return Intl.message(
'Requests',
name: 'requests',
desc: '',
args: [],
);
}
/// `View recently requested data`
String get requestsDesc {
return Intl.message(
'View recently requested data',
name: 'requestsDesc',
desc: '',
args: [],
);
}
/// `No proxy or no request`
String get nullRequestsDesc {
return Intl.message(
'No proxy or no request',
name: 'nullRequestsDesc',
desc: '',
args: [],
);
}
/// `Find process`
String get findProcessMode {
return Intl.message(
'Find process',
name: 'findProcessMode',
desc: '',
args: [],
);
}
/// `There is a risk of flashback after opening`
String get findProcessModeDesc {
return Intl.message(
'There is a risk of flashback after opening',
name: 'findProcessModeDesc',
desc: '',
args: [],
);
}
/// `Init`
String get init {
return Intl.message(
'Init',
name: 'init',
desc: '',
args: [],
);
}
/// `Long term effective`
String get infiniteTime {
return Intl.message(
'Long term effective',
name: 'infiniteTime',
desc: '',
args: [],
);
}
/// `Expiration time`
String get expirationTime {
return Intl.message(
'Expiration time',
name: 'expirationTime',
desc: '',
args: [],
);
}
} }
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> { class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -22,12 +22,11 @@ Future<void> main() async {
isCompatible: config.isCompatible, isCompatible: config.isCompatible,
selectedMap: config.currentSelectedMap, selectedMap: config.currentSelectedMap,
); );
await globalState.init( appState.navigationItems = navigation.getItems(
appState: appState, openLogs: config.openLogs,
config: config, hasProxies: false,
clashConfig: clashConfig,
); );
globalState.updateNavigationItems( await globalState.init(
appState: appState, appState: appState,
config: config, config: config,
clashConfig: clashConfig, clashConfig: clashConfig,
@@ -55,12 +54,19 @@ Future<void> vpnService() async {
int.parse(fd), int.parse(fd),
); );
})); }));
await globalState.init( await globalState.init(
appState: appState, appState: appState,
config: config, config: config,
clashConfig: clashConfig, clashConfig: clashConfig,
); );
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
final appLocalizations = await AppLocalizations.load( final appLocalizations = await AppLocalizations.load(
other.getLocaleForString(config.locale) ?? other.getLocaleForString(config.locale) ??
WidgetsBinding.instance.platformDispatcher.locale, WidgetsBinding.instance.platformDispatcher.locale,
@@ -69,6 +75,7 @@ Future<void> vpnService() async {
handleStart() async { handleStart() async {
await app?.tip(appLocalizations.startVpn); await app?.tip(appLocalizations.startVpn);
await globalState.startSystemProxy( await globalState.startSystemProxy(
appState: appState,
config: config, config: config,
clashConfig: clashConfig, clashConfig: clashConfig,
); );
@@ -80,19 +87,18 @@ Future<void> vpnService() async {
]; ];
} }
if (appState.isInit) { handleStart();
handleStart();
tile?.addListener( tile?.addListener(
TileListenerWithVpn( TileListenerWithVpn(
onStop: () async { onStop: () async {
await app?.tip(appLocalizations.stopVpn); await app?.tip(appLocalizations.stopVpn);
await globalState.stopSystemProxy(); await globalState.stopSystemProxy();
clashCore.shutdown(); clashCore.shutdown();
exit(0); exit(0);
}, },
), ),
); );
}
} }
class ClashMessageListenerWithVpn with ClashMessageListener { class ClashMessageListenerWithVpn with ClashMessageListener {

View File

@@ -1,7 +1,10 @@
import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'connection.dart';
import 'ffi.dart'; import 'ffi.dart';
import 'log.dart'; import 'log.dart';
import 'navigation.dart'; import 'navigation.dart';
@@ -19,6 +22,7 @@ class AppState with ChangeNotifier {
bool _isInit; bool _isInit;
VersionInfo? _versionInfo; VersionInfo? _versionInfo;
List<Traffic> _traffics; List<Traffic> _traffics;
Traffic _totalTraffic;
List<Log> _logs; List<Log> _logs;
String _currentLabel; String _currentLabel;
SystemColorSchemes _systemColorSchemes; SystemColorSchemes _systemColorSchemes;
@@ -29,6 +33,7 @@ class AppState with ChangeNotifier {
bool _isCompatible; bool _isCompatible;
List<Group> _groups; List<Group> _groups;
double _viewWidth; double _viewWidth;
List<Connection> _requests;
AppState({ AppState({
required Mode mode, required Mode mode,
@@ -42,7 +47,9 @@ class AppState with ChangeNotifier {
_viewWidth = 0, _viewWidth = 0,
_selectedMap = selectedMap, _selectedMap = selectedMap,
_sortNum = 0, _sortNum = 0,
_requests = [],
_mode = mode, _mode = mode,
_totalTraffic = Traffic(),
_delayMap = {}, _delayMap = {},
_groups = [], _groups = [],
_isCompatible = isCompatible, _isCompatible = isCompatible,
@@ -107,7 +114,7 @@ class AppState with ChangeNotifier {
} else { } else {
final index = groups.indexWhere((element) => element.name == proxyName); final index = groups.indexWhere((element) => element.name == proxyName);
if (index == -1) return type; if (index == -1) return type;
return "$type(${groups[index].now})"; return "$type(${groups[index].now ?? '*'})";
} }
} }
@@ -152,8 +159,39 @@ class AppState with ChangeNotifier {
} }
} }
addTraffic(Traffic value) { addTraffic(Traffic traffic) {
_traffics = List.from(_traffics)..add(value); _traffics = List.from(_traffics)..add(traffic);
const maxLength = 60;
if (_traffics.length > maxLength) {
_traffics = _traffics.sublist(_traffics.length - maxLength);
}
notifyListeners();
}
Traffic get totalTraffic => _totalTraffic;
set totalTraffic(Traffic value) {
if (_totalTraffic != value) {
_totalTraffic = value;
notifyListeners();
}
}
List<Connection> get requests => _requests;
set requests(List<Connection> value) {
if (_requests != value) {
_requests = value;
notifyListeners();
}
}
addRequest(Connection value) {
_requests.add(value);
final maxLength = Platform.isAndroid ? 1000 : 60;
if (_requests.length > maxLength) {
_requests = _requests.sublist(_requests.length - maxLength);
}
notifyListeners(); notifyListeners();
} }
@@ -168,8 +206,9 @@ class AppState with ChangeNotifier {
addLog(Log log) { addLog(Log log) {
_logs.add(log); _logs.add(log);
if (_logs.length > 60) { final maxLength = Platform.isAndroid ? 1000 : 60;
_logs = _logs.sublist(_logs.length - 60); if (_logs.length > maxLength) {
_logs = _logs.sublist(_logs.length - maxLength);
} }
notifyListeners(); notifyListeners();
} }

View File

@@ -16,7 +16,7 @@ class Tun with _$Tun {
const factory Tun({ const factory Tun({
@Default(false) bool enable, @Default(false) bool enable,
@Default(appName) String device, @Default(appName) String device,
@Default(TunStack.mixed) TunStack stack, @Default(TunStack.gvisor) TunStack stack,
@JsonKey(name: "dns-hijack") @Default(["any:53"]) List<String> dnsHijack, @JsonKey(name: "dns-hijack") @Default(["any:53"]) List<String> dnsHijack,
}) = _Tun; }) = _Tun;
@@ -108,29 +108,34 @@ class Dns {
class ClashConfig extends ChangeNotifier { class ClashConfig extends ChangeNotifier {
int _mixedPort; int _mixedPort;
bool _allowLan; bool _allowLan;
Mode _mode; bool _ipv6;
String _geodataLoader;
LogLevel _logLevel; LogLevel _logLevel;
String _externalController;
Mode _mode;
FindProcessMode _findProcessMode;
bool _unifiedDelay;
bool _tcpConcurrent;
Tun _tun; Tun _tun;
Dns _dns; Dns _dns;
List<String> _rules; List<String> _rules;
ClashConfig({ ClashConfig()
int? mixedPort, : _mixedPort = 7890,
Mode? mode, _mode = Mode.rule,
bool? allowLan, _ipv6 = false,
LogLevel? logLevel, _findProcessMode = FindProcessMode.off,
Tun? tun, _allowLan = false,
Dns? dns, _tcpConcurrent = false,
List<String>? rules, _logLevel = LogLevel.info,
}) : _mixedPort = mixedPort ?? 7890, _tun = const Tun(),
_mode = mode ?? Mode.rule, _unifiedDelay = false,
_allowLan = allowLan ?? false, _geodataLoader = geodataLoaderMemconservative,
_logLevel = logLevel ?? LogLevel.info, _externalController = '',
_tun = tun ?? const Tun(), _dns = Dns(),
_dns = dns ?? Dns(), _rules = [];
_rules = rules ?? [];
@JsonKey(name: "mixed-port") @JsonKey(name: "mixed-port", defaultValue: 7890)
int get mixedPort => _mixedPort; int get mixedPort => _mixedPort;
set mixedPort(int value) { set mixedPort(int value) {
@@ -140,6 +145,7 @@ class ClashConfig extends ChangeNotifier {
} }
} }
@JsonKey(defaultValue: Mode.rule)
Mode get mode => _mode; Mode get mode => _mode;
set mode(Mode value) { set mode(Mode value) {
@@ -149,6 +155,16 @@ class ClashConfig extends ChangeNotifier {
} }
} }
@JsonKey(name: "find-process-mode", defaultValue: FindProcessMode.off)
FindProcessMode get findProcessMode => _findProcessMode;
set findProcessMode(FindProcessMode value) {
if (_findProcessMode != value) {
_findProcessMode = value;
notifyListeners();
}
}
@JsonKey(name: "allow-lan") @JsonKey(name: "allow-lan")
bool get allowLan => _allowLan; bool get allowLan => _allowLan;
@@ -159,7 +175,7 @@ class ClashConfig extends ChangeNotifier {
} }
} }
@JsonKey(name: "log-level") @JsonKey(name: "log-level", defaultValue: LogLevel.info)
LogLevel get logLevel => _logLevel; LogLevel get logLevel => _logLevel;
set logLevel(LogLevel value) { set logLevel(LogLevel value) {
@@ -169,6 +185,56 @@ class ClashConfig extends ChangeNotifier {
} }
} }
@JsonKey(name: "external-controller", defaultValue: '')
String get externalController => _externalController;
set externalController(String value) {
if (_externalController != value) {
_externalController = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get ipv6 => _ipv6;
set ipv6(bool value) {
if (_ipv6 != value) {
_ipv6 = value;
notifyListeners();
}
}
@JsonKey(name: "geodata-loader", defaultValue: geodataLoaderMemconservative)
String get geodataLoader => _geodataLoader;
set geodataLoader(String value) {
if (_geodataLoader != value) {
_geodataLoader = value;
notifyListeners();
}
}
@JsonKey(name: "unified-delay", defaultValue: false)
bool get unifiedDelay => _unifiedDelay;
set unifiedDelay(bool value) {
if (_unifiedDelay != value) {
_unifiedDelay = value;
notifyListeners();
}
}
@JsonKey(name: "tcp-concurrent", defaultValue: false)
bool get tcpConcurrent => _tcpConcurrent;
set tcpConcurrent(bool value) {
if (_tcpConcurrent != value) {
_tcpConcurrent = value;
notifyListeners();
}
}
Tun get tun => _tun; Tun get tun => _tun;
set tun(Tun value) { set tun(Tun value) {
@@ -217,17 +283,6 @@ class ClashConfig extends ChangeNotifier {
return _$ClashConfigFromJson(json); return _$ClashConfigFromJson(json);
} }
ClashConfig copyWith({Tun? tun}) {
return ClashConfig(
mixedPort: mixedPort,
mode: mode,
logLevel: logLevel,
tun: tun ?? this.tun,
dns: dns,
allowLan: allowLan,
);
}
@override @override
String toString() { String toString() {
return 'ClashConfig{_mixedPort: $_mixedPort, _allowLan: $_allowLan, _mode: $_mode, _logLevel: $_logLevel, _tun: $_tun, _dns: $_dns, _rules: $_rules}'; return 'ClashConfig{_mixedPort: $_mixedPort, _allowLan: $_allowLan, _mode: $_mode, _logLevel: $_logLevel, _tun: $_tun, _dns: $_dns, _rules: $_rules}';

View File

@@ -8,6 +8,7 @@ import '../common/common.dart';
import 'models.dart'; import 'models.dart';
part 'generated/config.g.dart'; part 'generated/config.g.dart';
part 'generated/config.freezed.dart'; part 'generated/config.freezed.dart';
@freezed @freezed
@@ -23,6 +24,17 @@ class AccessControl with _$AccessControl {
_$AccessControlFromJson(json); _$AccessControlFromJson(json);
} }
@freezed
class Props with _$Props {
const factory Props({
AccessControl? accessControl,
bool? allowBypass,
bool? systemProxy,
}) = _Props;
factory Props.fromJson(Map<String, Object?> json) => _$PropsFromJson(json);
}
@JsonSerializable() @JsonSerializable()
class Config extends ChangeNotifier { class Config extends ChangeNotifier {
List<Profile> _profiles; List<Profile> _profiles;
@@ -41,6 +53,8 @@ class Config extends ChangeNotifier {
AccessControl _accessControl; AccessControl _accessControl;
bool _isAnimateToPage; bool _isAnimateToPage;
bool _autoCheckUpdate; bool _autoCheckUpdate;
bool _allowBypass;
bool _systemProxy;
DAV? _dav; DAV? _dav;
Config() Config()
@@ -56,8 +70,10 @@ class Config extends ChangeNotifier {
_isMinimizeOnExit = true, _isMinimizeOnExit = true,
_isAccessControl = false, _isAccessControl = false,
_autoCheckUpdate = true, _autoCheckUpdate = true,
_systemProxy = true,
_accessControl = const AccessControl(), _accessControl = const AccessControl(),
_isAnimateToPage = true; _isAnimateToPage = true,
_allowBypass = true;
deleteProfileById(String id) { deleteProfileById(String id) {
_profiles = profiles.where((element) => element.id != id).toList(); _profiles = profiles.where((element) => element.id != id).toList();
@@ -134,6 +150,15 @@ class Config extends ChangeNotifier {
} }
} }
String? get currentGroupName => currentProfile?.currentGroupName;
updateCurrentGroupName(String groupName) {
if (currentProfile?.currentGroupName != groupName) {
currentProfile?.currentGroupName = groupName;
notifyListeners();
}
}
SelectedMap get currentSelectedMap { SelectedMap get currentSelectedMap {
return currentProfile?.selectedMap ?? {}; return currentProfile?.selectedMap ?? {};
} }
@@ -305,8 +330,34 @@ class Config extends ChangeNotifier {
} }
} }
update( @JsonKey(defaultValue: true)
[Config? config, RecoveryOption recoveryOptions = RecoveryOption.all]) { bool get allowBypass {
return _allowBypass;
}
set allowBypass(bool value) {
if (_allowBypass != value) {
_allowBypass = value;
notifyListeners();
}
}
@JsonKey(defaultValue: true)
bool get systemProxy {
return _systemProxy;
}
set systemProxy(bool value) {
if (_systemProxy != value) {
_systemProxy = value;
notifyListeners();
}
}
update([
Config? config,
RecoveryOption recoveryOptions = RecoveryOption.all,
]) {
if (config != null) { if (config != null) {
_profiles = config._profiles; _profiles = config._profiles;
for (final profile in config._profiles) { for (final profile in config._profiles) {
@@ -325,6 +376,7 @@ class Config extends ChangeNotifier {
_openLog = config._openLog; _openLog = config._openLog;
_themeMode = config._themeMode; _themeMode = config._themeMode;
_locale = config._locale; _locale = config._locale;
_allowBypass = config._allowBypass;
_primaryColor = config._primaryColor; _primaryColor = config._primaryColor;
_proxiesSortType = config._proxiesSortType; _proxiesSortType = config._proxiesSortType;
_isMinimizeOnExit = config._isMinimizeOnExit; _isMinimizeOnExit = config._isMinimizeOnExit;

View File

@@ -14,6 +14,7 @@ class Metadata with _$Metadata {
required String destinationIP, required String destinationIP,
required String destinationPort, required String destinationPort,
required String host, required String host,
required String process,
required String remoteDestination, required String remoteDestination,
}) = _Metadata; }) = _Metadata;

View File

@@ -2,6 +2,7 @@
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/clash_config.dart'; import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/models/connection.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
part 'generated/ffi.g.dart'; part 'generated/ffi.g.dart';
@@ -66,16 +67,25 @@ class Now with _$Now {
@freezed @freezed
class Process with _$Process { class Process with _$Process {
const factory Process({ const factory Process({
required int uid, required int id,
required String network, required Metadata metadata,
required String source,
required String target,
}) = _Process; }) = _Process;
factory Process.fromJson(Map<String, Object?> json) => factory Process.fromJson(Map<String, Object?> json) =>
_$ProcessFromJson(json); _$ProcessFromJson(json);
} }
@freezed
class ProcessMapItem with _$ProcessMapItem {
const factory ProcessMapItem({
required int id,
required String value,
}) = _ProcessMapItem;
factory ProcessMapItem.fromJson(Map<String, Object?> json) =>
_$ProcessMapItemFromJson(json);
}
@freezed @freezed
class ExternalProvider with _$ExternalProvider { class ExternalProvider with _$ExternalProvider {
const factory ExternalProvider({ const factory ExternalProvider({

View File

@@ -135,7 +135,7 @@ class _$TunImpl implements _Tun {
const _$TunImpl( const _$TunImpl(
{this.enable = false, {this.enable = false,
this.device = appName, this.device = appName,
this.stack = TunStack.mixed, this.stack = TunStack.gvisor,
@JsonKey(name: "dns-hijack") @JsonKey(name: "dns-hijack")
final List<String> dnsHijack = const ["any:53"]}) final List<String> dnsHijack = const ["any:53"]})
: _dnsHijack = dnsHijack; : _dnsHijack = dnsHijack;

View File

@@ -35,27 +35,36 @@ Map<String, dynamic> _$DnsToJson(Dns instance) => <String, dynamic>{
'fake-ip-filter': instance.fakeIpFilter, 'fake-ip-filter': instance.fakeIpFilter,
}; };
ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig( ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig()
mixedPort: (json['mixed-port'] as num?)?.toInt(), ..mixedPort = (json['mixed-port'] as num?)?.toInt() ?? 7890
mode: $enumDecodeNullable(_$ModeEnumMap, json['mode']), ..mode = $enumDecodeNullable(_$ModeEnumMap, json['mode']) ?? Mode.rule
allowLan: json['allow-lan'] as bool?, ..findProcessMode = $enumDecodeNullable(
logLevel: $enumDecodeNullable(_$LogLevelEnumMap, json['log-level']), _$FindProcessModeEnumMap, json['find-process-mode']) ??
tun: json['tun'] == null FindProcessMode.off
? null ..allowLan = json['allow-lan'] as bool
: Tun.fromJson(json['tun'] as Map<String, dynamic>), ..logLevel =
dns: json['dns'] == null $enumDecodeNullable(_$LogLevelEnumMap, json['log-level']) ?? LogLevel.info
? null ..externalController = json['external-controller'] as String? ?? ''
: Dns.fromJson(json['dns'] as Map<String, dynamic>), ..ipv6 = json['ipv6'] as bool? ?? false
rules: ..geodataLoader = json['geodata-loader'] as String? ?? 'memconservative'
(json['rules'] as List<dynamic>?)?.map((e) => e as String).toList(), ..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>)
..rules = (json['rules'] as List<dynamic>).map((e) => e as String).toList();
Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) => Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) =>
<String, dynamic>{ <String, dynamic>{
'mixed-port': instance.mixedPort, 'mixed-port': instance.mixedPort,
'mode': _$ModeEnumMap[instance.mode]!, 'mode': _$ModeEnumMap[instance.mode]!,
'find-process-mode': _$FindProcessModeEnumMap[instance.findProcessMode]!,
'allow-lan': instance.allowLan, 'allow-lan': instance.allowLan,
'log-level': _$LogLevelEnumMap[instance.logLevel]!, 'log-level': _$LogLevelEnumMap[instance.logLevel]!,
'external-controller': instance.externalController,
'ipv6': instance.ipv6,
'geodata-loader': instance.geodataLoader,
'unified-delay': instance.unifiedDelay,
'tcp-concurrent': instance.tcpConcurrent,
'tun': instance.tun, 'tun': instance.tun,
'dns': instance.dns, 'dns': instance.dns,
'rules': instance.rules, 'rules': instance.rules,
@@ -67,6 +76,11 @@ const _$ModeEnumMap = {
Mode.direct: 'direct', Mode.direct: 'direct',
}; };
const _$FindProcessModeEnumMap = {
FindProcessMode.always: 'always',
FindProcessMode.off: 'off',
};
const _$LogLevelEnumMap = { const _$LogLevelEnumMap = {
LogLevel.debug: 'debug', LogLevel.debug: 'debug',
LogLevel.info: 'info', LogLevel.info: 'info',
@@ -79,7 +93,7 @@ _$TunImpl _$$TunImplFromJson(Map<String, dynamic> json) => _$TunImpl(
enable: json['enable'] as bool? ?? false, enable: json['enable'] as bool? ?? false,
device: json['device'] as String? ?? appName, device: json['device'] as String? ?? appName,
stack: $enumDecodeNullable(_$TunStackEnumMap, json['stack']) ?? stack: $enumDecodeNullable(_$TunStackEnumMap, json['stack']) ??
TunStack.mixed, TunStack.gvisor,
dnsHijack: (json['dns-hijack'] as List<dynamic>?) dnsHijack: (json['dns-hijack'] as List<dynamic>?)
?.map((e) => e as String) ?.map((e) => e as String)
.toList() ?? .toList() ??

View File

@@ -239,3 +239,193 @@ abstract class _AccessControl implements AccessControl {
_$$AccessControlImplCopyWith<_$AccessControlImpl> get copyWith => _$$AccessControlImplCopyWith<_$AccessControlImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
Props _$PropsFromJson(Map<String, dynamic> json) {
return _Props.fromJson(json);
}
/// @nodoc
mixin _$Props {
AccessControl? get accessControl => throw _privateConstructorUsedError;
bool? get allowBypass => throw _privateConstructorUsedError;
bool? get systemProxy => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$PropsCopyWith<Props> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PropsCopyWith<$Res> {
factory $PropsCopyWith(Props value, $Res Function(Props) then) =
_$PropsCopyWithImpl<$Res, Props>;
@useResult
$Res call(
{AccessControl? accessControl, bool? allowBypass, bool? systemProxy});
$AccessControlCopyWith<$Res>? get accessControl;
}
/// @nodoc
class _$PropsCopyWithImpl<$Res, $Val extends Props>
implements $PropsCopyWith<$Res> {
_$PropsCopyWithImpl(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? allowBypass = freezed,
Object? systemProxy = freezed,
}) {
return _then(_value.copyWith(
accessControl: freezed == accessControl
? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?,
allowBypass: freezed == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
as bool?,
systemProxy: freezed == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool?,
) 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);
});
}
}
/// @nodoc
abstract class _$$PropsImplCopyWith<$Res> implements $PropsCopyWith<$Res> {
factory _$$PropsImplCopyWith(
_$PropsImpl value, $Res Function(_$PropsImpl) then) =
__$$PropsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{AccessControl? accessControl, bool? allowBypass, bool? systemProxy});
@override
$AccessControlCopyWith<$Res>? get accessControl;
}
/// @nodoc
class __$$PropsImplCopyWithImpl<$Res>
extends _$PropsCopyWithImpl<$Res, _$PropsImpl>
implements _$$PropsImplCopyWith<$Res> {
__$$PropsImplCopyWithImpl(
_$PropsImpl _value, $Res Function(_$PropsImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accessControl = freezed,
Object? allowBypass = freezed,
Object? systemProxy = freezed,
}) {
return _then(_$PropsImpl(
accessControl: freezed == accessControl
? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?,
allowBypass: freezed == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
as bool?,
systemProxy: freezed == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$PropsImpl implements _Props {
const _$PropsImpl({this.accessControl, this.allowBypass, this.systemProxy});
factory _$PropsImpl.fromJson(Map<String, dynamic> json) =>
_$$PropsImplFromJson(json);
@override
final AccessControl? accessControl;
@override
final bool? allowBypass;
@override
final bool? systemProxy;
@override
String toString() {
return 'Props(accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PropsImpl &&
(identical(other.accessControl, accessControl) ||
other.accessControl == accessControl) &&
(identical(other.allowBypass, allowBypass) ||
other.allowBypass == allowBypass) &&
(identical(other.systemProxy, systemProxy) ||
other.systemProxy == systemProxy));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, accessControl, allowBypass, systemProxy);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$PropsImplCopyWith<_$PropsImpl> get copyWith =>
__$$PropsImplCopyWithImpl<_$PropsImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$PropsImplToJson(
this,
);
}
}
abstract class _Props implements Props {
const factory _Props(
{final AccessControl? accessControl,
final bool? allowBypass,
final bool? systemProxy}) = _$PropsImpl;
factory _Props.fromJson(Map<String, dynamic> json) = _$PropsImpl.fromJson;
@override
AccessControl? get accessControl;
@override
bool? get allowBypass;
@override
bool? get systemProxy;
@override
@JsonKey(ignore: true)
_$$PropsImplCopyWith<_$PropsImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -32,7 +32,9 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
: DAV.fromJson(json['dav'] as Map<String, dynamic>) : DAV.fromJson(json['dav'] as Map<String, dynamic>)
..isAnimateToPage = json['isAnimateToPage'] as bool? ?? true ..isAnimateToPage = json['isAnimateToPage'] as bool? ?? true
..isCompatible = json['isCompatible'] as bool? ?? true ..isCompatible = json['isCompatible'] as bool? ?? true
..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true; ..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true
..allowBypass = json['allowBypass'] as bool? ?? true
..systemProxy = json['systemProxy'] as bool? ?? true;
Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'profiles': instance.profiles, 'profiles': instance.profiles,
@@ -52,6 +54,8 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'isAnimateToPage': instance.isAnimateToPage, 'isAnimateToPage': instance.isAnimateToPage,
'isCompatible': instance.isCompatible, 'isCompatible': instance.isCompatible,
'autoCheckUpdate': instance.autoCheckUpdate, 'autoCheckUpdate': instance.autoCheckUpdate,
'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy,
}; };
const _$ThemeModeEnumMap = { const _$ThemeModeEnumMap = {
@@ -93,3 +97,19 @@ const _$AccessControlModeEnumMap = {
AccessControlMode.acceptSelected: 'acceptSelected', AccessControlMode.acceptSelected: 'acceptSelected',
AccessControlMode.rejectSelected: 'rejectSelected', AccessControlMode.rejectSelected: 'rejectSelected',
}; };
_$PropsImpl _$$PropsImplFromJson(Map<String, dynamic> json) => _$PropsImpl(
accessControl: json['accessControl'] == null
? null
: AccessControl.fromJson(
json['accessControl'] as Map<String, dynamic>),
allowBypass: json['allowBypass'] as bool?,
systemProxy: json['systemProxy'] as bool?,
);
Map<String, dynamic> _$$PropsImplToJson(_$PropsImpl instance) =>
<String, dynamic>{
'accessControl': instance.accessControl,
'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy,
};

View File

@@ -27,6 +27,7 @@ mixin _$Metadata {
String get destinationIP => throw _privateConstructorUsedError; String get destinationIP => throw _privateConstructorUsedError;
String get destinationPort => throw _privateConstructorUsedError; String get destinationPort => throw _privateConstructorUsedError;
String get host => throw _privateConstructorUsedError; String get host => throw _privateConstructorUsedError;
String get process => throw _privateConstructorUsedError;
String get remoteDestination => throw _privateConstructorUsedError; String get remoteDestination => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -48,6 +49,7 @@ abstract class $MetadataCopyWith<$Res> {
String destinationIP, String destinationIP,
String destinationPort, String destinationPort,
String host, String host,
String process,
String remoteDestination}); String remoteDestination});
} }
@@ -71,6 +73,7 @@ class _$MetadataCopyWithImpl<$Res, $Val extends Metadata>
Object? destinationIP = null, Object? destinationIP = null,
Object? destinationPort = null, Object? destinationPort = null,
Object? host = null, Object? host = null,
Object? process = null,
Object? remoteDestination = null, Object? remoteDestination = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
@@ -102,6 +105,10 @@ class _$MetadataCopyWithImpl<$Res, $Val extends Metadata>
? _value.host ? _value.host
: host // ignore: cast_nullable_to_non_nullable : host // ignore: cast_nullable_to_non_nullable
as String, as String,
process: null == process
? _value.process
: process // ignore: cast_nullable_to_non_nullable
as String,
remoteDestination: null == remoteDestination remoteDestination: null == remoteDestination
? _value.remoteDestination ? _value.remoteDestination
: remoteDestination // ignore: cast_nullable_to_non_nullable : remoteDestination // ignore: cast_nullable_to_non_nullable
@@ -126,6 +133,7 @@ abstract class _$$MetadataImplCopyWith<$Res>
String destinationIP, String destinationIP,
String destinationPort, String destinationPort,
String host, String host,
String process,
String remoteDestination}); String remoteDestination});
} }
@@ -147,6 +155,7 @@ class __$$MetadataImplCopyWithImpl<$Res>
Object? destinationIP = null, Object? destinationIP = null,
Object? destinationPort = null, Object? destinationPort = null,
Object? host = null, Object? host = null,
Object? process = null,
Object? remoteDestination = null, Object? remoteDestination = null,
}) { }) {
return _then(_$MetadataImpl( return _then(_$MetadataImpl(
@@ -178,6 +187,10 @@ class __$$MetadataImplCopyWithImpl<$Res>
? _value.host ? _value.host
: host // ignore: cast_nullable_to_non_nullable : host // ignore: cast_nullable_to_non_nullable
as String, as String,
process: null == process
? _value.process
: process // ignore: cast_nullable_to_non_nullable
as String,
remoteDestination: null == remoteDestination remoteDestination: null == remoteDestination
? _value.remoteDestination ? _value.remoteDestination
: remoteDestination // ignore: cast_nullable_to_non_nullable : remoteDestination // ignore: cast_nullable_to_non_nullable
@@ -197,6 +210,7 @@ class _$MetadataImpl implements _Metadata {
required this.destinationIP, required this.destinationIP,
required this.destinationPort, required this.destinationPort,
required this.host, required this.host,
required this.process,
required this.remoteDestination}); required this.remoteDestination});
factory _$MetadataImpl.fromJson(Map<String, dynamic> json) => factory _$MetadataImpl.fromJson(Map<String, dynamic> json) =>
@@ -217,11 +231,13 @@ class _$MetadataImpl implements _Metadata {
@override @override
final String host; final String host;
@override @override
final String process;
@override
final String remoteDestination; final String remoteDestination;
@override @override
String toString() { String toString() {
return 'Metadata(uid: $uid, network: $network, sourceIP: $sourceIP, sourcePort: $sourcePort, destinationIP: $destinationIP, destinationPort: $destinationPort, host: $host, remoteDestination: $remoteDestination)'; return 'Metadata(uid: $uid, network: $network, sourceIP: $sourceIP, sourcePort: $sourcePort, destinationIP: $destinationIP, destinationPort: $destinationPort, host: $host, process: $process, remoteDestination: $remoteDestination)';
} }
@override @override
@@ -240,14 +256,24 @@ class _$MetadataImpl implements _Metadata {
(identical(other.destinationPort, destinationPort) || (identical(other.destinationPort, destinationPort) ||
other.destinationPort == destinationPort) && other.destinationPort == destinationPort) &&
(identical(other.host, host) || other.host == host) && (identical(other.host, host) || other.host == host) &&
(identical(other.process, process) || other.process == process) &&
(identical(other.remoteDestination, remoteDestination) || (identical(other.remoteDestination, remoteDestination) ||
other.remoteDestination == remoteDestination)); other.remoteDestination == remoteDestination));
} }
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash(runtimeType, uid, network, sourceIP, int get hashCode => Object.hash(
sourcePort, destinationIP, destinationPort, host, remoteDestination); runtimeType,
uid,
network,
sourceIP,
sourcePort,
destinationIP,
destinationPort,
host,
process,
remoteDestination);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -272,6 +298,7 @@ abstract class _Metadata implements Metadata {
required final String destinationIP, required final String destinationIP,
required final String destinationPort, required final String destinationPort,
required final String host, required final String host,
required final String process,
required final String remoteDestination}) = _$MetadataImpl; required final String remoteDestination}) = _$MetadataImpl;
factory _Metadata.fromJson(Map<String, dynamic> json) = factory _Metadata.fromJson(Map<String, dynamic> json) =
@@ -292,6 +319,8 @@ abstract class _Metadata implements Metadata {
@override @override
String get host; String get host;
@override @override
String get process;
@override
String get remoteDestination; String get remoteDestination;
@override @override
@JsonKey(ignore: true) @JsonKey(ignore: true)

View File

@@ -15,6 +15,7 @@ _$MetadataImpl _$$MetadataImplFromJson(Map<String, dynamic> json) =>
destinationIP: json['destinationIP'] as String, destinationIP: json['destinationIP'] as String,
destinationPort: json['destinationPort'] as String, destinationPort: json['destinationPort'] as String,
host: json['host'] as String, host: json['host'] as String,
process: json['process'] as String,
remoteDestination: json['remoteDestination'] as String, remoteDestination: json['remoteDestination'] as String,
); );
@@ -27,6 +28,7 @@ Map<String, dynamic> _$$MetadataImplToJson(_$MetadataImpl instance) =>
'destinationIP': instance.destinationIP, 'destinationIP': instance.destinationIP,
'destinationPort': instance.destinationPort, 'destinationPort': instance.destinationPort,
'host': instance.host, 'host': instance.host,
'process': instance.process,
'remoteDestination': instance.remoteDestination, 'remoteDestination': instance.remoteDestination,
}; };

View File

@@ -848,10 +848,8 @@ Process _$ProcessFromJson(Map<String, dynamic> json) {
/// @nodoc /// @nodoc
mixin _$Process { mixin _$Process {
int get uid => throw _privateConstructorUsedError; int get id => throw _privateConstructorUsedError;
String get network => throw _privateConstructorUsedError; Metadata get metadata => throw _privateConstructorUsedError;
String get source => throw _privateConstructorUsedError;
String get target => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
@@ -863,7 +861,9 @@ abstract class $ProcessCopyWith<$Res> {
factory $ProcessCopyWith(Process value, $Res Function(Process) then) = factory $ProcessCopyWith(Process value, $Res Function(Process) then) =
_$ProcessCopyWithImpl<$Res, Process>; _$ProcessCopyWithImpl<$Res, Process>;
@useResult @useResult
$Res call({int uid, String network, String source, String target}); $Res call({int id, Metadata metadata});
$MetadataCopyWith<$Res> get metadata;
} }
/// @nodoc /// @nodoc
@@ -879,30 +879,28 @@ class _$ProcessCopyWithImpl<$Res, $Val extends Process>
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? uid = null, Object? id = null,
Object? network = null, Object? metadata = null,
Object? source = null,
Object? target = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
uid: null == uid id: null == id
? _value.uid ? _value.id
: uid // ignore: cast_nullable_to_non_nullable : id // ignore: cast_nullable_to_non_nullable
as int, as int,
network: null == network metadata: null == metadata
? _value.network ? _value.metadata
: network // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
as String, as Metadata,
source: null == source
? _value.source
: source // ignore: cast_nullable_to_non_nullable
as String,
target: null == target
? _value.target
: target // ignore: cast_nullable_to_non_nullable
as String,
) as $Val); ) as $Val);
} }
@override
@pragma('vm:prefer-inline')
$MetadataCopyWith<$Res> get metadata {
return $MetadataCopyWith<$Res>(_value.metadata, (value) {
return _then(_value.copyWith(metadata: value) as $Val);
});
}
} }
/// @nodoc /// @nodoc
@@ -912,7 +910,10 @@ abstract class _$$ProcessImplCopyWith<$Res> implements $ProcessCopyWith<$Res> {
__$$ProcessImplCopyWithImpl<$Res>; __$$ProcessImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({int uid, String network, String source, String target}); $Res call({int id, Metadata metadata});
@override
$MetadataCopyWith<$Res> get metadata;
} }
/// @nodoc /// @nodoc
@@ -926,28 +927,18 @@ class __$$ProcessImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? uid = null, Object? id = null,
Object? network = null, Object? metadata = null,
Object? source = null,
Object? target = null,
}) { }) {
return _then(_$ProcessImpl( return _then(_$ProcessImpl(
uid: null == uid id: null == id
? _value.uid ? _value.id
: uid // ignore: cast_nullable_to_non_nullable : id // ignore: cast_nullable_to_non_nullable
as int, as int,
network: null == network metadata: null == metadata
? _value.network ? _value.metadata
: network // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
as String, as Metadata,
source: null == source
? _value.source
: source // ignore: cast_nullable_to_non_nullable
as String,
target: null == target
? _value.target
: target // ignore: cast_nullable_to_non_nullable
as String,
)); ));
} }
} }
@@ -955,27 +946,19 @@ class __$$ProcessImplCopyWithImpl<$Res>
/// @nodoc /// @nodoc
@JsonSerializable() @JsonSerializable()
class _$ProcessImpl implements _Process { class _$ProcessImpl implements _Process {
const _$ProcessImpl( const _$ProcessImpl({required this.id, required this.metadata});
{required this.uid,
required this.network,
required this.source,
required this.target});
factory _$ProcessImpl.fromJson(Map<String, dynamic> json) => factory _$ProcessImpl.fromJson(Map<String, dynamic> json) =>
_$$ProcessImplFromJson(json); _$$ProcessImplFromJson(json);
@override @override
final int uid; final int id;
@override @override
final String network; final Metadata metadata;
@override
final String source;
@override
final String target;
@override @override
String toString() { String toString() {
return 'Process(uid: $uid, network: $network, source: $source, target: $target)'; return 'Process(id: $id, metadata: $metadata)';
} }
@override @override
@@ -983,15 +966,14 @@ class _$ProcessImpl implements _Process {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$ProcessImpl && other is _$ProcessImpl &&
(identical(other.uid, uid) || other.uid == uid) && (identical(other.id, id) || other.id == id) &&
(identical(other.network, network) || other.network == network) && (identical(other.metadata, metadata) ||
(identical(other.source, source) || other.source == source) && other.metadata == metadata));
(identical(other.target, target) || other.target == target));
} }
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash(runtimeType, uid, network, source, target); int get hashCode => Object.hash(runtimeType, id, metadata);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -1009,27 +991,176 @@ class _$ProcessImpl implements _Process {
abstract class _Process implements Process { abstract class _Process implements Process {
const factory _Process( const factory _Process(
{required final int uid, {required final int id,
required final String network, required final Metadata metadata}) = _$ProcessImpl;
required final String source,
required final String target}) = _$ProcessImpl;
factory _Process.fromJson(Map<String, dynamic> json) = _$ProcessImpl.fromJson; factory _Process.fromJson(Map<String, dynamic> json) = _$ProcessImpl.fromJson;
@override @override
int get uid; int get id;
@override @override
String get network; Metadata get metadata;
@override
String get source;
@override
String get target;
@override @override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$ProcessImplCopyWith<_$ProcessImpl> get copyWith => _$$ProcessImplCopyWith<_$ProcessImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
ProcessMapItem _$ProcessMapItemFromJson(Map<String, dynamic> json) {
return _ProcessMapItem.fromJson(json);
}
/// @nodoc
mixin _$ProcessMapItem {
int get id => throw _privateConstructorUsedError;
String get value => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ProcessMapItemCopyWith<ProcessMapItem> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProcessMapItemCopyWith<$Res> {
factory $ProcessMapItemCopyWith(
ProcessMapItem value, $Res Function(ProcessMapItem) then) =
_$ProcessMapItemCopyWithImpl<$Res, ProcessMapItem>;
@useResult
$Res call({int id, String value});
}
/// @nodoc
class _$ProcessMapItemCopyWithImpl<$Res, $Val extends ProcessMapItem>
implements $ProcessMapItemCopyWith<$Res> {
_$ProcessMapItemCopyWithImpl(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? id = null,
Object? value = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
value: null == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$ProcessMapItemImplCopyWith<$Res>
implements $ProcessMapItemCopyWith<$Res> {
factory _$$ProcessMapItemImplCopyWith(_$ProcessMapItemImpl value,
$Res Function(_$ProcessMapItemImpl) then) =
__$$ProcessMapItemImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({int id, String value});
}
/// @nodoc
class __$$ProcessMapItemImplCopyWithImpl<$Res>
extends _$ProcessMapItemCopyWithImpl<$Res, _$ProcessMapItemImpl>
implements _$$ProcessMapItemImplCopyWith<$Res> {
__$$ProcessMapItemImplCopyWithImpl(
_$ProcessMapItemImpl _value, $Res Function(_$ProcessMapItemImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? value = null,
}) {
return _then(_$ProcessMapItemImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
value: null == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$ProcessMapItemImpl implements _ProcessMapItem {
const _$ProcessMapItemImpl({required this.id, required this.value});
factory _$ProcessMapItemImpl.fromJson(Map<String, dynamic> json) =>
_$$ProcessMapItemImplFromJson(json);
@override
final int id;
@override
final String value;
@override
String toString() {
return 'ProcessMapItem(id: $id, value: $value)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProcessMapItemImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.value, value) || other.value == value));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, id, value);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ProcessMapItemImplCopyWith<_$ProcessMapItemImpl> get copyWith =>
__$$ProcessMapItemImplCopyWithImpl<_$ProcessMapItemImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$ProcessMapItemImplToJson(
this,
);
}
}
abstract class _ProcessMapItem implements ProcessMapItem {
const factory _ProcessMapItem(
{required final int id,
required final String value}) = _$ProcessMapItemImpl;
factory _ProcessMapItem.fromJson(Map<String, dynamic> json) =
_$ProcessMapItemImpl.fromJson;
@override
int get id;
@override
String get value;
@override
@JsonKey(ignore: true)
_$$ProcessMapItemImplCopyWith<_$ProcessMapItemImpl> get copyWith =>
throw _privateConstructorUsedError;
}
ExternalProvider _$ExternalProviderFromJson(Map<String, dynamic> json) { ExternalProvider _$ExternalProviderFromJson(Map<String, dynamic> json) {
return _ExternalProvider.fromJson(json); return _ExternalProvider.fromJson(json);
} }

View File

@@ -56,6 +56,8 @@ const _$MessageTypeEnumMap = {
MessageType.delay: 'delay', MessageType.delay: 'delay',
MessageType.process: 'process', MessageType.process: 'process',
MessageType.now: 'now', MessageType.now: 'now',
MessageType.request: 'request',
MessageType.run: 'run',
}; };
_$DelayImpl _$$DelayImplFromJson(Map<String, dynamic> json) => _$DelayImpl( _$DelayImpl _$$DelayImplFromJson(Map<String, dynamic> json) => _$DelayImpl(
@@ -81,18 +83,27 @@ Map<String, dynamic> _$$NowImplToJson(_$NowImpl instance) => <String, dynamic>{
_$ProcessImpl _$$ProcessImplFromJson(Map<String, dynamic> json) => _$ProcessImpl _$$ProcessImplFromJson(Map<String, dynamic> json) =>
_$ProcessImpl( _$ProcessImpl(
uid: (json['uid'] as num).toInt(), id: (json['id'] as num).toInt(),
network: json['network'] as String, metadata: Metadata.fromJson(json['metadata'] as Map<String, dynamic>),
source: json['source'] as String,
target: json['target'] as String,
); );
Map<String, dynamic> _$$ProcessImplToJson(_$ProcessImpl instance) => Map<String, dynamic> _$$ProcessImplToJson(_$ProcessImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'uid': instance.uid, 'id': instance.id,
'network': instance.network, 'metadata': instance.metadata,
'source': instance.source, };
'target': instance.target,
_$ProcessMapItemImpl _$$ProcessMapItemImplFromJson(Map<String, dynamic> json) =>
_$ProcessMapItemImpl(
id: (json['id'] as num).toInt(),
value: json['value'] as String,
);
Map<String, dynamic> _$$ProcessMapItemImplToJson(
_$ProcessMapItemImpl instance) =>
<String, dynamic>{
'id': instance.id,
'value': instance.value,
}; };
_$ExternalProviderImpl _$$ExternalProviderImplFromJson( _$ExternalProviderImpl _$$ExternalProviderImplFromJson(

View File

@@ -24,10 +24,10 @@ Profile _$ProfileFromJson(Map<String, dynamic> json) => Profile(
id: json['id'] as String?, id: json['id'] as String?,
label: json['label'] as String?, label: json['label'] as String?,
url: json['url'] as String?, url: json['url'] as String?,
currentGroupName: json['currentGroupName'] as String?,
userInfo: json['userInfo'] == null userInfo: json['userInfo'] == null
? null ? null
: UserInfo.fromJson(json['userInfo'] as Map<String, dynamic>), : UserInfo.fromJson(json['userInfo'] as Map<String, dynamic>),
proxyName: json['proxyName'] as String?,
lastUpdateDate: json['lastUpdateDate'] == null lastUpdateDate: json['lastUpdateDate'] == null
? null ? null
: DateTime.parse(json['lastUpdateDate'] as String), : DateTime.parse(json['lastUpdateDate'] as String),
@@ -43,7 +43,7 @@ Profile _$ProfileFromJson(Map<String, dynamic> json) => Profile(
Map<String, dynamic> _$ProfileToJson(Profile instance) => <String, dynamic>{ Map<String, dynamic> _$ProfileToJson(Profile instance) => <String, dynamic>{
'id': instance.id, 'id': instance.id,
'label': instance.label, 'label': instance.label,
'proxyName': instance.proxyName, 'currentGroupName': instance.currentGroupName,
'url': instance.url, 'url': instance.url,
'lastUpdateDate': instance.lastUpdateDate?.toIso8601String(), 'lastUpdateDate': instance.lastUpdateDate?.toIso8601String(),
'autoUpdateDuration': instance.autoUpdateDuration.inMicroseconds, 'autoUpdateDuration': instance.autoUpdateDuration.inMicroseconds,

View File

@@ -28,6 +28,8 @@ const _$GroupTypeEnumMap = {
GroupType.Selector: 'Selector', GroupType.Selector: 'Selector',
GroupType.URLTest: 'URLTest', GroupType.URLTest: 'URLTest',
GroupType.Fallback: 'Fallback', GroupType.Fallback: 'Fallback',
GroupType.LoadBalance: 'LoadBalance',
GroupType.Relay: 'Relay',
}; };
_$ProxyImpl _$$ProxyImplFromJson(Map<String, dynamic> json) => _$ProxyImpl( _$ProxyImpl _$$ProxyImplFromJson(Map<String, dynamic> json) => _$ProxyImpl(

View File

@@ -1583,6 +1583,7 @@ abstract class _ProxiesCardSelectorState implements ProxiesCardSelectorState {
/// @nodoc /// @nodoc
mixin _$ProxiesSelectorState { mixin _$ProxiesSelectorState {
List<String> get groupNames => throw _privateConstructorUsedError; List<String> get groupNames => throw _privateConstructorUsedError;
String? get currentGroupName => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
$ProxiesSelectorStateCopyWith<ProxiesSelectorState> get copyWith => $ProxiesSelectorStateCopyWith<ProxiesSelectorState> get copyWith =>
@@ -1595,7 +1596,7 @@ abstract class $ProxiesSelectorStateCopyWith<$Res> {
$Res Function(ProxiesSelectorState) then) = $Res Function(ProxiesSelectorState) then) =
_$ProxiesSelectorStateCopyWithImpl<$Res, ProxiesSelectorState>; _$ProxiesSelectorStateCopyWithImpl<$Res, ProxiesSelectorState>;
@useResult @useResult
$Res call({List<String> groupNames}); $Res call({List<String> groupNames, String? currentGroupName});
} }
/// @nodoc /// @nodoc
@@ -1613,12 +1614,17 @@ class _$ProxiesSelectorStateCopyWithImpl<$Res,
@override @override
$Res call({ $Res call({
Object? groupNames = null, Object? groupNames = null,
Object? currentGroupName = freezed,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
groupNames: null == groupNames groupNames: null == groupNames
? _value.groupNames ? _value.groupNames
: groupNames // ignore: cast_nullable_to_non_nullable : groupNames // ignore: cast_nullable_to_non_nullable
as List<String>, as List<String>,
currentGroupName: freezed == currentGroupName
? _value.currentGroupName
: currentGroupName // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val); ) as $Val);
} }
} }
@@ -1631,7 +1637,7 @@ abstract class _$$ProxiesSelectorStateImplCopyWith<$Res>
__$$ProxiesSelectorStateImplCopyWithImpl<$Res>; __$$ProxiesSelectorStateImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({List<String> groupNames}); $Res call({List<String> groupNames, String? currentGroupName});
} }
/// @nodoc /// @nodoc
@@ -1646,12 +1652,17 @@ class __$$ProxiesSelectorStateImplCopyWithImpl<$Res>
@override @override
$Res call({ $Res call({
Object? groupNames = null, Object? groupNames = null,
Object? currentGroupName = freezed,
}) { }) {
return _then(_$ProxiesSelectorStateImpl( return _then(_$ProxiesSelectorStateImpl(
groupNames: null == groupNames groupNames: null == groupNames
? _value._groupNames ? _value._groupNames
: groupNames // ignore: cast_nullable_to_non_nullable : groupNames // ignore: cast_nullable_to_non_nullable
as List<String>, as List<String>,
currentGroupName: freezed == currentGroupName
? _value.currentGroupName
: currentGroupName // ignore: cast_nullable_to_non_nullable
as String?,
)); ));
} }
} }
@@ -1659,7 +1670,8 @@ class __$$ProxiesSelectorStateImplCopyWithImpl<$Res>
/// @nodoc /// @nodoc
class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState { class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState {
const _$ProxiesSelectorStateImpl({required final List<String> groupNames}) const _$ProxiesSelectorStateImpl(
{required final List<String> groupNames, required this.currentGroupName})
: _groupNames = groupNames; : _groupNames = groupNames;
final List<String> _groupNames; final List<String> _groupNames;
@@ -1670,9 +1682,12 @@ class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState {
return EqualUnmodifiableListView(_groupNames); return EqualUnmodifiableListView(_groupNames);
} }
@override
final String? currentGroupName;
@override @override
String toString() { String toString() {
return 'ProxiesSelectorState(groupNames: $groupNames)'; return 'ProxiesSelectorState(groupNames: $groupNames, currentGroupName: $currentGroupName)';
} }
@override @override
@@ -1681,12 +1696,14 @@ class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState {
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$ProxiesSelectorStateImpl && other is _$ProxiesSelectorStateImpl &&
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other._groupNames, _groupNames)); .equals(other._groupNames, _groupNames) &&
(identical(other.currentGroupName, currentGroupName) ||
other.currentGroupName == currentGroupName));
} }
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(runtimeType,
runtimeType, const DeepCollectionEquality().hash(_groupNames)); const DeepCollectionEquality().hash(_groupNames), currentGroupName);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -1699,11 +1716,14 @@ class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState {
abstract class _ProxiesSelectorState implements ProxiesSelectorState { abstract class _ProxiesSelectorState implements ProxiesSelectorState {
const factory _ProxiesSelectorState( const factory _ProxiesSelectorState(
{required final List<String> groupNames}) = _$ProxiesSelectorStateImpl; {required final List<String> groupNames,
required final String? currentGroupName}) = _$ProxiesSelectorStateImpl;
@override @override
List<String> get groupNames; List<String> get groupNames;
@override @override
String? get currentGroupName;
@override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$ProxiesSelectorStateImplCopyWith<_$ProxiesSelectorStateImpl> _$$ProxiesSelectorStateImplCopyWith<_$ProxiesSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;

View File

@@ -62,7 +62,7 @@ class UserInfo {
class Profile { class Profile {
String id; String id;
String? label; String? label;
String? proxyName; String? currentGroupName;
String? url; String? url;
DateTime? lastUpdateDate; DateTime? lastUpdateDate;
Duration autoUpdateDuration; Duration autoUpdateDuration;
@@ -74,8 +74,8 @@ class Profile {
String? id, String? id,
this.label, this.label,
this.url, this.url,
this.currentGroupName,
this.userInfo, this.userInfo,
this.proxyName,
this.lastUpdateDate, this.lastUpdateDate,
SelectedMap? selectedMap, SelectedMap? selectedMap,
Duration? autoUpdateDuration, Duration? autoUpdateDuration,
@@ -134,6 +134,7 @@ class Profile {
return _$ProfileFromJson(json); return _$ProfileFromJson(json);
} }
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
@@ -141,34 +142,31 @@ class Profile {
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
id == other.id && id == other.id &&
label == other.label && label == other.label &&
proxyName == other.proxyName && currentGroupName == other.currentGroupName &&
url == other.url && url == other.url &&
lastUpdateDate == other.lastUpdateDate && lastUpdateDate == other.lastUpdateDate &&
autoUpdateDuration == other.autoUpdateDuration && autoUpdateDuration == other.autoUpdateDuration &&
userInfo == other.userInfo && userInfo == other.userInfo &&
autoUpdate == other.autoUpdate; autoUpdate == other.autoUpdate &&
selectedMap == other.selectedMap;
@override @override
int get hashCode => int get hashCode =>
id.hashCode ^ id.hashCode ^
label.hashCode ^ label.hashCode ^
proxyName.hashCode ^ currentGroupName.hashCode ^
url.hashCode ^ url.hashCode ^
lastUpdateDate.hashCode ^ lastUpdateDate.hashCode ^
autoUpdateDuration.hashCode ^ autoUpdateDuration.hashCode ^
userInfo.hashCode ^ userInfo.hashCode ^
autoUpdate.hashCode; autoUpdate.hashCode ^
selectedMap.hashCode;
@override
String toString() {
return 'Profile{id: $id, label: $label, proxyName: $proxyName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate}';
}
Profile copyWith({ Profile copyWith({
String? label, String? label,
String? url, String? url,
UserInfo? userInfo, UserInfo? userInfo,
String? groupName, String? currentGroupName,
String? proxyName, String? proxyName,
DateTime? lastUpdateDate, DateTime? lastUpdateDate,
Duration? autoUpdateDuration, Duration? autoUpdateDuration,
@@ -179,7 +177,7 @@ class Profile {
id: id, id: id,
label: label ?? this.label, label: label ?? this.label,
url: url ?? this.url, url: url ?? this.url,
proxyName: proxyName ?? this.proxyName, currentGroupName: currentGroupName ?? this.currentGroupName,
userInfo: userInfo ?? this.userInfo, userInfo: userInfo ?? this.userInfo,
selectedMap: selectedMap ?? this.selectedMap, selectedMap: selectedMap ?? this.selectedMap,
lastUpdateDate: lastUpdateDate ?? this.lastUpdateDate, lastUpdateDate: lastUpdateDate ?? this.lastUpdateDate,

View File

@@ -94,6 +94,7 @@ class ProxiesCardSelectorState with _$ProxiesCardSelectorState {
class ProxiesSelectorState with _$ProxiesSelectorState { class ProxiesSelectorState with _$ProxiesSelectorState {
const factory ProxiesSelectorState({ const factory ProxiesSelectorState({
required List<String> groupNames, required List<String> groupNames,
required String? currentGroupName,
}) = _ProxiesSelectorState; }) = _ProxiesSelectorState;
} }

View File

@@ -107,6 +107,9 @@ class _ScanPageState extends State<ScanPage> with WidgetsBindingObserver {
case TorchState.unavailable: case TorchState.unavailable:
icon = const Icon(Icons.flash_off); icon = const Icon(Icons.flash_off);
backgroundColor = Colors.transparent; backgroundColor = Colors.transparent;
case TorchState.auto:
icon = const Icon(Icons.flash_auto);
backgroundColor = Colors.orange;
} }
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 8), margin: const EdgeInsets.symmetric(horizontal: 8),

View File

@@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@@ -22,6 +23,9 @@ class App {
await onExit!(); await onExit!();
} }
break; break;
case "gc":
clashCore.requestGc();
break;
default: default:
throw MissingPluginException(); throw MissingPluginException();
} }
@@ -29,10 +33,6 @@ class App {
} }
} }
setOnExit(Function() onExit) {
this.onExit = onExit;
}
factory App() { factory App() {
_instance ??= App._internal(); _instance ??= App._internal();
return _instance!; return _instance!;
@@ -68,9 +68,9 @@ class App {
}); });
} }
Future<String?> getPackageName(Metadata metadata) async { Future<String?> resolverProcess(Process process) async {
return await methodChannel?.invokeMethod<String>("getPackageName", { return await methodChannel?.invokeMethod<String>("resolverProcess", {
"data": json.encode(metadata), "data": json.encode(process),
}); });
} }
} }

View File

@@ -79,6 +79,10 @@ class Proxy extends ProxyPlatform {
} }
} }
// updateStartTime() async {
// startTime = clashCore.getRunTime();
// }
updateStartTime() async { updateStartTime() async {
startTime = await getRunTime(); startTime = await getRunTime();
} }

View File

@@ -17,7 +17,6 @@ class GlobalState {
Function? updateCurrentDelayDebounce; Function? updateCurrentDelayDebounce;
PageController? pageController; PageController? pageController;
final navigatorKey = GlobalKey<NavigatorState>(); final navigatorKey = GlobalKey<NavigatorState>();
final Map<int, String?> packageNameMap = {};
late AppController appController; late AppController appController;
GlobalKey<CommonScaffoldState> homeScaffoldKey = GlobalKey(); GlobalKey<CommonScaffoldState> homeScaffoldKey = GlobalKey();
List<Function> updateFunctionLists = []; List<Function> updateFunctionLists = [];
@@ -58,16 +57,28 @@ class GlobalState {
} }
Future<void> startSystemProxy({ Future<void> startSystemProxy({
required AppState appState,
required Config config, required Config config,
required ClashConfig clashConfig, required ClashConfig clashConfig,
}) async { }) async {
final args = final args = config.isAccessControl
config.isAccessControl ? json.encode(config.accessControl) : null; ? json.encode(
Props(
accessControl: config.accessControl,
allowBypass: config.allowBypass,
),
)
: null;
await proxyManager.startProxy( await proxyManager.startProxy(
port: clashConfig.mixedPort, port: clashConfig.mixedPort,
args: args, args: args,
); );
startListenUpdate(); startListenUpdate();
applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
} }
Future<void> stopSystemProxy() async { Future<void> stopSystemProxy() async {
@@ -105,12 +116,6 @@ class GlobalState {
clashConfig: clashConfig, clashConfig: clashConfig,
); );
} }
if (!appState.isInit) return;
await applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
updateCoreVersionInfo(appState); updateCoreVersionInfo(appState);
} }
@@ -133,19 +138,6 @@ class GlobalState {
}); });
} }
updateNavigationItems({
required AppState appState,
required Config config,
required ClashConfig clashConfig,
}) {
final group = appState.currentGroups;
final hasProfile = config.profiles.isNotEmpty;
appState.navigationItems = navigation.getItems(
openLogs: config.openLogs,
hasProxies: group.isNotEmpty && hasProfile,
);
}
Future<void> updateGroups(AppState appState) async { Future<void> updateGroups(AppState appState) async {
appState.groups = await clashCore.getProxiesGroups(); appState.groups = await clashCore.getProxiesGroups();
} }
@@ -209,6 +201,7 @@ class GlobalState {
final traffic = clashCore.getTraffic(); final traffic = clashCore.getTraffic();
if (appState != null) { if (appState != null) {
appState.addTraffic(traffic); appState.addTraffic(traffic);
appState.totalTraffic = clashCore.getTotalTraffic();
} }
if (Platform.isAndroid) { if (Platform.isAndroid) {
final currentProfile = config.currentProfile; final currentProfile = config.currentProfile;
@@ -225,30 +218,30 @@ class GlobalState {
required String message, required String message,
SnackBarAction? action, SnackBarAction? action,
}) { }) {
final width = context.width; // final width = context.width;
EdgeInsets margin; // EdgeInsets margin;
if (width < 600) { // if (width < 600) {
margin = const EdgeInsets.only( // margin = const EdgeInsets.only(
bottom: 96, // bottom: 96,
right: 16, // right: 16,
left: 16, // left: 16,
); // );
} else { // } else {
margin = EdgeInsets.only( // margin = EdgeInsets.only(
bottom: 16, // bottom: 16,
left: 16, // left: 16,
right: width - 316, // right: width - 316,
); // );
} // }
ScaffoldMessenger.of(context).showSnackBar( // ScaffoldMessenger.of(context).showSnackBar(
SnackBar( // SnackBar(
action: action, // action: action,
content: Text(message), // content: Text(message),
behavior: SnackBarBehavior.floating, // behavior: SnackBarBehavior.floating,
duration: const Duration(milliseconds: 1500), // duration: const Duration(milliseconds: 1500),
margin: margin, // margin: margin,
), // ),
); // );
} }
Future<T?> safeRun<T>( Future<T?> safeRun<T>(

View File

@@ -1,6 +1,7 @@
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/proxy.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/plugins/app.dart';
@@ -56,9 +57,26 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
} }
@override @override
void onProcess(Metadata metadata) async { void onProcess(Process process) async {
var packageName = await app?.getPackageName(metadata); var packageName = await app?.resolverProcess(process);
globalState.packageNameMap[metadata.uid] = packageName; clashCore.setProcessMap(
super.onProcess(metadata); ProcessMapItem(
id: process.id,
value: packageName ?? "",
),
);
super.onProcess(process);
}
@override
void onRequest(Connection connection) async {
globalState.appController.appState.addRequest(connection);
super.onRequest(connection);
}
@override
void onRun(String runTime) async {
// proxy?.updateStartTime();
super.onRun(runTime);
} }
} }

View File

@@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
class CommonScaffold extends StatefulWidget { class CommonScaffold extends StatefulWidget {
final Widget body; final Widget body;
final Widget? bottomNavigationBar; final Widget? bottomNavigationBar;
final Widget? floatingActionButton;
final String title; final String title;
final Widget? leading; final Widget? leading;
final List<Widget>? actions; final List<Widget>? actions;
@@ -19,6 +20,7 @@ class CommonScaffold extends StatefulWidget {
this.leading, this.leading,
required this.title, required this.title,
this.actions, this.actions,
this.floatingActionButton,
this.automaticallyImplyLeading = true, this.automaticallyImplyLeading = true,
}); });
@@ -116,13 +118,14 @@ class CommonScaffoldState extends State<CommonScaffold> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _platformContainer( return _platformContainer(
child: Scaffold( child: Scaffold(
floatingActionButton: ValueListenableBuilder( floatingActionButton: widget.floatingActionButton ??
valueListenable: _floatingActionButton, ValueListenableBuilder(
builder: (_, floatingActionButton, __) { valueListenable: _floatingActionButton,
return floatingActionButton ?? Container(); builder: (_, floatingActionButton, __) {
}, return floatingActionButton ?? Container();
), },
floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling, ),
resizeToAvoidBottomInset: true,
appBar: PreferredSize( appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight), preferredSize: const Size.fromHeight(kToolbarHeight),
child: Stack( child: Stack(

View File

@@ -277,10 +277,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: file_picker name: file_picker
sha256: "29c90806ac5f5fb896547720b73b17ee9aed9bba540dc5d91fe29f8c5745b10a" sha256: "2ca051989f69d1b2ca012b2cf3ccf78c70d40144f0861ff2c063493f7c8c3d45"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "8.0.3" version: "8.0.5"
file_selector_linux: file_selector_linux:
dependency: transitive dependency: transitive
description: description:
@@ -517,6 +517,22 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
isolate_contactor:
dependency: transitive
description:
name: isolate_contactor
sha256: f1be0a90f91e4309ef37cc45280b2a84e769e848aae378318dd3dd263cfc482a
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.2.0"
isolate_manager:
dependency: transitive
description:
name: isolate_manager
sha256: "8fb916c4444fd408f089448f904f083ac3e169ea1789fd4d987b25809af92188"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.3.1"
jovial_misc: jovial_misc:
dependency: transitive dependency: transitive
description: description:
@@ -537,10 +553,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: js name: js
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.7.1" version: "0.6.7"
json_annotation: json_annotation:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -649,10 +665,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: mobile_scanner name: mobile_scanner
sha256: f34c83198d9381f6c100dfaec647c275630840cbcda5d6c5eb6ba264beb96be4 sha256: b8c0e9afcfd52534f85ec666f3d52156f560b5e6c25b1e3d4fe2087763607926
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "5.0.1" version: "5.1.1"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@@ -753,10 +769,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.1.4" version: "3.1.5"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -820,6 +836,22 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.2.1" version: "3.2.1"
re_editor:
dependency: "direct main"
description:
name: re_editor
sha256: db7a82e95f0f74301e85d4d5c805a8b8a5ba43d6c0d26673b7e35dc011f06635
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.0"
re_highlight:
dependency: "direct main"
description:
name: re_highlight
sha256: "6c4ac3f76f939fb7ca9df013df98526634e17d8f7460e028bd23a035870024f2"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.3"
screen_retriever: screen_retriever:
dependency: transitive dependency: transitive
description: description:

View File

@@ -1,7 +1,7 @@
name: fl_clash name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free. description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none' publish_to: 'none'
version: 0.8.16 version: 0.8.25
environment: environment:
sdk: '>=3.1.0 <4.0.0' sdk: '>=3.1.0 <4.0.0'
@@ -24,7 +24,7 @@ dependencies:
windows_single_instance: ^1.0.1 windows_single_instance: ^1.0.1
json_annotation: ^4.9.0 json_annotation: ^4.9.0
file_picker: ^8.0.3 file_picker: ^8.0.3
mobile_scanner: 5.0.1 mobile_scanner: ^5.1.1
app_links: ^3.5.0 app_links: ^3.5.0
win32_registry: ^1.1.2 win32_registry: ^1.1.2
tray_manager: ^0.2.1 tray_manager: ^0.2.1
@@ -39,6 +39,8 @@ dependencies:
webdav_client: ^1.2.2 webdav_client: ^1.2.2
dio: ^5.4.3+1 dio: ^5.4.3+1
country_flags: ^2.2.0 country_flags: ^2.2.0
re_editor: ^0.3.0
re_highlight: ^0.0.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter

View File

@@ -12,10 +12,7 @@ enum PlatformType {
macos, macos,
} }
enum Arch { enum Arch { amd64, arm64, arm }
amd64,
arm64,
}
class BuildLibItem { class BuildLibItem {
PlatformType platform; PlatformType platform;
@@ -64,6 +61,11 @@ class Build {
arch: Arch.amd64, arch: Arch.amd64,
archName: 'amd64', archName: 'amd64',
), ),
BuildLibItem(
platform: PlatformType.android,
arch: Arch.arm,
archName: 'armeabi-v7a',
),
BuildLibItem( BuildLibItem(
platform: PlatformType.android, platform: PlatformType.android,
arch: Arch.arm64, arch: Arch.arm64,
@@ -334,7 +336,7 @@ class BuildCommand extends Command {
final archName = argResults?['arch']; final archName = argResults?['arch'];
final currentArches = final currentArches =
arches.where((element) => element.name == archName).toList(); arches.where((element) => element.name == archName).toList();
final arch = currentArches.isEmpty ? null : arches.first; final arch = currentArches.isEmpty ? null : currentArches.first;
await _buildLib(arch); await _buildLib(arch);
if (build != "all") { if (build != "all") {
return; return;
@@ -357,10 +359,11 @@ class BuildCommand extends Command {
break; break;
case PlatformType.android: case PlatformType.android:
final targetMap = { final targetMap = {
Arch.arm: "android-arm",
Arch.arm64: "android-arm64",
Arch.amd64: "android-x64", Arch.amd64: "android-x64",
Arch.arm64: "android-arm64"
}; };
final defaultArches = [Arch.amd64, Arch.arm64]; final defaultArches = [Arch.arm, Arch.arm64, Arch.amd64];
final defaultTargets = defaultArches final defaultTargets = defaultArches
.where((element) => arch == null ? true : element == arch) .where((element) => arch == null ? true : element == arch)
.map((e) => targetMap[e]) .map((e) => targetMap[e])

View File

@@ -1,5 +1,4 @@
// ignore_for_file: avoid_print // ignore_for_file: avoid_print
void main() async { void main() async {
String input = """ String input = """