Compare commits
1 Commits
v0.8.92
...
v0.8.92-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdfb688e62 |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -21,7 +21,7 @@ migrate_working_dir/
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
@@ -41,6 +41,11 @@ app.*.symbols
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
#AI generated
|
||||
CLAUDE.md
|
||||
/.claude
|
||||
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
@@ -53,7 +58,6 @@ app.*.map.json
|
||||
/android/core/**/cmake-build-*/
|
||||
/android/core/**/jniLibs/
|
||||
|
||||
|
||||
#FlClash
|
||||
/libclash/
|
||||
/android/app/src/main/jniLibs/
|
||||
@@ -61,3 +65,6 @@ app.*.map.json
|
||||
/macos/**/Package.resolved
|
||||
devtools_options.yaml
|
||||
|
||||
# FVM Version Cache
|
||||
.fvm/
|
||||
.fvmrc
|
||||
29
.metadata
29
.metadata
@@ -1,11 +1,11 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled.
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
channel: stable
|
||||
revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
@@ -13,26 +13,11 @@ project_type: app
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
- platform: android
|
||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
- platform: ios
|
||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
- platform: linux
|
||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
- platform: macos
|
||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
- platform: web
|
||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
- platform: windows
|
||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
||||
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
|
||||
# User provided section
|
||||
|
||||
|
||||
7
.run/main.dart.run.xml
Normal file
7
.run/main.dart.run.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
|
||||
<option name="additionalArgs" value="--dart-define-from-file env.json" />
|
||||
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
10
Makefile
10
Makefile
@@ -1,10 +0,0 @@
|
||||
android_arm64:
|
||||
dart ./setup.dart android --arch arm64
|
||||
macos_arm64:
|
||||
dart ./setup.dart macos --arch arm64
|
||||
android_app:
|
||||
dart ./setup.dart android
|
||||
android_arm64_core:
|
||||
dart ./setup.dart android --arch arm64 --out core
|
||||
macos_arm64_core:
|
||||
dart ./setup.dart macos --arch arm64 --out core
|
||||
@@ -64,16 +64,17 @@ android {
|
||||
buildTypes {
|
||||
debug {
|
||||
isMinifyEnabled = false
|
||||
applicationIdSuffix = ".debug"
|
||||
applicationIdSuffix = ".dev"
|
||||
}
|
||||
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
signingConfig = if (isRelease) {
|
||||
signingConfigs.getByName("release")
|
||||
if (isRelease) {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
} else {
|
||||
signingConfigs.getByName("debug")
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
applicationIdSuffix = ".dev"
|
||||
}
|
||||
|
||||
proguardFiles(
|
||||
|
||||
@@ -41,6 +41,25 @@
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
|
||||
"android_client_info": {
|
||||
"package_name": "com.follow.clash.dev"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "0"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
package com.follow.clash
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context.MODE_PRIVATE
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.widget.Toast
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.models.SharedState
|
||||
import com.google.gson.Gson
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
@@ -21,6 +26,30 @@ import kotlin.coroutines.resume
|
||||
|
||||
private const val ICON_TTL_DAYS = 1L
|
||||
|
||||
val Application.sharedState: SharedState
|
||||
get() {
|
||||
try {
|
||||
val sp = getSharedPreferences("FlutterSharedPreferences", MODE_PRIVATE)
|
||||
val res = sp.getString("flutter.sharedState", "")
|
||||
return Gson().fromJson(res, SharedState::class.java)
|
||||
} catch (_: Exception) {
|
||||
return SharedState()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var lastToast: Toast? = null
|
||||
|
||||
fun Application.showToast(text: String?) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
lastToast?.cancel()
|
||||
lastToast = Toast.makeText(this, text, Toast.LENGTH_LONG).apply {
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
suspend fun PackageManager.getPackageIconPath(packageName: String): String =
|
||||
withContext(Dispatchers.IO) {
|
||||
val cacheDir = GlobalState.application.cacheDir
|
||||
@@ -118,4 +147,4 @@ fun <T> MethodChannel.invokeMethodOnMainThread(
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.follow.clash
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.plugins.AppPlugin
|
||||
import com.follow.clash.plugins.ServicePlugin
|
||||
@@ -18,9 +17,6 @@ class MainActivity : FlutterActivity(),
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
State.destroyServiceEngine()
|
||||
}
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.follow.clash
|
||||
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.common.ServiceDelegate
|
||||
import com.follow.clash.common.formatString
|
||||
import com.follow.clash.common.intent
|
||||
@@ -8,6 +9,7 @@ import com.follow.clash.service.ICallbackInterface
|
||||
import com.follow.clash.service.IEventInterface
|
||||
import com.follow.clash.service.IRemoteInterface
|
||||
import com.follow.clash.service.IResultInterface
|
||||
import com.follow.clash.service.IVoidInterface
|
||||
import com.follow.clash.service.RemoteService
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
import com.follow.clash.service.models.VpnOptions
|
||||
@@ -40,7 +42,7 @@ object Service {
|
||||
delegate.unbind()
|
||||
}
|
||||
|
||||
suspend fun invokeAction(data: String, cb: (result: String) -> Unit): Result<Unit> {
|
||||
suspend fun invokeAction(data: String, cb: ((result: String) -> Unit)?): Result<Unit> {
|
||||
val res = mutableListOf<ByteArray>()
|
||||
return delegate.useService {
|
||||
it.invokeAction(
|
||||
@@ -51,13 +53,50 @@ object Service {
|
||||
res.add(result ?: byteArrayOf())
|
||||
ack?.onAck()
|
||||
if (isSuccess) {
|
||||
cb(res.formatString())
|
||||
cb?.let { cb ->
|
||||
cb(res.formatString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun quickSetup(
|
||||
initParamsString: String,
|
||||
setupParamsString: String,
|
||||
onStarted: (() -> Unit)?,
|
||||
onResult: ((result: String) -> Unit)?,
|
||||
): Result<Unit> {
|
||||
val res = mutableListOf<ByteArray>()
|
||||
return delegate.useService {
|
||||
it.quickSetup(
|
||||
initParamsString,
|
||||
setupParamsString,
|
||||
object : ICallbackInterface.Stub() {
|
||||
override fun onResult(
|
||||
result: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
|
||||
) {
|
||||
res.add(result ?: byteArrayOf())
|
||||
ack?.onAck()
|
||||
if (isSuccess) {
|
||||
onResult?.let { cb ->
|
||||
cb(res.formatString())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
object : IVoidInterface.Stub() {
|
||||
override fun invoke() {
|
||||
onStarted?.let { onStarted ->
|
||||
onStarted()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setEventListener(
|
||||
cb: ((result: String?) -> Unit)?
|
||||
): Result<Unit> {
|
||||
@@ -65,24 +104,24 @@ object Service {
|
||||
return delegate.useService {
|
||||
it.setEventListener(
|
||||
when (cb != null) {
|
||||
true -> object : IEventInterface.Stub() {
|
||||
override fun onEvent(
|
||||
id: String, data: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
|
||||
) {
|
||||
if (results[id] == null) {
|
||||
results[id] = mutableListOf()
|
||||
}
|
||||
results[id]?.add(data ?: byteArrayOf())
|
||||
ack?.onAck()
|
||||
if (isSuccess) {
|
||||
cb(results[id]?.formatString())
|
||||
results.remove(id)
|
||||
true -> object : IEventInterface.Stub() {
|
||||
override fun onEvent(
|
||||
id: String, data: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
|
||||
) {
|
||||
if (results[id] == null) {
|
||||
results[id] = mutableListOf()
|
||||
}
|
||||
results[id]?.add(data ?: byteArrayOf())
|
||||
ack?.onAck()
|
||||
if (isSuccess) {
|
||||
cb(results[id]?.formatString())
|
||||
results.remove(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false -> null
|
||||
})
|
||||
false -> null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +155,7 @@ object Service {
|
||||
try {
|
||||
block(callback)
|
||||
} catch (e: Exception) {
|
||||
GlobalState.log("awaitIResultInterface $e")
|
||||
if (continuation.isActive) {
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
package com.follow.clash
|
||||
|
||||
import android.net.VpnService
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.models.SharedState
|
||||
import com.follow.clash.plugins.AppPlugin
|
||||
import com.follow.clash.plugins.ServicePlugin
|
||||
import com.follow.clash.plugins.TilePlugin
|
||||
import io.flutter.FlutterInjector
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
import com.google.gson.Gson
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.embedding.engine.dart.DartExecutor
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
enum class RunState {
|
||||
START, PENDING, STOP
|
||||
@@ -25,20 +24,17 @@ object State {
|
||||
|
||||
var runTime: Long = 0
|
||||
|
||||
var sharedState: SharedState = SharedState()
|
||||
|
||||
val runStateFlow: MutableStateFlow<RunState> = MutableStateFlow(RunState.STOP)
|
||||
|
||||
var flutterEngine: FlutterEngine? = null
|
||||
var serviceFlutterEngine: FlutterEngine? = null
|
||||
|
||||
val appPlugin: AppPlugin?
|
||||
get() = flutterEngine?.plugin<AppPlugin>() ?: serviceFlutterEngine?.plugin<AppPlugin>()
|
||||
|
||||
val servicePlugin: ServicePlugin?
|
||||
get() = flutterEngine?.plugin<ServicePlugin>()
|
||||
?: serviceFlutterEngine?.plugin<ServicePlugin>()
|
||||
get() = flutterEngine?.plugin<AppPlugin>()
|
||||
|
||||
val tilePlugin: TilePlugin?
|
||||
get() = flutterEngine?.plugin<TilePlugin>() ?: serviceFlutterEngine?.plugin<TilePlugin>()
|
||||
get() = flutterEngine?.plugin<TilePlugin>()
|
||||
|
||||
suspend fun handleToggleAction() {
|
||||
var action: (suspend () -> Unit)?
|
||||
@@ -77,7 +73,7 @@ object State {
|
||||
if (flutterEngine != null) {
|
||||
return
|
||||
}
|
||||
startServiceWithEngine()
|
||||
startServiceWithPref()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -88,9 +84,10 @@ object State {
|
||||
return
|
||||
}
|
||||
tilePlugin?.handleStop()
|
||||
if (flutterEngine != null || serviceFlutterEngine != null) {
|
||||
if (flutterEngine != null) {
|
||||
return
|
||||
}
|
||||
GlobalState.application.showToast(sharedState.stopTip)
|
||||
handleStopService()
|
||||
}
|
||||
}
|
||||
@@ -106,72 +103,99 @@ object State {
|
||||
startService()
|
||||
}
|
||||
|
||||
fun handleStopService() {
|
||||
GlobalState.launch {
|
||||
runLock.withLock {
|
||||
if (runStateFlow.value != RunState.START) {
|
||||
return@launch
|
||||
}
|
||||
runStateFlow.tryEmit(RunState.PENDING)
|
||||
runTime = Service.stopService()
|
||||
runStateFlow.tryEmit(RunState.STOP)
|
||||
}
|
||||
destroyServiceEngine()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun destroyServiceEngine() {
|
||||
runLock.withLock {
|
||||
GlobalState.log("Destroy service engine")
|
||||
withContext(Dispatchers.Main) {
|
||||
runCatching {
|
||||
serviceFlutterEngine?.destroy()
|
||||
serviceFlutterEngine = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startServiceWithEngine() {
|
||||
private fun startServiceWithPref() {
|
||||
GlobalState.launch {
|
||||
runLock.withLock {
|
||||
if (runStateFlow.value != RunState.STOP) {
|
||||
return@launch
|
||||
}
|
||||
GlobalState.log("Create service engine")
|
||||
withContext(Dispatchers.Main) {
|
||||
serviceFlutterEngine?.destroy()
|
||||
serviceFlutterEngine = FlutterEngine(GlobalState.application)
|
||||
serviceFlutterEngine?.plugins?.add(ServicePlugin())
|
||||
serviceFlutterEngine?.plugins?.add(AppPlugin())
|
||||
serviceFlutterEngine?.plugins?.add(TilePlugin())
|
||||
val dartEntrypoint = DartExecutor.DartEntrypoint(
|
||||
FlutterInjector.instance().flutterLoader().findAppBundlePath(), "_service"
|
||||
)
|
||||
serviceFlutterEngine?.dartExecutor?.executeDartEntrypoint(dartEntrypoint)
|
||||
}
|
||||
sharedState = GlobalState.application.sharedState
|
||||
setupAndStart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun syncState() {
|
||||
GlobalState.setCrashlytics(sharedState.crashlytics)
|
||||
Service.updateNotificationParams(
|
||||
NotificationParams(
|
||||
title = sharedState.currentProfileName,
|
||||
stopText = sharedState.stopText,
|
||||
onlyStatisticsProxy = sharedState.onlyStatisticsProxy
|
||||
)
|
||||
)
|
||||
Service.setCrashlytics(sharedState.crashlytics)
|
||||
}
|
||||
|
||||
private suspend fun setupAndStart() {
|
||||
Service.bind()
|
||||
syncState()
|
||||
GlobalState.application.showToast(sharedState.startTip)
|
||||
val initParams = mutableMapOf<String, Any>()
|
||||
initParams["home-dir"] = GlobalState.application.filesDir.path
|
||||
initParams["version"] = android.os.Build.VERSION.SDK_INT
|
||||
val initParamsString = Gson().toJson(initParams)
|
||||
val setupParamsString = Gson().toJson(sharedState.setupParams)
|
||||
Service.quickSetup(
|
||||
initParamsString,
|
||||
setupParamsString,
|
||||
onStarted = {
|
||||
startService()
|
||||
},
|
||||
onResult = {
|
||||
if (it.isNotEmpty()) {
|
||||
GlobalState.application.showToast(it)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun startService() {
|
||||
GlobalState.launch {
|
||||
runLock.withLock {
|
||||
if (runStateFlow.value != RunState.STOP) {
|
||||
return@launch
|
||||
}
|
||||
runStateFlow.tryEmit(RunState.PENDING)
|
||||
if (servicePlugin == null) {
|
||||
return@launch
|
||||
try {
|
||||
runStateFlow.tryEmit(RunState.PENDING)
|
||||
val options = sharedState.vpnOptions ?: return@launch
|
||||
appPlugin?.let {
|
||||
it.prepare(options.enable) {
|
||||
runTime = Service.startService(options, runTime)
|
||||
runStateFlow.tryEmit(RunState.START)
|
||||
}
|
||||
} ?: run {
|
||||
val intent = VpnService.prepare(GlobalState.application)
|
||||
if (intent != null) {
|
||||
return@launch
|
||||
}
|
||||
runTime = Service.startService(options, runTime)
|
||||
runStateFlow.tryEmit(RunState.START)
|
||||
}
|
||||
} finally {
|
||||
if (runStateFlow.value == RunState.PENDING) {
|
||||
runStateFlow.tryEmit(RunState.STOP)
|
||||
}
|
||||
}
|
||||
val options = servicePlugin?.handleGetVpnOptions() ?: return@launch
|
||||
appPlugin?.prepare(options.enable) {
|
||||
runTime = Service.startService(options, runTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun handleStopService() {
|
||||
runLock.withLock {
|
||||
if (runStateFlow.value != RunState.START) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
runStateFlow.tryEmit(RunState.PENDING)
|
||||
runTime = Service.stopService()
|
||||
runStateFlow.tryEmit(RunState.STOP)
|
||||
} finally {
|
||||
if (runStateFlow.value == RunState.PENDING) {
|
||||
runStateFlow.tryEmit(RunState.START)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
package com.follow.clash.models
|
||||
|
||||
import com.follow.clash.service.models.VpnOptions
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class AppState(
|
||||
data class SharedState(
|
||||
val startTip: String = "Starting VPN...",
|
||||
val stopTip: String = "Stopping VPN...",
|
||||
val crashlytics: Boolean = true,
|
||||
val currentProfileName: String = "FlClash",
|
||||
val stopText: String = "Stop",
|
||||
val onlyStatisticsProxy: Boolean = false,
|
||||
val vpnOptions: VpnOptions? = null,
|
||||
val setupParams: SetupParams? = null,
|
||||
)
|
||||
|
||||
data class SetupParams(
|
||||
@SerializedName("test-url")
|
||||
val testUrl: String,
|
||||
@SerializedName("selected-map")
|
||||
val selectedMap: Map<String, String>,
|
||||
)
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.content.pm.ComponentInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
@@ -24,6 +23,7 @@ import com.follow.clash.common.QuickAction
|
||||
import com.follow.clash.common.quickIntent
|
||||
import com.follow.clash.getPackageIconPath
|
||||
import com.follow.clash.models.Package
|
||||
import com.follow.clash.showToast
|
||||
import com.google.gson.Gson
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
@@ -193,7 +193,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
}
|
||||
|
||||
private fun tip(message: String?) {
|
||||
Toast.makeText(GlobalState.application, message, Toast.LENGTH_LONG).show()
|
||||
GlobalState.application.showToast(message)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
|
||||
@@ -3,13 +3,9 @@ package com.follow.clash.plugins
|
||||
import com.follow.clash.RunState
|
||||
import com.follow.clash.Service
|
||||
import com.follow.clash.State
|
||||
import com.follow.clash.awaitResult
|
||||
import com.follow.clash.common.Components
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.invokeMethodOnMainThread
|
||||
import com.follow.clash.models.AppState
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
import com.follow.clash.service.models.VpnOptions
|
||||
import com.follow.clash.models.SharedState
|
||||
import com.google.gson.Gson
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
@@ -38,7 +34,7 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) {
|
||||
"init" -> {
|
||||
handleInit(call, result)
|
||||
handleInit(result)
|
||||
}
|
||||
|
||||
"shutdown" -> {
|
||||
@@ -90,13 +86,10 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
}
|
||||
|
||||
private fun handleStop(result: MethodChannel.Result) {
|
||||
State.handleStopService()
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
suspend fun handleGetVpnOptions(): VpnOptions? {
|
||||
val res = flutterMethodChannel.awaitResult<String>("getVpnOptions", null)
|
||||
return Gson().fromJson(res, VpnOptions::class.java)
|
||||
launch {
|
||||
State.handleStopService()
|
||||
result.success(true)
|
||||
}
|
||||
}
|
||||
|
||||
val semaphore = Semaphore(10)
|
||||
@@ -116,31 +109,19 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
|
||||
private fun handleSyncState(call: MethodCall, result: MethodChannel.Result) {
|
||||
val data = call.arguments<String>()!!
|
||||
val params = Gson().fromJson(data, AppState::class.java)
|
||||
GlobalState.setCrashlytics(params.crashlytics)
|
||||
State.sharedState = Gson().fromJson(data, SharedState::class.java)
|
||||
launch {
|
||||
Service.updateNotificationParams(
|
||||
NotificationParams(
|
||||
title = params.currentProfileName,
|
||||
stopText = params.stopText,
|
||||
onlyStatisticsProxy = params.onlyStatisticsProxy
|
||||
)
|
||||
)
|
||||
Service.setCrashlytics(params.crashlytics)
|
||||
State.syncState()
|
||||
result.success("")
|
||||
}
|
||||
}
|
||||
|
||||
fun handleInit(call: MethodCall, result: MethodChannel.Result) {
|
||||
|
||||
fun handleInit(result: MethodChannel.Result) {
|
||||
Service.bind()
|
||||
launch {
|
||||
val needSetEventListener = call.arguments<Boolean>() ?: false
|
||||
when (needSetEventListener) {
|
||||
true -> Service.setEventListener {
|
||||
handleSendEvent(it)
|
||||
}
|
||||
|
||||
false -> Service.setEventListener(null)
|
||||
Service.setEventListener {
|
||||
handleSendEvent(it)
|
||||
}.onSuccess {
|
||||
result.success("")
|
||||
}.onFailure {
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath(libs.build.kotlin)
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.android.library") apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory =
|
||||
rootProject.layout.buildDirectory
|
||||
.dir("../../build")
|
||||
@@ -31,4 +22,3 @@ subprojects {
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,14 @@ Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean su
|
||||
suspend(suspended);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_quickSetup(JNIEnv *env, jobject thiz, jstring init_params_string,
|
||||
jstring setup_params_string, jobject cb) {
|
||||
const auto interface = new_global(cb);
|
||||
quickSetup(interface, get_string(init_params_string), get_string(setup_params_string));
|
||||
}
|
||||
|
||||
|
||||
static jmethodID m_tun_interface_protect;
|
||||
static jmethodID m_tun_interface_resolve_process;
|
||||
@@ -99,12 +107,12 @@ call_tun_interface_resolve_process_impl(void *tun_interface, const int protocol,
|
||||
const int uid) {
|
||||
ATTACH_JNI();
|
||||
const auto packageName = reinterpret_cast<jstring>(env->CallObjectMethod(
|
||||
static_cast<jobject>(tun_interface),
|
||||
m_tun_interface_resolve_process,
|
||||
protocol,
|
||||
new_string(source),
|
||||
new_string(target),
|
||||
uid));
|
||||
static_cast<jobject>(tun_interface),
|
||||
m_tun_interface_resolve_process,
|
||||
protocol,
|
||||
new_string(source),
|
||||
new_string(target),
|
||||
uid));
|
||||
return get_string(packageName);
|
||||
}
|
||||
|
||||
@@ -191,4 +199,10 @@ extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean suspended) {
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_quickSetup(JNIEnv *env, jobject thiz, jstring init_params_string,
|
||||
jstring setup_params_string, jobject cb) {
|
||||
}
|
||||
#endif
|
||||
@@ -102,6 +102,28 @@ data object Core {
|
||||
}
|
||||
}
|
||||
|
||||
fun quickSetup(
|
||||
initParamsString: String,
|
||||
setupParamsString: String,
|
||||
cb: (result: String?) -> Unit,
|
||||
) {
|
||||
quickSetup(
|
||||
initParamsString,
|
||||
setupParamsString,
|
||||
object : InvokeInterface {
|
||||
override fun onResult(result: String?) {
|
||||
cb(result)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private external fun quickSetup(
|
||||
initParamsString: String,
|
||||
setupParamsString: String,
|
||||
cb: InvokeInterface
|
||||
)
|
||||
|
||||
external fun stopTun()
|
||||
|
||||
external fun getTraffic(onlyStatisticsProxy: Boolean): String
|
||||
|
||||
@@ -4,11 +4,13 @@ package com.follow.clash.service;
|
||||
import com.follow.clash.service.ICallbackInterface;
|
||||
import com.follow.clash.service.IEventInterface;
|
||||
import com.follow.clash.service.IResultInterface;
|
||||
import com.follow.clash.service.IVoidInterface;
|
||||
import com.follow.clash.service.models.VpnOptions;
|
||||
import com.follow.clash.service.models.NotificationParams;
|
||||
|
||||
interface IRemoteInterface {
|
||||
void invokeAction(in String data, in ICallbackInterface callback);
|
||||
void quickSetup(in String initParamsString, in String setupParamsString, in ICallbackInterface callback, in IVoidInterface onStarted);
|
||||
void updateNotificationParams(in NotificationParams params);
|
||||
void startService(in VpnOptions options, in long runTime, in IResultInterface result);
|
||||
void stopService(in IResultInterface result);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// IVoidInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface IVoidInterface {
|
||||
oneway void invoke();
|
||||
}
|
||||
@@ -98,6 +98,35 @@ class RemoteService : Service(),
|
||||
}
|
||||
}
|
||||
|
||||
override fun quickSetup(
|
||||
initParamsString: String,
|
||||
setupParamsString: String,
|
||||
callback: ICallbackInterface,
|
||||
onStarted: IVoidInterface
|
||||
) {
|
||||
Core.quickSetup(initParamsString, setupParamsString) {
|
||||
launch {
|
||||
runCatching {
|
||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||
for ((index, chunk) in chunks.withIndex()) {
|
||||
suspendCancellableCoroutine { cont ->
|
||||
callback.onResult(
|
||||
chunk,
|
||||
index == chunks.lastIndex,
|
||||
object : IAckInterface.Stub() {
|
||||
override fun onAck() {
|
||||
cont.resume(Unit)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onStarted()
|
||||
}
|
||||
|
||||
override fun updateNotificationParams(params: NotificationParams?) {
|
||||
State.notificationParamsFlow.tryEmit(params)
|
||||
}
|
||||
@@ -108,6 +137,7 @@ class RemoteService : Service(),
|
||||
runtime: Long,
|
||||
result: IResultInterface,
|
||||
) {
|
||||
GlobalState.log("remote startService")
|
||||
State.options = options
|
||||
handleStartService(runtime, result)
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ class VpnService : SystemVpnService(), IBaseService,
|
||||
addDnsServer(DNS6)
|
||||
}
|
||||
setMtu(9000)
|
||||
options.accessControl.let { accessControl ->
|
||||
options.accessControlProps.let { accessControl ->
|
||||
if (accessControl.enable) {
|
||||
when (accessControl.mode) {
|
||||
AccessControlMode.ACCEPT_SELECTED -> {
|
||||
|
||||
@@ -6,7 +6,7 @@ import kotlinx.parcelize.Parcelize
|
||||
import java.net.InetAddress
|
||||
|
||||
@Parcelize
|
||||
data class AccessControl(
|
||||
data class AccessControlProps(
|
||||
val enable: Boolean,
|
||||
val mode: AccessControlMode,
|
||||
val acceptList: List<String>,
|
||||
@@ -19,7 +19,7 @@ data class VpnOptions(
|
||||
val port: Int,
|
||||
val ipv6: Boolean,
|
||||
val dnsHijacking: Boolean,
|
||||
val accessControl: AccessControl,
|
||||
val accessControlProps: AccessControlProps,
|
||||
val allowBypass: Boolean,
|
||||
val systemProxy: Boolean,
|
||||
val bypassDomain: List<String>,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
pluginManagement {
|
||||
val flutterSdkPath = run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
val flutterSdkPath =
|
||||
run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
|
||||
@@ -129,14 +129,8 @@
|
||||
"compatibleDesc": "Opening it will lose part of its application ability and gain the support of full amount of Clash.",
|
||||
"notSelectedTip": "The current proxy group cannot be selected.",
|
||||
"tip": "tip",
|
||||
"backupAndRecovery": "Backup and Recovery",
|
||||
"backupAndRecoveryDesc": "Sync data via WebDAV or file",
|
||||
"account": "Account",
|
||||
"backup": "Backup",
|
||||
"recovery": "Recovery",
|
||||
"recoveryProfiles": "Only recovery profiles",
|
||||
"recoveryAll": "Recovery all data",
|
||||
"recoverySuccess": "Recovery success",
|
||||
"backupSuccess": "Backup success",
|
||||
"noInfo": "No info",
|
||||
"pleaseBindWebDAV": "Please bind WebDAV",
|
||||
@@ -219,9 +213,7 @@
|
||||
"local": "Local",
|
||||
"remote": "Remote",
|
||||
"remoteBackupDesc": "Backup local data to WebDAV",
|
||||
"remoteRecoveryDesc": "Recovery data from WebDAV",
|
||||
"localBackupDesc": "Backup local data to local",
|
||||
"localRecoveryDesc": "Recovery data from file",
|
||||
"mode": "Mode",
|
||||
"time": "Time",
|
||||
"source": "Source",
|
||||
@@ -381,9 +373,9 @@
|
||||
"systemApp": "System APP",
|
||||
"noNetworkApp": "No network APP",
|
||||
"contactMe": "Contact me",
|
||||
"recoveryStrategy": "Recovery strategy",
|
||||
"recoveryStrategy_override": "Override",
|
||||
"recoveryStrategy_compatible": "Compatible",
|
||||
"restoreStrategy": "Restore strategy",
|
||||
"restoreStrategy_override": "Override",
|
||||
"restoreStrategy_compatible": "Compatible",
|
||||
"logsTest": "Logs test",
|
||||
"emptyTip": "{label} cannot be empty",
|
||||
"urlTip": "{label} must be a url",
|
||||
@@ -466,5 +458,22 @@
|
||||
"vpnConfigChangeDetected": "VPN configuration change detected",
|
||||
"restart": "Restart",
|
||||
"speedStatistics": "Speed statistics",
|
||||
"resetPageChangesTip": "The current page has changes. Are you sure you want to reset?"
|
||||
"resetPageChangesTip": "The current page has changes. Are you sure you want to reset?",
|
||||
"overwriteTypeCustom": "Custom",
|
||||
"overwriteTypeCustomDesc": "Custom mode, fully customize proxy groups and rules",
|
||||
"unknownNetworkError": "Unknown network error",
|
||||
"networkRequestException": "Network request exception, please try again later.",
|
||||
"restoreException": "Recovery exception",
|
||||
"networkException": "Network exception, please check your connection and try again",
|
||||
"invalidBackupFile": "Invalid backup file",
|
||||
"pruneCache": "Prune cache",
|
||||
"backupAndRestore": "Backup and Restore",
|
||||
"backupAndRestoreDesc": "Sync data via WebDAV or files",
|
||||
"restore": "Restore",
|
||||
"restoreSuccess": "Restore success",
|
||||
"restoreFromWebDAVDesc": "Restore data via WebDAV",
|
||||
"restoreFromFileDesc": "Restore data via file",
|
||||
"restoreOnlyConfig": "Restore configuration files only",
|
||||
"restoreAllData": "Restore all data",
|
||||
"addProfile": "Add Profile"
|
||||
}
|
||||
@@ -129,14 +129,8 @@
|
||||
"compatibleDesc": "有効化すると一部機能を失いますが、Clashの完全サポートを獲得",
|
||||
"notSelectedTip": "現在のプロキシグループは選択できません",
|
||||
"tip": "ヒント",
|
||||
"backupAndRecovery": "バックアップと復元",
|
||||
"backupAndRecoveryDesc": "WebDAVまたはファイルでデータを同期",
|
||||
"account": "アカウント",
|
||||
"backup": "バックアップ",
|
||||
"recovery": "復元",
|
||||
"recoveryProfiles": "プロファイルのみ復元",
|
||||
"recoveryAll": "全データ復元",
|
||||
"recoverySuccess": "復元成功",
|
||||
"backupSuccess": "バックアップ成功",
|
||||
"noInfo": "情報なし",
|
||||
"pleaseBindWebDAV": "WebDAVをバインドしてください",
|
||||
@@ -219,9 +213,7 @@
|
||||
"local": "ローカル",
|
||||
"remote": "リモート",
|
||||
"remoteBackupDesc": "WebDAVにデータをバックアップ",
|
||||
"remoteRecoveryDesc": "WebDAVからデータを復元",
|
||||
"localBackupDesc": "ローカルにデータをバックアップ",
|
||||
"localRecoveryDesc": "ファイルからデータを復元",
|
||||
"mode": "モード",
|
||||
"time": "時間",
|
||||
"source": "ソース",
|
||||
@@ -382,9 +374,9 @@
|
||||
"systemApp": "システムアプリ",
|
||||
"noNetworkApp": "ネットワークなしアプリ",
|
||||
"contactMe": "連絡する",
|
||||
"recoveryStrategy": "リカバリー戦略",
|
||||
"recoveryStrategy_override": "オーバーライド",
|
||||
"recoveryStrategy_compatible": "互換性",
|
||||
"restoreStrategy": "復元ストラテジー",
|
||||
"restoreStrategy_override": "上書き",
|
||||
"restoreStrategy_compatible": "互換",
|
||||
"logsTest": "ログテスト",
|
||||
"emptyTip": "{label}は空欄にできません",
|
||||
"urlTip": "{label}はURLである必要があります",
|
||||
@@ -467,5 +459,22 @@
|
||||
"vpnConfigChangeDetected": "VPN設定の変更が検出されました",
|
||||
"restart": "再起動",
|
||||
"speedStatistics": "速度統計",
|
||||
"resetPageChangesTip": "現在のページに変更があります。リセットしてもよろしいですか?"
|
||||
"resetPageChangesTip": "現在のページに変更があります。リセットしてもよろしいですか?",
|
||||
"overwriteTypeCustom": "カスタム",
|
||||
"overwriteTypeCustomDesc": "カスタムモード、プロキシグループとルールを完全にカスタマイズ可能",
|
||||
"unknownNetworkError": "不明なネットワークエラー",
|
||||
"networkRequestException": "ネットワーク要求例外、後でもう一度試してください。",
|
||||
"restoreException": "復元例外",
|
||||
"networkException": "ネットワーク例外、接続を確認してもう一度お試しください",
|
||||
"invalidBackupFile": "無効なバックアップファイル",
|
||||
"pruneCache": "キャッシュの削除",
|
||||
"backupAndRestore": "バックアップと復元",
|
||||
"backupAndRestoreDesc": "WebDAVまたはファイルを介してデータを同期する",
|
||||
"restore": "復元",
|
||||
"restoreSuccess": "復元に成功しました",
|
||||
"restoreFromWebDAVDesc": "WebDAVを介してデータを復元する",
|
||||
"restoreFromFileDesc": "ファイルを介してデータを復元する",
|
||||
"restoreOnlyConfig": "設定ファイルのみを復元する",
|
||||
"restoreAllData": "すべてのデータを復元する",
|
||||
"addProfile": "プロファイルを追加"
|
||||
}
|
||||
@@ -382,9 +382,9 @@
|
||||
"systemApp": "Системное приложение",
|
||||
"noNetworkApp": "Приложение без сети",
|
||||
"contactMe": "Свяжитесь со мной",
|
||||
"recoveryStrategy": "Стратегия восстановления",
|
||||
"recoveryStrategy_override": "Переопределение",
|
||||
"recoveryStrategy_compatible": "Совместимый",
|
||||
"restoreStrategy": "Стратегия восстановления",
|
||||
"restoreStrategy_override": "Перезаписать",
|
||||
"restoreStrategy_compatible": "Совместимый",
|
||||
"logsTest": "Тест журналов",
|
||||
"emptyTip": "{label} не может быть пустым",
|
||||
"urlTip": "{label} должен быть URL",
|
||||
@@ -467,5 +467,22 @@
|
||||
"vpnConfigChangeDetected": "Обнаружено изменение конфигурации VPN",
|
||||
"restart": "Перезапустить",
|
||||
"speedStatistics": "Статистика скорости",
|
||||
"resetPageChangesTip": "На текущей странице есть изменения. Вы уверены, что хотите сбросить?"
|
||||
"resetPageChangesTip": "На текущей странице есть изменения. Вы уверены, что хотите сбросить?",
|
||||
"overwriteTypeCustom": "Пользовательский",
|
||||
"overwriteTypeCustomDesc": "Пользовательский режим, полная настройка групп прокси и правил",
|
||||
"unknownNetworkError": "Неизвестная сетевая ошибка",
|
||||
"networkRequestException": "Исключение сетевого запроса, пожалуйста, попробуйте позже.",
|
||||
"restoreException": "Ошибка восстановления",
|
||||
"networkException": "Ошибка сети, проверьте соединение и попробуйте еще раз",
|
||||
"invalidBackupFile": "Неверный файл резервной копии",
|
||||
"pruneCache": "Очистить кэш",
|
||||
"backupAndRestore": "Резервное копирование и восстановление",
|
||||
"backupAndRestoreDesc": "Синхронизация данных через WebDAV или файлы",
|
||||
"restore": "Восстановить",
|
||||
"restoreSuccess": "Восстановление успешно",
|
||||
"restoreFromWebDAVDesc": "Восстановить данные через WebDAV",
|
||||
"restoreFromFileDesc": "Восстановить данные из файла",
|
||||
"restoreOnlyConfig": "Восстановить только файлы конфигурации",
|
||||
"restoreAllData": "Восстановить все данные",
|
||||
"addProfile": "Добавить профиль"
|
||||
}
|
||||
@@ -129,14 +129,8 @@
|
||||
"compatibleDesc": "开启将失去部分应用能力,获得全量的Clash的支持",
|
||||
"notSelectedTip": "当前代理组无法选中",
|
||||
"tip": "提示",
|
||||
"backupAndRecovery": "备份与恢复",
|
||||
"backupAndRecoveryDesc": "通过WebDAV或者文件同步数据",
|
||||
"account": "账号",
|
||||
"backup": "备份",
|
||||
"recovery": "恢复",
|
||||
"recoveryProfiles": "仅恢复配置文件",
|
||||
"recoveryAll": "恢复所有数据",
|
||||
"recoverySuccess": "恢复成功",
|
||||
"backupSuccess": "备份成功",
|
||||
"noInfo": "暂无信息",
|
||||
"pleaseBindWebDAV": "请绑定WebDAV",
|
||||
@@ -219,9 +213,7 @@
|
||||
"local": "本地",
|
||||
"remote": "远程",
|
||||
"remoteBackupDesc": "备份数据到WebDAV",
|
||||
"remoteRecoveryDesc": "通过WebDAV恢复数据",
|
||||
"localBackupDesc": "备份数据到本地",
|
||||
"localRecoveryDesc": "通过文件恢复数据",
|
||||
"mode": "模式",
|
||||
"time": "时间",
|
||||
"source": "来源",
|
||||
@@ -382,9 +374,9 @@
|
||||
"systemApp": "系统应用",
|
||||
"noNetworkApp": "无网络应用",
|
||||
"contactMe": "联系我",
|
||||
"recoveryStrategy": "恢复策略",
|
||||
"recoveryStrategy_override": "覆盖",
|
||||
"recoveryStrategy_compatible": "兼容",
|
||||
"restoreStrategy": "恢复策略",
|
||||
"restoreStrategy_override": "覆盖",
|
||||
"restoreStrategy_compatible": "兼容",
|
||||
"logsTest": "日志测试",
|
||||
"emptyTip": "{label}不能为空",
|
||||
"urlTip": "{label}必须为URL",
|
||||
@@ -467,5 +459,22 @@
|
||||
"vpnConfigChangeDetected": "检测到VPN相关配置改动",
|
||||
"restart": "重启",
|
||||
"speedStatistics": "网速统计",
|
||||
"resetPageChangesTip": "当前页面存在更改,确定重置吗?"
|
||||
"resetPageChangesTip": "当前页面存在更改,确定重置吗?",
|
||||
"overwriteTypeCustom": "自定义",
|
||||
"overwriteTypeCustomDesc": "自定义模式,支持完全自定义修改代理组以及规则",
|
||||
"unknownNetworkError": "未知网络错误",
|
||||
"networkRequestException": "网络请求异常,请稍后再试。",
|
||||
"restoreException": "恢复异常",
|
||||
"networkException": "网络异常,请检查连接后重试",
|
||||
"invalidBackupFile": "无效备份文件",
|
||||
"pruneCache": "修剪缓存",
|
||||
"backupAndRestore": "备份与恢复",
|
||||
"backupAndRestoreDesc": "通过WebDAV或者文件同步数据",
|
||||
"restore": "恢复",
|
||||
"restoreSuccess": "恢复成功",
|
||||
"restoreFromWebDAVDesc": "通过WebDAV恢复数据",
|
||||
"restoreFromFileDesc": "通过文件恢复数据",
|
||||
"restoreOnlyConfig": "仅恢复配置文件",
|
||||
"restoreAllData": "恢复所有数据",
|
||||
"addProfile": "添加配置"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ targets:
|
||||
build_extensions:
|
||||
'^lib/models/{{}}.dart': 'lib/models/generated/{{}}.g.dart'
|
||||
'^lib/providers/{{}}.dart': 'lib/providers/generated/{{}}.g.dart'
|
||||
'^lib/database/{{}}.dart': 'lib/database/generated/{{}}.g.dart'
|
||||
freezed:
|
||||
options:
|
||||
build_extensions:
|
||||
|
||||
Submodule core/Clash.Meta updated: e0cf7fb4e6...c27f82fbdf
@@ -37,12 +37,6 @@ var (
|
||||
mBatch, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
||||
)
|
||||
|
||||
type ExternalProviders []ExternalProvider
|
||||
|
||||
func (a ExternalProviders) Len() int { return len(a) }
|
||||
func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
||||
func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
func getExternalProvidersRaw() map[string]cp.Provider {
|
||||
eps := make(map[string]cp.Provider)
|
||||
for n, p := range tunnel.Providers() {
|
||||
@@ -241,25 +235,8 @@ func updateConfig(params *UpdateParams) {
|
||||
updateListeners()
|
||||
}
|
||||
|
||||
func parseWithPath(path string) (*config.Config, error) {
|
||||
buf, err := readFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawConfig := config.DefaultRawConfig()
|
||||
err = UnmarshalJson(buf, rawConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parseRawConfig, err := config.ParseRawConfig(rawConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parseRawConfig, nil
|
||||
}
|
||||
|
||||
func setupConfig(params *SetupParams) error {
|
||||
func applyConfig(params *SetupParams) error {
|
||||
runtime.GC()
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
var err error
|
||||
@@ -271,7 +248,6 @@ func setupConfig(params *SetupParams) error {
|
||||
hub.ApplyConfig(currentConfig)
|
||||
patchSelectGroup(params.SelectedMap)
|
||||
updateListeners()
|
||||
runtime.GC()
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,11 @@ type ExternalProvider struct {
|
||||
SubscriptionInfo *provider.SubscriptionInfo `json:"subscription-info"`
|
||||
}
|
||||
|
||||
type ProxiesData struct {
|
||||
Proxies map[string]constant.Proxy `json:"proxies"`
|
||||
All []string `json:"all"`
|
||||
}
|
||||
|
||||
const (
|
||||
messageMethod Method = "message"
|
||||
initClashMethod Method = "initClash"
|
||||
|
||||
38
core/go.mod
38
core/go.mod
@@ -10,6 +10,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/RyuaNerin/go-krypto v1.3.0 // indirect
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
@@ -18,18 +19,14 @@ require (
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/coreos/go-iptables v0.8.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/enfein/mieru/v3 v3.22.1 // indirect
|
||||
github.com/enfein/mieru/v3 v3.26.2 // indirect
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.3 // indirect
|
||||
github.com/go-chi/render v1.0.3 // 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/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.4.0 // indirect
|
||||
@@ -37,7 +34,7 @@ require (
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
@@ -52,37 +49,42 @@ require (
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b // indirect
|
||||
github.com/metacubex/blake3 v0.1.0 // indirect
|
||||
github.com/metacubex/chacha v0.1.5 // indirect
|
||||
github.com/metacubex/chi v0.1.0 // indirect
|
||||
github.com/metacubex/cpu v0.1.0 // indirect
|
||||
github.com/metacubex/fswatch v0.1.1 // indirect
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect
|
||||
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 // indirect
|
||||
github.com/metacubex/hkdf v0.1.0 // indirect
|
||||
github.com/metacubex/hpke v0.1.0 // indirect
|
||||
github.com/metacubex/http v0.1.0 // indirect
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 // indirect
|
||||
github.com/metacubex/mlkem v0.1.0 // indirect
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
|
||||
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 // indirect
|
||||
github.com/metacubex/qpack v0.6.0 // indirect
|
||||
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001 // indirect
|
||||
github.com/metacubex/randv2 v0.2.0 // indirect
|
||||
github.com/metacubex/restls-client-go v0.1.7 // indirect
|
||||
github.com/metacubex/sing v0.5.6 // indirect
|
||||
github.com/metacubex/sing-mux v0.3.4 // indirect
|
||||
github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb // indirect
|
||||
github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.9 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.11 // indirect
|
||||
github.com/metacubex/sing-vmess v0.2.4 // indirect
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
|
||||
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 // indirect
|
||||
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 // indirect
|
||||
github.com/metacubex/utls v1.8.3 // indirect
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 // indirect
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // indirect
|
||||
github.com/metacubex/tls v0.1.1 // indirect
|
||||
github.com/metacubex/utls v1.8.4 // indirect
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect
|
||||
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
|
||||
github.com/miekg/dns v1.1.63 // indirect
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/openacid/low v0.1.21 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/sagernet/cors v1.2.1 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||
github.com/samber/lo v1.52.0 // indirect
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
||||
@@ -103,7 +105,7 @@ require (
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
93
core/go.sum
93
core/go.sum
@@ -1,3 +1,5 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg=
|
||||
github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
|
||||
github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss=
|
||||
@@ -12,9 +14,6 @@ github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xW
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
|
||||
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -22,10 +21,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/enfein/mieru/v3 v3.22.1 h1:/XGYYXpEhEJlxosmtbpEJkhtRLHB8IToG7LB8kU2ZDY=
|
||||
github.com/enfein/mieru/v3 v3.22.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
github.com/enfein/mieru/v3 v3.26.2 h1:U/2XJc+3vrJD9r815FoFdwToQFEcqSOzzzWIPPhjfEU=
|
||||
github.com/enfein/mieru/v3 v3.26.2/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||
@@ -39,15 +36,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
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-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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
@@ -57,17 +47,15 @@ github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakr
|
||||
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
||||
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
@@ -98,47 +86,62 @@ github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cq
|
||||
github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk=
|
||||
github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M=
|
||||
github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/chi v0.1.0 h1:rjNDyDj50nRpicG43CNkIw4ssiCbmDL8d7wJXKlUCsg=
|
||||
github.com/metacubex/chi v0.1.0/go.mod h1:zM5u5oMQt8b2DjvDHvzadKrP6B2ztmasL1YHRMbVV+g=
|
||||
github.com/metacubex/cpu v0.1.0 h1:8PeTdV9j6UKbN1K5Jvtbi/Jock7dknvzyYuLb8Conmk=
|
||||
github.com/metacubex/cpu v0.1.0/go.mod h1:09VEt4dSRLR+bOA8l4w4NDuzGZ8n5dkMv7e8axgEeTU=
|
||||
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
|
||||
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ=
|
||||
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be h1:Y7SigZIqfv/+RIA/D7R6EbB9p+brPRoGOM6zobSmRIM=
|
||||
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
|
||||
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 h1:hUL81H0Ic/XIDkvtn9M1pmfDdfid7JzYQToY4Ps1TvQ=
|
||||
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||
github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
|
||||
github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4=
|
||||
github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
|
||||
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
|
||||
github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
|
||||
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
|
||||
github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I=
|
||||
github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ=
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
|
||||
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 h1:I1uvJl206/HbkzEAZpLgGkZgUveOZb+P+6oTUj7dN+o=
|
||||
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c=
|
||||
github.com/metacubex/qpack v0.6.0 h1:YqClGIMOpiRYLjV1qOs483Od08MdPgRnHjt90FuaAKw=
|
||||
github.com/metacubex/qpack v0.6.0/go.mod h1:lKGSi7Xk94IMvHGOmxS9eIei3bvIqpOAImEBsaOwTkA=
|
||||
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001 h1:RlT3bFCIDM/NR9GWaDbFCrweOwpHRfgaT9c0zuRlPhY=
|
||||
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001/go.mod h1:oNzMrmylS897M3zSMuapIdwSwfq6F2qW01Z3NhVRJhk=
|
||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
|
||||
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
|
||||
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing v0.5.6 h1:mEPDCadsCj3DB8gn+t/EtposlYuALEkExa/LUguw6/c=
|
||||
github.com/metacubex/sing v0.5.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0=
|
||||
github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4=
|
||||
github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb h1:gxrJmnxuEAel+kh3V7ntqkHjURif0xKDu76nzr/BF5Y=
|
||||
github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb/go.mod h1:JK4+PYUKps6pnlicKjsSUAjAcvIUjhorIjdNZGg930M=
|
||||
github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e h1:MLxp42z9Jd6LtY2suyawnl24oNzIsFxWc15bNeDIGxA=
|
||||
github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e/go.mod h1:+lgKTd52xAarGtqugALISShyw4KxnoEpYe2u0zJh26w=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||
github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU=
|
||||
github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-tun v0.4.11 h1:NG5zpvYPbBXf+9GSUmDaGCDwl3hZXV677tbRAw0QtCM=
|
||||
github.com/metacubex/sing-tun v0.4.11/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I=
|
||||
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
|
||||
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM=
|
||||
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4=
|
||||
github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2BhiAPgbJygiWhesPlfGmF+9Vw6ARdk=
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/tls v0.1.1 h1:BEcZrsPTTfNf4sKZ02EbZodv4UIj7fgHWa1Eqo12Bc0=
|
||||
github.com/metacubex/tls v0.1.1/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
|
||||
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
|
||||
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E=
|
||||
@@ -149,9 +152,6 @@ github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd
|
||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
|
||||
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/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
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/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
|
||||
@@ -164,10 +164,6 @@ github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
@@ -181,16 +177,9 @@ github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
@@ -234,22 +223,20 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
|
||||
67
core/hub.go
67
core/hub.go
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
@@ -19,11 +20,11 @@ import (
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||
"golang.org/x/exp/slices"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
@@ -43,10 +44,8 @@ func handleInitClash(paramsString string) bool {
|
||||
return false
|
||||
}
|
||||
version = params.Version
|
||||
if !isInit {
|
||||
constant.SetHomeDir(params.HomeDir)
|
||||
isInit = true
|
||||
}
|
||||
constant.SetHomeDir(params.HomeDir)
|
||||
isInit = true
|
||||
return isInit
|
||||
}
|
||||
|
||||
@@ -97,10 +96,52 @@ func handleValidateConfig(path string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func handleGetProxies() map[string]constant.Proxy {
|
||||
func handleGetProxies() ProxiesData {
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
return tunnel.ProxiesWithProviders()
|
||||
|
||||
nameList := config.GetProxyNameList()
|
||||
|
||||
proxies := make(map[string]constant.Proxy)
|
||||
|
||||
for name, proxy := range tunnel.Proxies() {
|
||||
proxies[name] = proxy
|
||||
}
|
||||
for _, p := range tunnel.Providers() {
|
||||
for _, proxy := range p.Proxies() {
|
||||
proxies[proxy.Name()] = proxy
|
||||
}
|
||||
}
|
||||
|
||||
hasGlobal := false
|
||||
allNames := make([]string, 0, len(nameList)+1)
|
||||
|
||||
for _, name := range nameList {
|
||||
if name == "GLOBAL" {
|
||||
hasGlobal = true
|
||||
}
|
||||
|
||||
p, ok := proxies[name]
|
||||
if !ok || p == nil {
|
||||
continue
|
||||
}
|
||||
switch p.Type() {
|
||||
case constant.Selector, constant.URLTest, constant.Fallback, constant.Relay, constant.LoadBalance:
|
||||
allNames = append(allNames, name)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if !hasGlobal {
|
||||
if p, ok := proxies["GLOBAL"]; ok && p != nil {
|
||||
allNames = append([]string{"GLOBAL"}, allNames...)
|
||||
}
|
||||
}
|
||||
|
||||
return ProxiesData{
|
||||
All: allNames,
|
||||
Proxies: proxies,
|
||||
}
|
||||
}
|
||||
|
||||
func handleChangeProxy(data string, fn func(string string)) {
|
||||
@@ -143,7 +184,7 @@ func handleChangeProxy(data string, fn func(string string)) {
|
||||
}
|
||||
|
||||
func handleGetTraffic(onlyStatisticsProxy bool) string {
|
||||
up, down := statistic.DefaultManager.Current(onlyStatisticsProxy)
|
||||
up, down := statistic.DefaultManager.NowTraffic(onlyStatisticsProxy)
|
||||
traffic := map[string]int64{
|
||||
"up": up,
|
||||
"down": down,
|
||||
@@ -157,7 +198,7 @@ func handleGetTraffic(onlyStatisticsProxy bool) string {
|
||||
}
|
||||
|
||||
func handleGetTotalTraffic(onlyStatisticsProxy bool) string {
|
||||
up, down := statistic.DefaultManager.Total(onlyStatisticsProxy)
|
||||
up, down := statistic.DefaultManager.TotalTraffic(onlyStatisticsProxy)
|
||||
traffic := map[string]int64{
|
||||
"up": up,
|
||||
"down": down,
|
||||
@@ -287,7 +328,9 @@ func handleGetExternalProviders() string {
|
||||
}
|
||||
eps = append(eps, *externalProvider)
|
||||
}
|
||||
sort.Sort(ExternalProviders(eps))
|
||||
slices.SortFunc(eps, func(a, b ExternalProvider) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
data, err := json.Marshal(eps)
|
||||
if err != nil {
|
||||
return ""
|
||||
@@ -493,10 +536,10 @@ func handleSetupConfig(bytes []byte) string {
|
||||
err := UnmarshalJson(bytes, params)
|
||||
if err != nil {
|
||||
log.Errorln("unmarshalRawConfig error %v", err)
|
||||
_ = setupConfig(defaultSetupParams())
|
||||
_ = applyConfig(defaultSetupParams())
|
||||
return err.Error()
|
||||
}
|
||||
err = setupConfig(params)
|
||||
err = applyConfig(params)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
23
core/lib.go
23
core/lib.go
@@ -201,9 +201,29 @@ func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
|
||||
//export startTUN
|
||||
func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool {
|
||||
handleStartTun(callback, int(fd), takeCString(stackChar), takeCString(addressChar), takeCString(dnsChar))
|
||||
if !isRunning {
|
||||
handleStartListener()
|
||||
} else {
|
||||
handleResetConnections()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//export quickSetup
|
||||
func quickSetup(callback unsafe.Pointer, initParamsChar *C.char, setupParamsChar *C.char) {
|
||||
go func() {
|
||||
initParamsString := takeCString(initParamsChar)
|
||||
setupParamsString := takeCString(setupParamsChar)
|
||||
if !handleInitClash(initParamsString) {
|
||||
invokeResult(callback, "init failed")
|
||||
return
|
||||
}
|
||||
isRunning = true
|
||||
message := handleSetupConfig([]byte(setupParamsString))
|
||||
invokeResult(callback, message)
|
||||
}()
|
||||
}
|
||||
|
||||
//export setEventListener
|
||||
func setEventListener(listener unsafe.Pointer) {
|
||||
if eventListener != nil || listener == nil {
|
||||
@@ -241,6 +261,9 @@ func sendMessage(message Message) {
|
||||
//export stopTun
|
||||
func stopTun() {
|
||||
handleStopTun()
|
||||
if isRunning {
|
||||
handleStopListener()
|
||||
}
|
||||
}
|
||||
|
||||
//export suspend
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
@@ -25,6 +26,7 @@ class Application extends ConsumerStatefulWidget {
|
||||
|
||||
class ApplicationState extends ConsumerState<Application> {
|
||||
Timer? _autoUpdateProfilesTaskTimer;
|
||||
bool _preHasVpn = false;
|
||||
|
||||
final _pageTransitionsTheme = const PageTransitionsTheme(
|
||||
builders: <TargetPlatform, PageTransitionsBuilder>{
|
||||
@@ -45,22 +47,22 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_autoUpdateProfilesTask();
|
||||
globalState.appController = AppController(context, ref);
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
final currentContext = globalState.navigatorKey.currentContext;
|
||||
if (currentContext != null) {
|
||||
globalState.appController = AppController(currentContext, ref);
|
||||
await appController.attach(currentContext, ref);
|
||||
} else {
|
||||
exit(0);
|
||||
}
|
||||
await globalState.appController.init();
|
||||
globalState.appController.initLink();
|
||||
_autoUpdateProfilesTask();
|
||||
appController.initLink();
|
||||
app?.initShortcuts();
|
||||
});
|
||||
}
|
||||
|
||||
void _autoUpdateProfilesTask() {
|
||||
_autoUpdateProfilesTaskTimer = Timer(const Duration(minutes: 20), () async {
|
||||
await globalState.appController.autoUpdateProfiles();
|
||||
await appController.autoUpdateProfiles();
|
||||
_autoUpdateProfilesTask();
|
||||
});
|
||||
}
|
||||
@@ -81,11 +83,13 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
child: CoreManager(
|
||||
child: ConnectivityManager(
|
||||
onConnectivityChanged: (results) async {
|
||||
if (!results.contains(ConnectivityResult.vpn)) {
|
||||
coreController.closeConnections();
|
||||
commonPrint.log('connectivityChanged ${results.toString()}');
|
||||
appController.updateLocalIp();
|
||||
final hasVpn = results.contains(ConnectivityResult.vpn);
|
||||
if (_preHasVpn == hasVpn) {
|
||||
appController.addCheckIp();
|
||||
}
|
||||
globalState.appController.updateLocalIp();
|
||||
globalState.appController.addCheckIpNumDebounce();
|
||||
_preHasVpn = hasVpn;
|
||||
},
|
||||
child: child,
|
||||
),
|
||||
@@ -163,8 +167,7 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
linkManager.destroy();
|
||||
_autoUpdateProfilesTaskTimer?.cancel();
|
||||
await coreController.destroy();
|
||||
await globalState.appController.savePreferences();
|
||||
await globalState.appController.handleExit();
|
||||
await appController.handleExit();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
@@ -18,14 +17,11 @@ extension ArchiveExt on Archive {
|
||||
final archiveFile = ArchiveFile(relativePath, data.length, data);
|
||||
addFile(archiveFile);
|
||||
}
|
||||
// else if (entity is Directory) {
|
||||
// addDirectoryToArchive(entity.path, parentPath);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void addTextFile<T>(String name, T raw) {
|
||||
final data = json.encode(raw);
|
||||
addFile(ArchiveFile.string(name, data));
|
||||
}
|
||||
// void addTextFile<T>(String name, T raw) {
|
||||
// final data = json.encode(raw);
|
||||
// addFile(ArchiveFile.string(name, data));
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -6,17 +6,21 @@ export 'constant.dart';
|
||||
export 'context.dart';
|
||||
export 'converter.dart';
|
||||
export 'datetime.dart';
|
||||
export 'file.dart';
|
||||
export 'fixed.dart';
|
||||
export 'function.dart';
|
||||
export 'future.dart';
|
||||
export 'hive.dart';
|
||||
export 'http.dart';
|
||||
export 'icons.dart';
|
||||
export 'indexing.dart';
|
||||
export 'iterable.dart';
|
||||
export 'keyboard.dart';
|
||||
export 'launch.dart';
|
||||
export 'link.dart';
|
||||
export 'lock.dart';
|
||||
export 'measure.dart';
|
||||
export 'migration.dart';
|
||||
export 'mixin.dart';
|
||||
export 'navigation.dart';
|
||||
export 'navigator.dart';
|
||||
@@ -32,8 +36,10 @@ export 'proxy.dart';
|
||||
export 'render.dart';
|
||||
export 'request.dart';
|
||||
export 'scroll.dart';
|
||||
export 'snowflake.dart';
|
||||
export 'string.dart';
|
||||
export 'system.dart';
|
||||
export 'task.dart';
|
||||
export 'text.dart';
|
||||
export 'tray.dart';
|
||||
export 'utils.dart';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
|
||||
import 'string.dart';
|
||||
|
||||
List<Group> computeSort({
|
||||
required List<Group> groups,
|
||||
required ProxiesSortType sortType,
|
||||
@@ -10,53 +9,54 @@ List<Group> computeSort({
|
||||
required Map<String, String> selectedMap,
|
||||
required String defaultTestUrl,
|
||||
}) {
|
||||
List<Proxy> sortOfDelay({
|
||||
required List<Group> groups,
|
||||
required List<Proxy> proxies,
|
||||
required DelayMap delayMap,
|
||||
required Map<String, String> selectedMap,
|
||||
required String testUrl,
|
||||
}) {
|
||||
return List.from(proxies)..sort((a, b) {
|
||||
final aDelayState = computeProxyDelayState(
|
||||
proxyName: a.name,
|
||||
testUrl: testUrl,
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
delayMap: delayMap,
|
||||
);
|
||||
final bDelayState = computeProxyDelayState(
|
||||
proxyName: b.name,
|
||||
testUrl: testUrl,
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
delayMap: delayMap,
|
||||
);
|
||||
return aDelayState.compareTo(bDelayState);
|
||||
});
|
||||
}
|
||||
|
||||
List<Proxy> sortOfName(List<Proxy> proxies) {
|
||||
return List.of(proxies)..sort((a, b) => a.name.compareTo(b.name));
|
||||
}
|
||||
|
||||
return groups.map((group) {
|
||||
final proxies = group.all;
|
||||
final newProxies = switch (sortType) {
|
||||
ProxiesSortType.none => proxies,
|
||||
ProxiesSortType.delay => _sortOfDelay(
|
||||
ProxiesSortType.delay => sortOfDelay(
|
||||
groups: groups,
|
||||
proxies: proxies,
|
||||
delayMap: delayMap,
|
||||
selectedMap: selectedMap,
|
||||
testUrl: group.testUrl.getSafeValue(defaultTestUrl),
|
||||
testUrl: group.testUrl.takeFirstValid([defaultTestUrl]),
|
||||
),
|
||||
ProxiesSortType.name => _sortOfName(proxies),
|
||||
ProxiesSortType.name => sortOfName(proxies),
|
||||
};
|
||||
return group.copyWith(all: newProxies);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
DelayState computeProxyDelayState({
|
||||
required String proxyName,
|
||||
required String testUrl,
|
||||
required List<Group> groups,
|
||||
required Map<String, String> selectedMap,
|
||||
required DelayMap delayMap,
|
||||
}) {
|
||||
final state = computeRealSelectedProxyState(
|
||||
proxyName,
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
);
|
||||
final currentDelayMap = delayMap[state.testUrl.getSafeValue(testUrl)] ?? {};
|
||||
final delay = currentDelayMap[state.proxyName];
|
||||
return DelayState(delay: delay ?? 0, group: state.group);
|
||||
}
|
||||
|
||||
SelectedProxyState computeRealSelectedProxyState(
|
||||
String proxyName, {
|
||||
required List<Group> groups,
|
||||
required Map<String, String> selectedMap,
|
||||
}) {
|
||||
return _getRealSelectedProxyState(
|
||||
SelectedProxyState(proxyName: proxyName),
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
);
|
||||
}
|
||||
|
||||
SelectedProxyState _getRealSelectedProxyState(
|
||||
SelectedProxyState getRealSelectedProxyState(
|
||||
SelectedProxyState state, {
|
||||
required List<Group> groups,
|
||||
required Map<String, String> selectedMap,
|
||||
@@ -72,39 +72,39 @@ SelectedProxyState _getRealSelectedProxyState(
|
||||
if (currentSelectedName.isEmpty) {
|
||||
return newState;
|
||||
}
|
||||
return _getRealSelectedProxyState(
|
||||
return getRealSelectedProxyState(
|
||||
newState.copyWith(proxyName: currentSelectedName, testUrl: group.testUrl),
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
);
|
||||
}
|
||||
|
||||
List<Proxy> _sortOfDelay({
|
||||
SelectedProxyState computeRealSelectedProxyState(
|
||||
String proxyName, {
|
||||
required List<Group> groups,
|
||||
required List<Proxy> proxies,
|
||||
required DelayMap delayMap,
|
||||
required Map<String, String> selectedMap,
|
||||
required String testUrl,
|
||||
}) {
|
||||
return List.from(proxies)..sort((a, b) {
|
||||
final aDelayState = computeProxyDelayState(
|
||||
proxyName: a.name,
|
||||
testUrl: testUrl,
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
delayMap: delayMap,
|
||||
);
|
||||
final bDelayState = computeProxyDelayState(
|
||||
proxyName: b.name,
|
||||
testUrl: testUrl,
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
delayMap: delayMap,
|
||||
);
|
||||
return aDelayState.compareTo(bDelayState);
|
||||
});
|
||||
return getRealSelectedProxyState(
|
||||
SelectedProxyState(proxyName: proxyName),
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
);
|
||||
}
|
||||
|
||||
List<Proxy> _sortOfName(List<Proxy> proxies) {
|
||||
return List.of(proxies)..sort((a, b) => a.name.compareTo(b.name));
|
||||
DelayState computeProxyDelayState({
|
||||
required String proxyName,
|
||||
required String testUrl,
|
||||
required List<Group> groups,
|
||||
required Map<String, String> selectedMap,
|
||||
required DelayMap delayMap,
|
||||
}) {
|
||||
final state = computeRealSelectedProxyState(
|
||||
proxyName,
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
);
|
||||
final currentDelayMap =
|
||||
delayMap[state.testUrl.takeFirstValid([testUrl])] ?? {};
|
||||
final delay = currentDelayMap[state.proxyName];
|
||||
return DelayState(delay: delay ?? 0, group: state.group);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ final listHeaderPadding = EdgeInsets.only(
|
||||
bottom: 8.ap,
|
||||
);
|
||||
|
||||
const watchExecution = true;
|
||||
|
||||
final defaultTextScaleFactor =
|
||||
WidgetsBinding.instance.platformDispatcher.textScaleFactor;
|
||||
const httpTimeoutDuration = Duration(milliseconds: 5000);
|
||||
@@ -63,6 +65,7 @@ final commonFilter = ImageFilter.blur(
|
||||
tileMode: TileMode.mirror,
|
||||
);
|
||||
|
||||
const listEquality = ListEquality();
|
||||
const navigationItemListEquality = ListEquality<NavigationItem>();
|
||||
const trackerInfoListEquality = ListEquality<TrackerInfo>();
|
||||
const stringListEquality = ListEquality<String>();
|
||||
@@ -70,15 +73,18 @@ const intListEquality = ListEquality<int>();
|
||||
const logListEquality = ListEquality<Log>();
|
||||
const groupListEquality = ListEquality<Group>();
|
||||
const ruleListEquality = ListEquality<Rule>();
|
||||
const scriptEquality = ListEquality<Script>();
|
||||
const scriptListEquality = ListEquality<Script>();
|
||||
const externalProviderListEquality = ListEquality<ExternalProvider>();
|
||||
const packageListEquality = ListEquality<Package>();
|
||||
const profileListEquality = ListEquality<Profile>();
|
||||
const hotKeyActionListEquality = ListEquality<HotKeyAction>();
|
||||
const stringAndStringMapEquality = MapEquality<String, String>();
|
||||
const stringAndStringMapEntryListEquality =
|
||||
ListEquality<MapEntry<String, String>>();
|
||||
const stringAndStringMapEntryIterableEquality =
|
||||
IterableEquality<MapEntry<String, String>>();
|
||||
const stringAndObjectMapEntryIterableEquality =
|
||||
IterableEquality<MapEntry<String, Object?>>();
|
||||
const delayMapEquality = MapEquality<String, Map<String, int?>>();
|
||||
const stringSetEquality = SetEquality<String>();
|
||||
const keyboardModifierListEquality = SetEquality<KeyboardModifier>();
|
||||
@@ -119,3 +125,6 @@ const scriptTemplate = '''
|
||||
const main = (config) => {
|
||||
return config;
|
||||
}''';
|
||||
|
||||
const backupDatabaseName = 'database.sqlite';
|
||||
const configJsonName = 'config.json';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:fl_clash/l10n/l10n.dart';
|
||||
import 'package:fl_clash/manager/manager.dart';
|
||||
import 'package:fl_clash/models/widget.dart';
|
||||
import 'package:fl_clash/models/state.dart';
|
||||
import 'package:fl_clash/widgets/scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
@@ -10,19 +9,10 @@ class DAVClient {
|
||||
Completer<bool> pingCompleter = Completer();
|
||||
late String fileName;
|
||||
|
||||
DAVClient(DAV dav) {
|
||||
client = newClient(
|
||||
dav.uri,
|
||||
user: dav.user,
|
||||
password: dav.password,
|
||||
);
|
||||
DAVClient(DAVProps dav) {
|
||||
client = newClient(dav.uri, user: dav.user, password: dav.password);
|
||||
fileName = dav.fileName;
|
||||
client.setHeaders(
|
||||
{
|
||||
'accept-charset': 'utf-8',
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
);
|
||||
client.setHeaders({'accept-charset': 'utf-8', 'Content-Type': 'text/xml'});
|
||||
client.setConnectTimeout(8000);
|
||||
client.setSendTimeout(60000);
|
||||
client.setReceiveTimeout(60000);
|
||||
@@ -42,15 +32,16 @@ class DAVClient {
|
||||
|
||||
String get backupFile => '$root/$fileName';
|
||||
|
||||
Future<bool> backup(Uint8List data) async {
|
||||
Future<bool> backup(String localFilePath) async {
|
||||
await client.mkdir(root);
|
||||
await client.write(backupFile, data);
|
||||
await client.writeFromFile(localFilePath, backupFile);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<List<int>> recovery() async {
|
||||
Future<bool> restore() async {
|
||||
await client.mkdir(root);
|
||||
final data = await client.read(backupFile);
|
||||
return data;
|
||||
final backupFilePath = await appPath.backupFilePath;
|
||||
await client.read2File(backupFile, backupFilePath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
38
lib/common/file.dart
Normal file
38
lib/common/file.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'dart:io';
|
||||
|
||||
extension FileExt on File {
|
||||
Future<void> safeCopy(String newPath) async {
|
||||
if (!await exists()) {
|
||||
await create(recursive: true);
|
||||
return;
|
||||
}
|
||||
final targetFile = File(newPath);
|
||||
if (!await targetFile.exists()) {
|
||||
await targetFile.create(recursive: true);
|
||||
}
|
||||
await copy(newPath);
|
||||
}
|
||||
|
||||
Future<File> safeWriteAsString(String str) async {
|
||||
if (!await exists()) {
|
||||
await create(recursive: true);
|
||||
}
|
||||
return await writeAsString(str);
|
||||
}
|
||||
|
||||
Future<File> safeWriteAsBytes(List<int> bytes) async {
|
||||
if (!await exists()) {
|
||||
await create(recursive: true);
|
||||
}
|
||||
return await writeAsBytes(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
extension FileSystemEntityExt on FileSystemEntity {
|
||||
Future<void> safeDelete({bool recursive = false}) async {
|
||||
if (!await exists()) {
|
||||
return;
|
||||
}
|
||||
await delete(recursive: recursive);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
|
||||
class FlClashHttpOverrides extends HttpOverrides {
|
||||
static String handleFindProxy(Uri url) {
|
||||
if ([localhost].contains(url.host)) {
|
||||
return 'DIRECT';
|
||||
}
|
||||
final port = globalState.config.patchClashConfig.mixedPort;
|
||||
final isStart = globalState.appState.runTime != null;
|
||||
final port = appController.config.patchClashConfig.mixedPort;
|
||||
final isStart = appController.isStart;
|
||||
commonPrint.log('find $url proxy:$isStart');
|
||||
if (!isStart) return 'DIRECT';
|
||||
return 'PROXY localhost:$port';
|
||||
|
||||
260
lib/common/indexing.dart
Normal file
260
lib/common/indexing.dart
Normal file
@@ -0,0 +1,260 @@
|
||||
import 'dart:math';
|
||||
|
||||
class Indexing {
|
||||
static const String digits =
|
||||
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
static const String integerZero = 'a0';
|
||||
static const String smallestInteger = 'A00000000000000000000000000';
|
||||
|
||||
static Indexing? _instance;
|
||||
|
||||
Indexing._internal();
|
||||
|
||||
factory Indexing() {
|
||||
_instance ??= Indexing._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
int _getIntegerLength(String head) {
|
||||
if (head.compareTo('a') >= 0 && head.compareTo('z') <= 0) {
|
||||
return head.codeUnitAt(0) - 'a'.codeUnitAt(0) + 2;
|
||||
} else if (head.compareTo('A') >= 0 && head.compareTo('Z') <= 0) {
|
||||
return 'Z'.codeUnitAt(0) - head.codeUnitAt(0) + 2;
|
||||
} else {
|
||||
throw Exception('Invalid order key head: $head');
|
||||
}
|
||||
}
|
||||
|
||||
bool _validateInteger(String integer) {
|
||||
if (integer.length != _getIntegerLength(integer[0])) {
|
||||
throw Exception('Invalid integer part of order key: $integer');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String? _incrementInteger(String x) {
|
||||
_validateInteger(x);
|
||||
String head = x[0];
|
||||
List<String> digs = x.substring(1).split('');
|
||||
|
||||
bool carry = true;
|
||||
|
||||
for (int i = digs.length - 1; carry && i >= 0; i--) {
|
||||
int d = digits.indexOf(digs[i]) + 1;
|
||||
if (d == digits.length) {
|
||||
digs[i] = '0';
|
||||
} else {
|
||||
digs[i] = digits[d];
|
||||
carry = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (carry) {
|
||||
if (head == 'Z') {
|
||||
return 'a0';
|
||||
}
|
||||
if (head == 'z') {
|
||||
return null;
|
||||
}
|
||||
String h = String.fromCharCode(head.codeUnitAt(0) + 1);
|
||||
if (h.compareTo('a') > 0) {
|
||||
digs.add('0');
|
||||
} else {
|
||||
digs.removeLast();
|
||||
}
|
||||
return h + digs.join('');
|
||||
} else {
|
||||
return head + digs.join('');
|
||||
}
|
||||
}
|
||||
|
||||
String? _decrementInteger(String x) {
|
||||
_validateInteger(x);
|
||||
String head = x[0];
|
||||
List<String> digs = x.substring(1).split('');
|
||||
|
||||
bool borrow = true;
|
||||
|
||||
for (int i = digs.length - 1; borrow && i >= 0; i--) {
|
||||
int d = digits.indexOf(digs[i]) - 1;
|
||||
if (d == -1) {
|
||||
digs[i] = digits[digits.length - 1];
|
||||
} else {
|
||||
digs[i] = digits[d];
|
||||
borrow = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (borrow) {
|
||||
if (head == 'a') {
|
||||
return 'Z${digits[digits.length - 1]}';
|
||||
}
|
||||
if (head == 'A') {
|
||||
return null;
|
||||
}
|
||||
String h = String.fromCharCode(head.codeUnitAt(0) - 1);
|
||||
if (h.compareTo('Z') < 0) {
|
||||
digs.add(digits[digits.length - 1]);
|
||||
} else {
|
||||
digs.removeLast();
|
||||
}
|
||||
return h + digs.join('');
|
||||
} else {
|
||||
return head + digs.join('');
|
||||
}
|
||||
}
|
||||
|
||||
String _midpoint(String a, String? b) {
|
||||
if (b != null && a.compareTo(b) >= 0) {
|
||||
throw Exception(
|
||||
'Second order key must be greater than the first: $a, $b',
|
||||
);
|
||||
}
|
||||
|
||||
if (a.isNotEmpty && a[a.length - 1] == '0' ||
|
||||
(b != null && b.isNotEmpty && b[b.length - 1] == '0')) {
|
||||
throw Exception('Trailing zeros are not allowed: $a, $b');
|
||||
}
|
||||
|
||||
if (b != null) {
|
||||
int n = 0;
|
||||
while ((n < a.length ? a[n] : '0') == b[n]) {
|
||||
n++;
|
||||
}
|
||||
|
||||
if (n > 0) {
|
||||
return b.substring(0, n) +
|
||||
_midpoint(
|
||||
a.substring(min(n, a.length)),
|
||||
b.substring(min(n, b.length)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
int digitA = (a.isNotEmpty) ? digits.indexOf(a[0]) : 0;
|
||||
int digitB = (b != null && b.isNotEmpty)
|
||||
? digits.indexOf(b[0])
|
||||
: digits.length;
|
||||
|
||||
if (digitB - digitA > 1) {
|
||||
int midDigit = (digitA + digitB + 1) ~/ 2;
|
||||
return digits[midDigit];
|
||||
} else {
|
||||
if (b != null && b.length > 1) {
|
||||
return b.substring(0, 1);
|
||||
} else {
|
||||
return digits[digitA] +
|
||||
_midpoint(a.isNotEmpty ? a.substring(1) : '', null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _getIntegerPart(String key) {
|
||||
int integerPartLength = _getIntegerLength(key[0]);
|
||||
if (integerPartLength > key.length) {
|
||||
throw Exception('Invalid order key: $key');
|
||||
}
|
||||
return key.substring(0, integerPartLength);
|
||||
}
|
||||
|
||||
bool _validateOrderKey(String key) {
|
||||
if (key == smallestInteger) {
|
||||
throw Exception('Invalid order key: $key');
|
||||
}
|
||||
|
||||
String i = _getIntegerPart(key);
|
||||
String f = key.substring(i.length);
|
||||
if (f.isNotEmpty && f[f.length - 1] == '0') {
|
||||
throw Exception('Invalid order key: $key');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String? generateKeyBetween(String? a, String? b) {
|
||||
if (a != null) {
|
||||
_validateOrderKey(a);
|
||||
}
|
||||
|
||||
if (b != null) {
|
||||
_validateOrderKey(b);
|
||||
}
|
||||
|
||||
if (a != null && b != null && a.compareTo(b) >= 0) {
|
||||
throw Exception(
|
||||
'Second order key must be greater than the first: $a, $b',
|
||||
);
|
||||
}
|
||||
|
||||
if (a == null && b == null) {
|
||||
return integerZero;
|
||||
}
|
||||
|
||||
if (a == null) {
|
||||
b = b!;
|
||||
String ib = _getIntegerPart(b);
|
||||
String fb = b.substring(ib.length);
|
||||
if (ib == smallestInteger) {
|
||||
return ib + _midpoint('', fb);
|
||||
}
|
||||
return ib.compareTo(b) < 0 ? ib : _decrementInteger(ib);
|
||||
}
|
||||
|
||||
if (b == null) {
|
||||
String ia = _getIntegerPart(a);
|
||||
String fa = a.substring(ia.length);
|
||||
String? i = _incrementInteger(ia);
|
||||
return i ?? ia + _midpoint(fa, null);
|
||||
}
|
||||
|
||||
String ia = _getIntegerPart(a);
|
||||
String fa = a.substring(ia.length);
|
||||
String ib = _getIntegerPart(b);
|
||||
String fb = b.substring(ib.length);
|
||||
|
||||
if (ia == ib) {
|
||||
return ia + _midpoint(fa, fb);
|
||||
}
|
||||
|
||||
String? i = _incrementInteger(ia);
|
||||
return (i == null || i.compareTo(b) < 0) ? i : ia + _midpoint(fa, null);
|
||||
}
|
||||
|
||||
List<String?> generateNKeysBetween(String? a, String? b, int n) {
|
||||
if (n <= 0) {
|
||||
return [];
|
||||
}
|
||||
if (n == 1) {
|
||||
return [generateKeyBetween(a, b)];
|
||||
}
|
||||
|
||||
if (b == null) {
|
||||
String? c = generateKeyBetween(a, b);
|
||||
List<String?> result = [c];
|
||||
for (int i = 1; i < n; i++) {
|
||||
c = generateKeyBetween(c, b);
|
||||
result.add(c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (a == null) {
|
||||
String? c = generateKeyBetween(a, b);
|
||||
List<String?> result = [c];
|
||||
for (int i = 1; i < n; i++) {
|
||||
c = generateKeyBetween(a, c);
|
||||
result.add(c);
|
||||
}
|
||||
return result.reversed.toList();
|
||||
}
|
||||
|
||||
int mid = n ~/ 2;
|
||||
String? c = generateKeyBetween(a, b);
|
||||
return generateNKeysBetween(a, c, mid)
|
||||
.followedBy([c])
|
||||
.followedBy(generateNKeysBetween(c, b, n - mid - 1))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
final indexing = Indexing();
|
||||
@@ -1,5 +1,5 @@
|
||||
extension IterableExt<T> on Iterable<T> {
|
||||
Iterable<T> separated(T separator) sync* {
|
||||
extension IterableExt<E> on Iterable<E> {
|
||||
Iterable<E> separated(E separator) sync* {
|
||||
final iterator = this.iterator;
|
||||
if (!iterator.moveNext()) return;
|
||||
|
||||
@@ -11,7 +11,7 @@ extension IterableExt<T> on Iterable<T> {
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<List<T>> chunks(int size) sync* {
|
||||
Iterable<List<E>> chunks(int size) sync* {
|
||||
if (length == 0) return;
|
||||
var iterator = this.iterator;
|
||||
while (iterator.moveNext()) {
|
||||
@@ -23,7 +23,7 @@ extension IterableExt<T> on Iterable<T> {
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<T> fill(int length, {required T Function(int count) filler}) sync* {
|
||||
Iterable<E> fill(int length, {required E Function(int count) filler}) sync* {
|
||||
int count = 0;
|
||||
for (var item in this) {
|
||||
yield item;
|
||||
@@ -36,7 +36,7 @@ extension IterableExt<T> on Iterable<T> {
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<T> takeLast({int count = 50}) {
|
||||
Iterable<E> takeLast({int count = 50}) {
|
||||
if (count <= 0) return Iterable.empty();
|
||||
return count >= length ? this : toList().skip(length - count);
|
||||
}
|
||||
@@ -78,16 +78,18 @@ extension ListExt<T> on List<T> {
|
||||
return sublist(start);
|
||||
}
|
||||
|
||||
T safeGet(int index) {
|
||||
if (length > index) return this[index];
|
||||
return last;
|
||||
T? safeGet(int index, {T? defaultValue}) {
|
||||
if (index < 0 || index >= length) {
|
||||
return defaultValue;
|
||||
}
|
||||
return this[index];
|
||||
}
|
||||
|
||||
T safeLast(T value) {
|
||||
T safeLast(T defaultValue) {
|
||||
if (isNotEmpty) {
|
||||
return last;
|
||||
}
|
||||
return value;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void addOrRemove(T value) {
|
||||
|
||||
53
lib/common/migration.dart
Normal file
53
lib/common/migration.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
|
||||
class Migration {
|
||||
static Migration? _instance;
|
||||
late int _oldVersion;
|
||||
|
||||
Migration._internal();
|
||||
|
||||
final currentVersion = 1;
|
||||
|
||||
factory Migration() {
|
||||
_instance ??= Migration._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
Future<Config> migrationIfNeeded(
|
||||
Map<String, Object?>? configMap, {
|
||||
required Future<Config> Function(MigrationData data) sync,
|
||||
}) async {
|
||||
_oldVersion = await preferences.getVersion();
|
||||
if (_oldVersion == currentVersion) {
|
||||
try {
|
||||
return Config.realFromJson(configMap);
|
||||
} catch (_) {
|
||||
final isV0 = configMap?['proxiesStyle'] != null;
|
||||
if (isV0) {
|
||||
_oldVersion = 0;
|
||||
} else {
|
||||
throw 'Local data is damaged. A reset is required to fix this issue.';
|
||||
}
|
||||
}
|
||||
}
|
||||
MigrationData data = MigrationData(configMap: configMap);
|
||||
if (_oldVersion == 0 && configMap != null) {
|
||||
final clashConfigMap = await preferences.getClashConfigMap();
|
||||
if (clashConfigMap != null) {
|
||||
configMap['patchClashConfig'] = clashConfigMap;
|
||||
await preferences.clearClashConfig();
|
||||
}
|
||||
data = await _oldToNow(configMap);
|
||||
}
|
||||
final res = await sync(data);
|
||||
await preferences.setVersion(currentVersion);
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<MigrationData> _oldToNow(Map<String, Object?> configMap) async {
|
||||
return await oldToNowTask(configMap);
|
||||
}
|
||||
}
|
||||
|
||||
final migration = Migration();
|
||||
@@ -2,17 +2,21 @@ import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
|
||||
T get value => state;
|
||||
|
||||
set value(T value) {
|
||||
if (ref.mounted) {
|
||||
state = value;
|
||||
} else {
|
||||
onUpdate(value);
|
||||
}
|
||||
state = value;
|
||||
}
|
||||
|
||||
bool equals(T previous, T next) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(previous, next) {
|
||||
final res = super.updateShouldNotify(previous, next);
|
||||
final res = !equals(previous, next)
|
||||
? super.updateShouldNotify(previous, next)
|
||||
: true;
|
||||
if (res) {
|
||||
onUpdate(next);
|
||||
}
|
||||
@@ -21,31 +25,19 @@ mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
|
||||
|
||||
void onUpdate(T value) {}
|
||||
|
||||
void update(T Function(T) builder) {
|
||||
final value = builder(state);
|
||||
this.value = value;
|
||||
void update(T? Function(T) builder) {
|
||||
final res = builder(value);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
value = res;
|
||||
}
|
||||
}
|
||||
|
||||
mixin AnyNotifierMixin<T> on AnyNotifier<T, T> {
|
||||
mixin AsyncNotifierMixin<T> on AnyNotifier<AsyncValue<T>, T> {
|
||||
T get value;
|
||||
|
||||
set value(T value) {
|
||||
if (ref.mounted) {
|
||||
state = value;
|
||||
} else {
|
||||
onUpdate(value);
|
||||
}
|
||||
state = AsyncData(value);
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(previous, next) {
|
||||
final res = super.updateShouldNotify(previous, next);
|
||||
if (res) {
|
||||
onUpdate(next);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void onUpdate(T value) {}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BaseNavigator {
|
||||
static Future<T?> push<T>(BuildContext context, Widget child) async {
|
||||
if (globalState.appState.viewMode != ViewMode.mobile) {
|
||||
if (!appController.isMobile) {
|
||||
return await Navigator.of(
|
||||
context,
|
||||
).push<T>(CommonDesktopRoute(builder: (context) => child));
|
||||
|
||||
@@ -61,19 +61,39 @@ class AppPath {
|
||||
return directory.path;
|
||||
}
|
||||
|
||||
Future<String> get databasePath async {
|
||||
final mHomeDirPath = await homeDirPath;
|
||||
return join(mHomeDirPath, 'database.sqlite');
|
||||
}
|
||||
|
||||
Future<String> get backupFilePath async {
|
||||
final mHomeDirPath = await homeDirPath;
|
||||
return join(mHomeDirPath, 'backup.zip');
|
||||
}
|
||||
|
||||
Future<String> get restoreDirPath async {
|
||||
final mHomeDirPath = await homeDirPath;
|
||||
return join(mHomeDirPath, 'restore');
|
||||
}
|
||||
|
||||
Future<String> get tempFilePath async {
|
||||
final mTempDir = await tempDir.future;
|
||||
return join(mTempDir.path, 'temp${utils.id}');
|
||||
}
|
||||
|
||||
Future<String> get lockFilePath async {
|
||||
final homeDirPath = await appPath.homeDirPath;
|
||||
return join(homeDirPath, 'FlClash.lock');
|
||||
}
|
||||
|
||||
Future<String> get configFilePath async {
|
||||
final homeDirPath = await appPath.homeDirPath;
|
||||
return join(homeDirPath, 'config.yaml');
|
||||
final mHomeDirPath = await homeDirPath;
|
||||
return join(mHomeDirPath, 'config.yaml');
|
||||
}
|
||||
|
||||
Future<String> get validateFilePath async {
|
||||
final homeDirPath = await appPath.homeDirPath;
|
||||
return join(homeDirPath, 'temp', 'validate${utils.id}.yaml');
|
||||
Future<String> get sharedFilePath async {
|
||||
final mHomeDirPath = await homeDirPath;
|
||||
return join(mHomeDirPath, 'shared.json');
|
||||
}
|
||||
|
||||
Future<String> get sharedPreferencesPath async {
|
||||
@@ -86,9 +106,18 @@ class AppPath {
|
||||
return join(directory.path, profilesDirectoryName);
|
||||
}
|
||||
|
||||
Future<String> getProfilePath(String id) async {
|
||||
final directory = await profilesPath;
|
||||
return join(directory, '$id.yaml');
|
||||
Future<String> getProfilePath(String fileName) async {
|
||||
return join(await profilesPath, '$fileName.yaml');
|
||||
}
|
||||
|
||||
Future<String> get scriptsDirPath async {
|
||||
final path = await homeDirPath;
|
||||
return join(path, 'scripts');
|
||||
}
|
||||
|
||||
Future<String> getScriptPath(String fileName) async {
|
||||
final path = await scriptsDirPath;
|
||||
return join(path, '$fileName.js');
|
||||
}
|
||||
|
||||
Future<String> getIconsCacheDir() async {
|
||||
|
||||
@@ -7,9 +7,9 @@ import 'package:image_picker/image_picker.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
class Picker {
|
||||
Future<PlatformFile?> pickerFile() async {
|
||||
Future<PlatformFile?> pickerFile({bool withData = true}) async {
|
||||
final filePickerResult = await FilePicker.platform.pickFiles(
|
||||
withData: true,
|
||||
withData: withData,
|
||||
allowMultiple: false,
|
||||
initialDirectory: await appPath.downloadDirPath,
|
||||
);
|
||||
@@ -20,15 +20,33 @@ class Picker {
|
||||
final path = await FilePicker.platform.saveFile(
|
||||
fileName: fileName,
|
||||
initialDirectory: await appPath.downloadDirPath,
|
||||
bytes: system.isAndroid ? bytes : null,
|
||||
bytes: bytes,
|
||||
);
|
||||
if (!system.isAndroid && path != null) {
|
||||
final file = await File(path).create(recursive: true);
|
||||
await file.writeAsBytes(bytes);
|
||||
final file = File(path);
|
||||
await file.safeWriteAsBytes(bytes);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
Future<String?> saveFileWithPath(String fileName, String localPath) async {
|
||||
final localFile = File(localPath);
|
||||
if (!await localFile.exists()) {
|
||||
await localFile.create(recursive: true);
|
||||
}
|
||||
final bytes = Platform.isAndroid ? await localFile.readAsBytes() : null;
|
||||
final path = await FilePicker.platform.saveFile(
|
||||
fileName: fileName,
|
||||
initialDirectory: await appPath.downloadDirPath,
|
||||
bytes: bytes,
|
||||
);
|
||||
if (path != null && bytes == null) {
|
||||
await localFile.copy(path);
|
||||
}
|
||||
await localFile.safeDelete();
|
||||
return path;
|
||||
}
|
||||
|
||||
Future<String?> pickerConfigQRCode() async {
|
||||
final xFile = await ImagePicker().pickImage(source: ImageSource.gallery);
|
||||
if (xFile == null) {
|
||||
|
||||
@@ -24,20 +24,60 @@ class Preferences {
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
Future<ClashConfig?> getClashConfig() async {
|
||||
Future<int> getVersion() async {
|
||||
final preferences = await sharedPreferencesCompleter.future;
|
||||
final clashConfigString = preferences?.getString(clashConfigKey);
|
||||
if (clashConfigString == null) return null;
|
||||
final clashConfigMap = json.decode(clashConfigString);
|
||||
return ClashConfig.fromJson(clashConfigMap);
|
||||
return preferences?.getInt('version') ?? 0;
|
||||
}
|
||||
|
||||
Future<void> setVersion(int version) async {
|
||||
final preferences = await sharedPreferencesCompleter.future;
|
||||
await preferences?.setInt('version', version);
|
||||
}
|
||||
|
||||
Future<void> saveShareState(SharedState shareState) async {
|
||||
final preferences = await sharedPreferencesCompleter.future;
|
||||
await preferences?.setString('sharedState', json.encode(shareState));
|
||||
}
|
||||
|
||||
Future<Map<String, Object?>?> getConfigMap() async {
|
||||
try {
|
||||
final preferences = await sharedPreferencesCompleter.future;
|
||||
final configString = preferences?.getString(configKey);
|
||||
if (configString == null) return null;
|
||||
final Map<String, Object?>? configMap = json.decode(configString);
|
||||
return configMap;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, Object?>?> getClashConfigMap() async {
|
||||
try {
|
||||
final preferences = await sharedPreferencesCompleter.future;
|
||||
final clashConfigString = preferences?.getString(clashConfigKey);
|
||||
if (clashConfigString == null) return null;
|
||||
return json.decode(clashConfigString);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clearClashConfig() async {
|
||||
try {
|
||||
final preferences = await sharedPreferencesCompleter.future;
|
||||
await preferences?.remove(clashConfigKey);
|
||||
return;
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Config?> getConfig() async {
|
||||
final preferences = await sharedPreferencesCompleter.future;
|
||||
final configString = preferences?.getString(configKey);
|
||||
if (configString == null) return null;
|
||||
final configMap = json.decode(configString);
|
||||
return Config.compatibleFromJson(configMap);
|
||||
final configMap = await getConfigMap();
|
||||
if (configMap == null) {
|
||||
return null;
|
||||
}
|
||||
return Config.fromJson(configMap);
|
||||
}
|
||||
|
||||
Future<bool> saveConfig(Config config) async {
|
||||
@@ -45,14 +85,9 @@ class Preferences {
|
||||
return preferences?.setString(configKey, json.encode(config)) ?? false;
|
||||
}
|
||||
|
||||
Future<void> clearClashConfig() async {
|
||||
final preferences = await sharedPreferencesCompleter.future;
|
||||
preferences?.remove(clashConfigKey);
|
||||
}
|
||||
|
||||
Future<void> clearPreferences() async {
|
||||
final sharedPreferencesIns = await sharedPreferencesCompleter.future;
|
||||
sharedPreferencesIns?.clear();
|
||||
await sharedPreferencesIns?.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CommonPrint {
|
||||
static CommonPrint? _instance;
|
||||
@@ -16,12 +16,10 @@ class CommonPrint {
|
||||
void log(String? text, {LogLevel logLevel = LogLevel.info}) {
|
||||
final payload = '[APP] $text';
|
||||
debugPrint(payload);
|
||||
if (!globalState.isInit) {
|
||||
if (!appController.isAttach) {
|
||||
return;
|
||||
}
|
||||
globalState.appController.addLog(
|
||||
Log.app(payload).copyWith(logLevel: logLevel),
|
||||
);
|
||||
appController.addLog(Log.app(payload).copyWith(logLevel: logLevel));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import 'dart:typed_data';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -22,7 +24,7 @@ class Request {
|
||||
createHttpClient: () {
|
||||
final client = HttpClient();
|
||||
client.findProxy = (Uri uri) {
|
||||
client.userAgent = globalState.ua;
|
||||
client.userAgent = appController.ua;
|
||||
return FlClashHttpOverrides.handleFindProxy(uri);
|
||||
};
|
||||
return client;
|
||||
@@ -31,10 +33,23 @@ class Request {
|
||||
}
|
||||
|
||||
Future<Response<Uint8List>> getFileResponseForUrl(String url) async {
|
||||
return await _clashDio.get<Uint8List>(
|
||||
url,
|
||||
options: Options(responseType: ResponseType.bytes),
|
||||
);
|
||||
try {
|
||||
return await _clashDio.get<Uint8List>(
|
||||
url,
|
||||
options: Options(responseType: ResponseType.bytes),
|
||||
);
|
||||
} catch (e) {
|
||||
commonPrint.log('getFileResponseForUrl error ${e.toString()}');
|
||||
if (e is DioException) {
|
||||
if (e.type == DioExceptionType.unknown) {
|
||||
throw appLocalizations.unknownNetworkError;
|
||||
} else if (e.type == DioExceptionType.badResponse) {
|
||||
throw appLocalizations.networkException;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
throw appLocalizations.unknownNetworkError;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<String>> getTextResponseForUrl(String url) async {
|
||||
@@ -57,18 +72,23 @@ class Request {
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> checkForUpdate() async {
|
||||
final response = await dio.get(
|
||||
'https://api.github.com/repos/$repository/releases/latest',
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
if (response.statusCode != 200) return null;
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
final remoteVersion = data['tag_name'];
|
||||
final version = globalState.packageInfo.version;
|
||||
final hasUpdate =
|
||||
utils.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0;
|
||||
if (!hasUpdate) return null;
|
||||
return data;
|
||||
try {
|
||||
final response = await dio.get(
|
||||
'https://api.github.com/repos/$repository/releases/latest',
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
if (response.statusCode != 200) return null;
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
final remoteVersion = data['tag_name'];
|
||||
final version = globalState.packageInfo.version;
|
||||
final hasUpdate =
|
||||
utils.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0;
|
||||
if (!hasUpdate) return null;
|
||||
return data;
|
||||
} catch (e) {
|
||||
commonPrint.log('checkForUpdate failed', logLevel: LogLevel.warning);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final Map<String, IpInfo Function(Map<String, dynamic>)> _ipInfoSources = {
|
||||
@@ -83,6 +103,7 @@ class Request {
|
||||
|
||||
Future<Result<IpInfo?>> checkIp({CancelToken? cancelToken}) async {
|
||||
var failureCount = 0;
|
||||
final token = cancelToken ?? CancelToken();
|
||||
final futures = _ipInfoSources.entries.map((source) async {
|
||||
final Completer<Result<IpInfo?>> completer = Completer();
|
||||
handleFailRes() {
|
||||
@@ -94,7 +115,7 @@ class Request {
|
||||
final future = dio
|
||||
.get<Map<String, dynamic>>(
|
||||
source.key,
|
||||
cancelToken: cancelToken,
|
||||
cancelToken: token,
|
||||
options: Options(responseType: ResponseType.json),
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
@@ -117,7 +138,7 @@ class Request {
|
||||
return completer.future;
|
||||
});
|
||||
final res = await Future.any(futures);
|
||||
cancelToken?.cancel();
|
||||
token.cancel();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
58
lib/common/snowflake.dart
Normal file
58
lib/common/snowflake.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
class Snowflake {
|
||||
static Snowflake? _instance;
|
||||
|
||||
Snowflake._internal();
|
||||
|
||||
factory Snowflake() {
|
||||
_instance ??= Snowflake._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
static const int twepoch = 1704067200000;
|
||||
|
||||
static const int workerIdBits = 10;
|
||||
static const int sequenceBits = 12;
|
||||
|
||||
static const int maxWorkerId = -1 ^ (-1 << workerIdBits);
|
||||
static const int sequenceMask = -1 ^ (-1 << sequenceBits);
|
||||
|
||||
static const int workerIdShift = sequenceBits;
|
||||
static const int timestampLeftShift = sequenceBits + workerIdBits;
|
||||
|
||||
final int workerId = 1;
|
||||
int _lastTimestamp = -1;
|
||||
int _sequence = 0;
|
||||
|
||||
int get id {
|
||||
int timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
if (timestamp < _lastTimestamp) {
|
||||
throw ArgumentError(
|
||||
'Clock moved backwards. Refusing to generate id for ${_lastTimestamp - timestamp} milliseconds',
|
||||
);
|
||||
}
|
||||
if (timestamp == _lastTimestamp) {
|
||||
_sequence = (_sequence + 1) & sequenceMask;
|
||||
if (_sequence == 0) {
|
||||
timestamp = _getNextMillis(_lastTimestamp);
|
||||
}
|
||||
} else {
|
||||
_sequence = 0;
|
||||
}
|
||||
|
||||
_lastTimestamp = timestamp;
|
||||
|
||||
return ((timestamp - twepoch) << timestampLeftShift) |
|
||||
(workerId << workerIdShift) |
|
||||
_sequence;
|
||||
}
|
||||
|
||||
int _getNextMillis(int lastTimestamp) {
|
||||
int timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
while (timestamp <= lastTimestamp) {
|
||||
timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
final snowflake = Snowflake();
|
||||
33
lib/common/store.dart
Normal file
33
lib/common/store.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'dart:async';
|
||||
|
||||
class Store<T> {
|
||||
late T _data;
|
||||
|
||||
Store(Stream stream, T defaultValue) {
|
||||
stream.listen((data) {
|
||||
_add(data);
|
||||
});
|
||||
_data = defaultValue;
|
||||
}
|
||||
|
||||
bool equals(T oldValue, T newValue) {
|
||||
return oldValue == newValue;
|
||||
}
|
||||
|
||||
void _add(T value) {
|
||||
if (!equals(_data, value)) {
|
||||
_streamController.add(value);
|
||||
_data = value;
|
||||
}
|
||||
}
|
||||
|
||||
final StreamController<T> _streamController = StreamController<T>.broadcast();
|
||||
|
||||
Stream<T> get stream => _streamController.stream;
|
||||
|
||||
T get value => _data;
|
||||
|
||||
set value(T value) {
|
||||
_add(value);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
import 'print.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
|
||||
extension StringExtension on String {
|
||||
bool get isUrl {
|
||||
@@ -78,13 +77,26 @@ extension StringExtension on String {
|
||||
// bool containsToLower(String target) {
|
||||
// return toLowerCase().contains(target);
|
||||
// }
|
||||
}
|
||||
|
||||
extension StringExtensionSafe on String? {
|
||||
String getSafeValue(String defaultValue) {
|
||||
if (this == null || this!.isEmpty) {
|
||||
return defaultValue;
|
||||
Future<T> commonToJSON<T>() async {
|
||||
final thresholdLimit = 51200;
|
||||
if (length < thresholdLimit) {
|
||||
return json.decode(this);
|
||||
} else {
|
||||
return await decodeJSONTask<T>(this);
|
||||
}
|
||||
return this!;
|
||||
}
|
||||
}
|
||||
|
||||
extension StringNullExt on String? {
|
||||
String takeFirstValid(List<String?> others, {String defaultValue = ''}) {
|
||||
if (this != null && this!.trim().isNotEmpty) return this!.trim();
|
||||
|
||||
for (final s in others) {
|
||||
if (s != null && s.trim().isNotEmpty) {
|
||||
return s.trim();
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,8 +260,9 @@ class Windows {
|
||||
await Future.delayed(Duration(milliseconds: 300));
|
||||
final retryStatus = await retry(
|
||||
task: checkService,
|
||||
maxAttempts: 5,
|
||||
retryIf: (status) => status != WindowsHelperServiceStatus.running,
|
||||
delay: commonDuration,
|
||||
delay: Duration(seconds: 1),
|
||||
);
|
||||
return res && retryStatus == WindowsHelperServiceStatus.running;
|
||||
}
|
||||
|
||||
612
lib/common/task.dart
Normal file
612
lib/common/task.dart
Normal file
@@ -0,0 +1,612 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_flutter/drift_flutter.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/database/database.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
Future<T> decodeJSONTask<T>(String data) async {
|
||||
return await compute<String, T>(_decodeJSON, data);
|
||||
}
|
||||
|
||||
Future<T> _decodeJSON<T>(String content) async {
|
||||
return json.decode(content);
|
||||
}
|
||||
|
||||
Future<String> encodeJSONTask<T>(T data) async {
|
||||
return await compute<T, String>(_encodeJSON, data);
|
||||
}
|
||||
|
||||
Future<String> _encodeJSON<T>(T content) async {
|
||||
return json.encode(content);
|
||||
}
|
||||
|
||||
Future<String> encodeYamlTask<T>(T data) async {
|
||||
return await compute<T, String>(_encodeYaml, data);
|
||||
}
|
||||
|
||||
Future<String> _encodeYaml<T>(T content) async {
|
||||
return yaml.encode(content);
|
||||
}
|
||||
|
||||
Future<List<Group>> toGroupsTask(ComputeGroupsState data) async {
|
||||
return await compute<ComputeGroupsState, List<Group>>(_toGroupsTask, data);
|
||||
}
|
||||
|
||||
Future<List<Group>> _toGroupsTask(ComputeGroupsState state) async {
|
||||
final proxiesData = state.proxiesData;
|
||||
final all = proxiesData.all;
|
||||
final sortType = state.sortType;
|
||||
final delayMap = state.delayMap;
|
||||
final selectedMap = state.selectedMap;
|
||||
final defaultTestUrl = state.defaultTestUrl;
|
||||
final proxies = proxiesData.proxies;
|
||||
if (proxies.isEmpty) return [];
|
||||
final groupsRaw = all
|
||||
.where((name) {
|
||||
final proxy = proxies[name] ?? {};
|
||||
return GroupTypeExtension.valueList.contains(proxy['type']);
|
||||
})
|
||||
.map((groupName) {
|
||||
final group = proxies[groupName];
|
||||
group['all'] = ((group['all'] ?? []) as List)
|
||||
.map((name) => proxies[name])
|
||||
.where((proxy) => proxy != null)
|
||||
.toList();
|
||||
return group;
|
||||
})
|
||||
.toList();
|
||||
final groups = groupsRaw.map((e) => Group.fromJson(e)).toList();
|
||||
return computeSort(
|
||||
groups: groups,
|
||||
sortType: sortType,
|
||||
delayMap: delayMap,
|
||||
selectedMap: selectedMap,
|
||||
defaultTestUrl: defaultTestUrl,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> makeRealProfileTask(
|
||||
MakeRealProfileState data,
|
||||
) async {
|
||||
return await compute<MakeRealProfileState, Map<String, dynamic>>(
|
||||
_makeRealProfileTask,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _makeRealProfileTask(
|
||||
MakeRealProfileState data,
|
||||
) async {
|
||||
final rawConfig = Map.from(data.rawConfig);
|
||||
final realPatchConfig = data.realPatchConfig;
|
||||
final profilesPath = data.profilesPath;
|
||||
final profileId = data.profileId;
|
||||
final overrideDns = data.overrideDns;
|
||||
final addedRules = data.addedRules;
|
||||
final appendSystemDns = data.appendSystemDns;
|
||||
final defaultUA = data.defaultUA;
|
||||
String getProvidersFilePathInner(String type, String url) {
|
||||
return join(
|
||||
profilesPath,
|
||||
'providers',
|
||||
profileId.toString(),
|
||||
type,
|
||||
url.toMd5(),
|
||||
);
|
||||
}
|
||||
|
||||
rawConfig['external-controller'] = realPatchConfig.externalController.value;
|
||||
rawConfig['external-ui'] = '';
|
||||
rawConfig['interface-name'] = '';
|
||||
rawConfig['external-ui-url'] = '';
|
||||
rawConfig['tcp-concurrent'] = realPatchConfig.tcpConcurrent;
|
||||
rawConfig['unified-delay'] = realPatchConfig.unifiedDelay;
|
||||
rawConfig['ipv6'] = realPatchConfig.ipv6;
|
||||
rawConfig['log-level'] = realPatchConfig.logLevel.name;
|
||||
rawConfig['port'] = 0;
|
||||
rawConfig['socks-port'] = 0;
|
||||
rawConfig['keep-alive-interval'] = realPatchConfig.keepAliveInterval;
|
||||
rawConfig['mixed-port'] = realPatchConfig.mixedPort;
|
||||
rawConfig['port'] = realPatchConfig.port;
|
||||
rawConfig['socks-port'] = realPatchConfig.socksPort;
|
||||
rawConfig['redir-port'] = realPatchConfig.redirPort;
|
||||
rawConfig['tproxy-port'] = realPatchConfig.tproxyPort;
|
||||
rawConfig['find-process-mode'] = realPatchConfig.findProcessMode.name;
|
||||
rawConfig['allow-lan'] = realPatchConfig.allowLan;
|
||||
rawConfig['mode'] = realPatchConfig.mode.name;
|
||||
if (rawConfig['tun'] == null) {
|
||||
rawConfig['tun'] = {};
|
||||
}
|
||||
rawConfig['tun']['enable'] = realPatchConfig.tun.enable;
|
||||
rawConfig['tun']['device'] = realPatchConfig.tun.device;
|
||||
rawConfig['tun']['dns-hijack'] = realPatchConfig.tun.dnsHijack;
|
||||
rawConfig['tun']['stack'] = realPatchConfig.tun.stack.name;
|
||||
rawConfig['tun']['route-address'] = realPatchConfig.tun.routeAddress;
|
||||
rawConfig['tun']['auto-route'] = realPatchConfig.tun.autoRoute;
|
||||
rawConfig['geodata-loader'] = realPatchConfig.geodataLoader.name;
|
||||
if (rawConfig['sniffer']?['sniff'] != null) {
|
||||
for (final value in (rawConfig['sniffer']?['sniff'] as Map).values) {
|
||||
if (value['ports'] != null && value['ports'] is List) {
|
||||
value['ports'] =
|
||||
value['ports']?.map((item) => item.toString()).toList() ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rawConfig['profile'] == null) {
|
||||
rawConfig['profile'] = {};
|
||||
}
|
||||
if (rawConfig['proxy-providers'] != null) {
|
||||
final proxyProviders = rawConfig['proxy-providers'] as Map;
|
||||
for (final key in proxyProviders.keys) {
|
||||
final proxyProvider = proxyProviders[key];
|
||||
if (proxyProvider['type'] != 'http') {
|
||||
continue;
|
||||
}
|
||||
if (proxyProvider['url'] != null) {
|
||||
proxyProvider['path'] = getProvidersFilePathInner(
|
||||
'proxies',
|
||||
proxyProvider['url'],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rawConfig['rule-providers'] != null) {
|
||||
final ruleProviders = rawConfig['rule-providers'] as Map;
|
||||
for (final key in ruleProviders.keys) {
|
||||
final ruleProvider = ruleProviders[key];
|
||||
if (ruleProvider['type'] != 'http') {
|
||||
continue;
|
||||
}
|
||||
if (ruleProvider['url'] != null) {
|
||||
ruleProvider['path'] = getProvidersFilePathInner(
|
||||
'rules',
|
||||
ruleProvider['url'],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
rawConfig['profile']['store-selected'] = false;
|
||||
rawConfig['geox-url'] = realPatchConfig.geoXUrl.toJson();
|
||||
rawConfig['global-ua'] = realPatchConfig.globalUa ?? defaultUA;
|
||||
if (rawConfig['hosts'] == null) {
|
||||
rawConfig['hosts'] = {};
|
||||
}
|
||||
for (final host in realPatchConfig.hosts.entries) {
|
||||
rawConfig['hosts'][host.key] = host.value.splitByMultipleSeparators;
|
||||
}
|
||||
if (rawConfig['dns'] == null) {
|
||||
rawConfig['dns'] = {};
|
||||
}
|
||||
final isEnableDns = rawConfig['dns']['enable'] == true;
|
||||
final systemDns = 'system://';
|
||||
if (overrideDns || !isEnableDns) {
|
||||
final dns = switch (!isEnableDns) {
|
||||
true => realPatchConfig.dns.copyWith(
|
||||
nameserver: [...realPatchConfig.dns.nameserver, systemDns],
|
||||
),
|
||||
false => realPatchConfig.dns,
|
||||
};
|
||||
rawConfig['dns'] = dns.toJson();
|
||||
rawConfig['dns']['nameserver-policy'] = {};
|
||||
for (final entry in dns.nameserverPolicy.entries) {
|
||||
rawConfig['dns']['nameserver-policy'][entry.key] =
|
||||
entry.value.splitByMultipleSeparators;
|
||||
}
|
||||
}
|
||||
if (appendSystemDns) {
|
||||
final List<String> nameserver = List<String>.from(
|
||||
rawConfig['dns']['nameserver'] ?? [],
|
||||
);
|
||||
if (!nameserver.contains(systemDns)) {
|
||||
rawConfig['dns']['nameserver'] = [...nameserver, systemDns];
|
||||
}
|
||||
}
|
||||
List<String> rules = [];
|
||||
if (rawConfig['rules'] != null) {
|
||||
rules = List<String>.from(rawConfig['rules']);
|
||||
}
|
||||
rawConfig.remove('rules');
|
||||
if (addedRules.isNotEmpty) {
|
||||
final parsedNewRules = addedRules
|
||||
.map((item) => ParsedRule.parseString(item.value))
|
||||
.toList();
|
||||
final hasMatchPlaceholder = parsedNewRules.any(
|
||||
(item) => item.ruleTarget?.toUpperCase() == 'MATCH',
|
||||
);
|
||||
String? replacementTarget;
|
||||
|
||||
if (hasMatchPlaceholder) {
|
||||
for (int i = rules.length - 1; i >= 0; i--) {
|
||||
final parsed = ParsedRule.parseString(rules[i]);
|
||||
if (parsed.ruleAction == RuleAction.MATCH) {
|
||||
final target = parsed.ruleTarget;
|
||||
if (target != null && target.isNotEmpty) {
|
||||
replacementTarget = target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final List<String> finalAddedRules;
|
||||
|
||||
if (replacementTarget?.isNotEmpty == true) {
|
||||
finalAddedRules = [];
|
||||
for (int i = 0; i < parsedNewRules.length; i++) {
|
||||
final parsed = parsedNewRules[i];
|
||||
if (parsed.ruleTarget?.toUpperCase() == 'MATCH') {
|
||||
finalAddedRules.add(
|
||||
parsed.copyWith(ruleTarget: replacementTarget).value,
|
||||
);
|
||||
} else {
|
||||
finalAddedRules.add(addedRules[i].value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finalAddedRules = addedRules.map((e) => e.value).toList();
|
||||
}
|
||||
rules = [...finalAddedRules, ...rules];
|
||||
}
|
||||
rawConfig['rules'] = rules;
|
||||
return Map<String, dynamic>.from(rawConfig);
|
||||
}
|
||||
|
||||
Future<List<String>> shakingProfileTask(
|
||||
VM2<Iterable<int>, Iterable<int>> data,
|
||||
) async {
|
||||
return await compute<
|
||||
VM3<Iterable<int>, Iterable<int>, RootIsolateToken>,
|
||||
List<String>
|
||||
>(_shakingProfileTask, VM3(data.a, data.b, RootIsolateToken.instance!));
|
||||
}
|
||||
|
||||
Future<List<String>> _shakingProfileTask(
|
||||
VM3<Iterable<int>, Iterable<int>, RootIsolateToken> data,
|
||||
) async {
|
||||
final profileIds = data.a;
|
||||
final scriptIds = data.b;
|
||||
final token = data.c;
|
||||
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
|
||||
final profilesDir = Directory(await appPath.profilesPath);
|
||||
final scriptsDir = Directory(await appPath.scriptsDirPath);
|
||||
final providersDir = Directory(await appPath.getProvidersRootPath());
|
||||
final List<String> targets = [];
|
||||
void scanDirectory(
|
||||
Directory dir,
|
||||
Iterable<int> baseNames, {
|
||||
bool skipProvidersFolder = false,
|
||||
}) {
|
||||
if (!dir.existsSync()) return;
|
||||
final entities = dir.listSync(recursive: false, followLinks: false);
|
||||
|
||||
for (final entity in entities) {
|
||||
if (entity is File) {
|
||||
final id = basenameWithoutExtension(entity.path);
|
||||
if (!baseNames.contains(int.tryParse(id))) {
|
||||
targets.add(entity.path);
|
||||
}
|
||||
} else if (skipProvidersFolder && entity is Directory) {
|
||||
if (basename(entity.path) == 'providers') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scanDirectory(profilesDir, profileIds, skipProvidersFolder: true);
|
||||
scanDirectory(providersDir, profileIds);
|
||||
scanDirectory(scriptsDir, scriptIds);
|
||||
return targets;
|
||||
}
|
||||
|
||||
Future<String> encodeLogsTask(List<Log> data) async {
|
||||
return await compute<List<Log>, String>(_encodeLogsTask, data);
|
||||
}
|
||||
|
||||
Future<String> _encodeLogsTask(List<Log> data) async {
|
||||
final logsRaw = data.map((item) => item.toString());
|
||||
final logsRawString = logsRaw.join('\n');
|
||||
return logsRawString;
|
||||
}
|
||||
|
||||
Future<MigrationData> oldToNowTask(Map<String, Object?> data) async {
|
||||
final homeDir = await appPath.homeDirPath;
|
||||
return await compute<
|
||||
VM3<Map<String, Object?>, String, String>,
|
||||
MigrationData
|
||||
>(_oldToNowTask, VM3(data, homeDir, homeDir));
|
||||
}
|
||||
|
||||
Future<MigrationData> _oldToNowTask(
|
||||
VM3<Map<String, Object?>, String, String> data,
|
||||
) async {
|
||||
final configMap = data.a;
|
||||
final sourcePath = data.b;
|
||||
final targetPath = data.c;
|
||||
|
||||
final accessControlMap = configMap['accessControl'];
|
||||
final isAccessControl = configMap['isAccessControl'];
|
||||
if (accessControlMap != null) {
|
||||
(accessControlMap as Map)['enable'] = isAccessControl;
|
||||
if (configMap['vpnProps'] != null) {
|
||||
final vpnPropsRaw = configMap['vpnProps'] as Map;
|
||||
vpnPropsRaw['accessControl'] = accessControlMap;
|
||||
}
|
||||
}
|
||||
if (configMap['vpnProps'] != null) {
|
||||
final vpnPropsRaw = configMap['vpnProps'] as Map;
|
||||
vpnPropsRaw['accessControlProps'] = vpnPropsRaw['accessControl'];
|
||||
}
|
||||
configMap['davProps'] = configMap['dav'];
|
||||
final appSettingProps = configMap['appSetting'] as Map? ?? {};
|
||||
appSettingProps['restoreStrategy'] = appSettingProps['recoveryStrategy'];
|
||||
configMap['appSettingProps'] = appSettingProps;
|
||||
configMap['proxiesStyleProps'] = configMap['proxiesStyle'];
|
||||
configMap['proxiesStyleProps'] = configMap['proxiesStyle'];
|
||||
// final overwriteMap = configMap['overwrite'] as Map? ?? {};
|
||||
// configMap['overwriteType'] = overwriteMap['type'];
|
||||
// configMap['scriptId'] = overwriteMap['scriptOverwrite'];
|
||||
List rawScripts = configMap['scripts'] as List<dynamic>? ?? [];
|
||||
if (rawScripts.isEmpty) {
|
||||
final scriptPropsJson = configMap['scriptProps'] as Map<String, dynamic>?;
|
||||
if (scriptPropsJson != null) {
|
||||
rawScripts = scriptPropsJson['scripts'] as List<dynamic>? ?? [];
|
||||
}
|
||||
}
|
||||
final Map<String, int> idMap = {};
|
||||
final List<Script> scripts = [];
|
||||
for (final rawScript in rawScripts) {
|
||||
final id = rawScript['id'] as String?;
|
||||
final content = rawScript['content'] as String?;
|
||||
final label = rawScript['label'] as String?;
|
||||
if (id == null || content == null || label == null) {
|
||||
continue;
|
||||
}
|
||||
final newId = idMap.updateCacheValue(rawScript['id'], () => snowflake.id);
|
||||
final path = _getScriptPath(targetPath, newId.toString());
|
||||
final file = File(path);
|
||||
await file.safeWriteAsString(content);
|
||||
scripts.add(
|
||||
Script(id: newId, label: label, lastUpdateTime: DateTime.now()),
|
||||
);
|
||||
}
|
||||
List rawRules = configMap['rules'] as List<dynamic>? ?? [];
|
||||
final List<Rule> rules = [];
|
||||
final List<ProfileRuleLink> links = [];
|
||||
for (final rawRule in rawRules) {
|
||||
final id = idMap.updateCacheValue(rawRule['id'], () => snowflake.id);
|
||||
rawRule['id'] = id;
|
||||
rules.add(Rule.fromJson(rawRule));
|
||||
links.add(ProfileRuleLink(ruleId: id));
|
||||
}
|
||||
List rawProfiles = configMap['profiles'] as List<dynamic>? ?? [];
|
||||
final List<Profile> profiles = [];
|
||||
for (final rawProfile in rawProfiles) {
|
||||
final rawId = rawProfile['id'] as String?;
|
||||
if (rawId == null) {
|
||||
continue;
|
||||
}
|
||||
final profileId = idMap.updateCacheValue(rawId, () => snowflake.id);
|
||||
rawProfile['id'] = profileId;
|
||||
final overwrite = rawProfile['overwrite'] as Map?;
|
||||
if (overwrite != null) {
|
||||
final standardOverwrite = overwrite['standardOverwrite'] as Map?;
|
||||
if (standardOverwrite != null) {
|
||||
final addedRules = standardOverwrite['addedRules'] as List? ?? [];
|
||||
for (final addRule in addedRules) {
|
||||
final id = idMap.updateCacheValue(addRule['id'], () => snowflake.id);
|
||||
addRule['id'] = id;
|
||||
rules.add(Rule.fromJson(addRule));
|
||||
links.add(
|
||||
ProfileRuleLink(
|
||||
profileId: profileId,
|
||||
ruleId: id,
|
||||
scene: RuleScene.added,
|
||||
),
|
||||
);
|
||||
}
|
||||
final disabledRuleIds = standardOverwrite['disabledRuleIds'] as List?;
|
||||
if (disabledRuleIds != null) {
|
||||
for (final disabledRuleId in disabledRuleIds) {
|
||||
final newDisabledRuleId = idMap[disabledRuleId];
|
||||
if (newDisabledRuleId != null) {
|
||||
links.add(
|
||||
ProfileRuleLink(
|
||||
profileId: profileId,
|
||||
ruleId: newDisabledRuleId,
|
||||
scene: RuleScene.disabled,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final scriptOverwrite = overwrite['scriptOverwrite'] as Map?;
|
||||
if (scriptOverwrite != null) {
|
||||
final scriptId = scriptOverwrite['scriptId'] as String?;
|
||||
rawProfile['scriptId'] = scriptId != null ? idMap[scriptId] : null;
|
||||
}
|
||||
rawProfile['overwriteType'] = overwrite['type'];
|
||||
}
|
||||
|
||||
final sourceFile = File(_getProfilePath(sourcePath, rawId));
|
||||
final targetFilePath = _getProfilePath(targetPath, profileId.toString());
|
||||
await sourceFile.safeCopy(targetFilePath);
|
||||
profiles.add(Profile.fromJson(rawProfile));
|
||||
}
|
||||
final currentProfileId = configMap['currentProfileId'];
|
||||
configMap['currentProfileId'] = currentProfileId != null
|
||||
? idMap[currentProfileId]
|
||||
: null;
|
||||
return MigrationData(
|
||||
configMap: configMap,
|
||||
profiles: profiles,
|
||||
rules: rules,
|
||||
scripts: scripts,
|
||||
links: links,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> backupTask(
|
||||
Map<String, dynamic> configMap,
|
||||
Iterable<String> fileNames,
|
||||
) async {
|
||||
return await compute<
|
||||
VM3<Map<String, dynamic>, Iterable<String>, RootIsolateToken>,
|
||||
String
|
||||
>(_backupTask, VM3(configMap, fileNames, RootIsolateToken.instance!));
|
||||
}
|
||||
|
||||
Future<String> _backupTask<T>(
|
||||
VM3<Map<String, dynamic>, Iterable<String>, RootIsolateToken> args,
|
||||
) async {
|
||||
final configMap = args.a;
|
||||
final fileNames = args.b;
|
||||
final token = args.c;
|
||||
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
|
||||
final dbPath = await appPath.databasePath;
|
||||
final configStr = json.encode(configMap);
|
||||
final profilesDir = Directory(await appPath.profilesPath);
|
||||
final scriptsDir = Directory(await appPath.scriptsDirPath);
|
||||
final tempZipFilePath = await appPath.tempFilePath;
|
||||
final tempDBFile = File(await appPath.tempFilePath);
|
||||
final tempConfigFile = File(await appPath.tempFilePath);
|
||||
final dbFile = File(dbPath);
|
||||
if (await dbFile.exists()) {
|
||||
await dbFile.copy(tempDBFile.path);
|
||||
}
|
||||
final encoder = ZipFileEncoder();
|
||||
encoder.create(tempZipFilePath);
|
||||
await tempConfigFile.writeAsString(configStr);
|
||||
await encoder.addFile(tempDBFile, backupDatabaseName);
|
||||
await encoder.addFile(tempConfigFile, configJsonName);
|
||||
if (await profilesDir.exists()) {
|
||||
await encoder.addDirectory(
|
||||
profilesDir,
|
||||
filter: (file, _) {
|
||||
if (!fileNames.contains(basename(file.path))) {
|
||||
return ZipFileOperation.skip;
|
||||
}
|
||||
return ZipFileOperation.include;
|
||||
},
|
||||
);
|
||||
}
|
||||
if (await scriptsDir.exists()) {
|
||||
await encoder.addDirectory(
|
||||
scriptsDir,
|
||||
filter: (file, _) {
|
||||
if (!fileNames.contains(basename(file.path))) {
|
||||
return ZipFileOperation.skip;
|
||||
}
|
||||
return ZipFileOperation.include;
|
||||
},
|
||||
);
|
||||
}
|
||||
encoder.close();
|
||||
await tempConfigFile.safeDelete();
|
||||
await tempDBFile.safeDelete();
|
||||
return tempZipFilePath;
|
||||
}
|
||||
|
||||
Future<MigrationData> restoreTask() async {
|
||||
return await compute<RootIsolateToken, MigrationData>(
|
||||
_restoreTask,
|
||||
RootIsolateToken.instance!,
|
||||
);
|
||||
}
|
||||
|
||||
Future<MigrationData> _restoreTask(RootIsolateToken token) async {
|
||||
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
|
||||
final backupFilePath = await appPath.backupFilePath;
|
||||
final restoreDirPath = await appPath.restoreDirPath;
|
||||
final homeDirPath = await appPath.homeDirPath;
|
||||
final zipDecoder = ZipDecoder();
|
||||
final input = InputFileStream(backupFilePath);
|
||||
final archive = zipDecoder.decodeStream(input);
|
||||
final dir = Directory(restoreDirPath);
|
||||
await dir.create(recursive: true);
|
||||
for (final file in archive.files) {
|
||||
final outPath = join(restoreDirPath, posix.normalize(file.name));
|
||||
final outputStream = OutputFileStream(outPath);
|
||||
file.writeContent(outputStream);
|
||||
await outputStream.close();
|
||||
}
|
||||
await input.close();
|
||||
final restoreConfigFile = File(join(restoreDirPath, configJsonName));
|
||||
if (!await restoreConfigFile.exists()) {
|
||||
throw appLocalizations.invalidBackupFile;
|
||||
}
|
||||
final restoreConfigMap =
|
||||
json.decode(await restoreConfigFile.readAsString())
|
||||
as Map<String, Object?>?;
|
||||
final version = restoreConfigMap?['version'] ?? 0;
|
||||
MigrationData migrationData = MigrationData(configMap: restoreConfigMap);
|
||||
if (version == 0 && restoreConfigMap != null) {
|
||||
migrationData = await _oldToNowTask(
|
||||
VM3(restoreConfigMap, restoreDirPath, homeDirPath),
|
||||
);
|
||||
return migrationData;
|
||||
}
|
||||
final backupDatabaseFile = File(join(restoreDirPath, backupDatabaseName));
|
||||
if (!await backupDatabaseFile.exists()) {
|
||||
return migrationData;
|
||||
}
|
||||
final database = Database(
|
||||
driftDatabase(
|
||||
name: 'database',
|
||||
native: DriftNativeOptions(
|
||||
databaseDirectory: () async => Directory(restoreDirPath),
|
||||
),
|
||||
),
|
||||
);
|
||||
final results = await Future.wait([
|
||||
database.profilesDao.all().get(),
|
||||
database.scriptsDao.all().get(),
|
||||
database.rules.all().map((item) => item.toRule()).get(),
|
||||
database.profileRuleLinks.all().map((item) => item.toLink()).get(),
|
||||
]);
|
||||
final profiles = results[0].cast<Profile>();
|
||||
final scripts = results[1].cast<Script>();
|
||||
final profilesMigration = profiles.map(
|
||||
(item) => VM2(
|
||||
_getProfilePath(restoreDirPath, item.id.toString()),
|
||||
_getProfilePath(homeDirPath, item.id.toString()),
|
||||
),
|
||||
);
|
||||
final scriptsMigration = scripts.map(
|
||||
(item) => VM2(
|
||||
_getScriptPath(restoreDirPath, item.id.toString()),
|
||||
_getScriptPath(homeDirPath, item.id.toString()),
|
||||
),
|
||||
);
|
||||
await _copyWithMapList([...profilesMigration, ...scriptsMigration]);
|
||||
migrationData = migrationData.copyWith(
|
||||
profiles: profiles,
|
||||
scripts: scripts,
|
||||
rules: results[2].cast<Rule>(),
|
||||
links: results[3].cast<ProfileRuleLink>(),
|
||||
);
|
||||
await database.close();
|
||||
return migrationData;
|
||||
}
|
||||
|
||||
Future<void> _copyWithMapList(List<VM2<String, String>> copyMapList) async {
|
||||
await Future.wait(
|
||||
copyMapList.map((item) => File(item.a).safeCopy(item.b)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
String _getScriptPath(String root, String fileName) {
|
||||
return join(root, 'scripts', '$fileName.js');
|
||||
}
|
||||
|
||||
String _getProfilePath(String root, String fileName) {
|
||||
return join(root, 'profiles', '$fileName.yaml');
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/iterable.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
@@ -14,6 +13,15 @@ import 'system.dart';
|
||||
import 'window.dart';
|
||||
|
||||
class Tray {
|
||||
static Tray? _instance;
|
||||
|
||||
Tray._internal();
|
||||
|
||||
factory Tray() {
|
||||
_instance ??= Tray._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
String get trayIconSuffix {
|
||||
return system.isWindows ? 'ico' : 'png';
|
||||
}
|
||||
@@ -29,11 +37,10 @@ class Tray {
|
||||
}
|
||||
|
||||
Future _updateSystemTray({
|
||||
bool force = false,
|
||||
required bool isStart,
|
||||
required bool tunEnable,
|
||||
}) async {
|
||||
if (Platform.isLinux || force) {
|
||||
if (Platform.isLinux) {
|
||||
await trayManager.destroy();
|
||||
}
|
||||
await trayManager.setIcon(
|
||||
@@ -47,7 +54,7 @@ class Tray {
|
||||
|
||||
Future<void> update({
|
||||
required TrayState trayState,
|
||||
bool focus = false,
|
||||
required Traffic traffic,
|
||||
}) async {
|
||||
if (system.isAndroid) {
|
||||
return;
|
||||
@@ -56,7 +63,6 @@ class Tray {
|
||||
await _updateSystemTray(
|
||||
isStart: trayState.isStart,
|
||||
tunEnable: trayState.tunEnable,
|
||||
force: focus,
|
||||
);
|
||||
}
|
||||
List<MenuItem> menuItems = [];
|
||||
@@ -70,7 +76,7 @@ class Tray {
|
||||
final startMenuItem = MenuItem.checkbox(
|
||||
label: trayState.isStart ? appLocalizations.stop : appLocalizations.start,
|
||||
onClick: (_) async {
|
||||
globalState.appController.updateStart();
|
||||
appController.updateStart();
|
||||
},
|
||||
checked: false,
|
||||
);
|
||||
@@ -79,7 +85,7 @@ class Tray {
|
||||
final speedStatistics = MenuItem.checkbox(
|
||||
label: appLocalizations.speedStatistics,
|
||||
onClick: (_) async {
|
||||
globalState.appController.updateSpeedStatistics();
|
||||
appController.updateSpeedStatistics();
|
||||
},
|
||||
checked: trayState.showTrayTitle,
|
||||
);
|
||||
@@ -91,7 +97,7 @@ class Tray {
|
||||
MenuItem.checkbox(
|
||||
label: Intl.message(mode.name),
|
||||
onClick: (_) {
|
||||
globalState.appController.changeMode(mode);
|
||||
appController.changeMode(mode);
|
||||
},
|
||||
checked: mode == trayState.mode,
|
||||
),
|
||||
@@ -106,9 +112,8 @@ class Tray {
|
||||
MenuItem.checkbox(
|
||||
label: proxy.name,
|
||||
checked:
|
||||
globalState.getSelectedProxyName(group.name) == proxy.name,
|
||||
appController.getSelectedProxyName(group.name) == proxy.name,
|
||||
onClick: (_) {
|
||||
final appController = globalState.appController;
|
||||
appController.updateCurrentSelectedMap(group.name, proxy.name);
|
||||
appController.changeProxy(
|
||||
groupName: group.name,
|
||||
@@ -134,7 +139,7 @@ class Tray {
|
||||
MenuItem.checkbox(
|
||||
label: appLocalizations.tun,
|
||||
onClick: (_) {
|
||||
globalState.appController.updateTun();
|
||||
appController.updateTun();
|
||||
},
|
||||
checked: trayState.tunEnable,
|
||||
),
|
||||
@@ -143,7 +148,7 @@ class Tray {
|
||||
MenuItem.checkbox(
|
||||
label: appLocalizations.systemProxy,
|
||||
onClick: (_) {
|
||||
globalState.appController.updateSystemProxy();
|
||||
appController.updateSystemProxy();
|
||||
},
|
||||
checked: trayState.systemProxy,
|
||||
),
|
||||
@@ -153,7 +158,7 @@ class Tray {
|
||||
final autoStartMenuItem = MenuItem.checkbox(
|
||||
label: appLocalizations.autoLaunch,
|
||||
onClick: (_) async {
|
||||
globalState.appController.updateAutoLaunch();
|
||||
appController.updateAutoLaunch();
|
||||
},
|
||||
checked: trayState.autoLaunch,
|
||||
);
|
||||
@@ -169,7 +174,7 @@ class Tray {
|
||||
final exitMenuItem = MenuItem(
|
||||
label: appLocalizations.exit,
|
||||
onClick: (_) async {
|
||||
await globalState.appController.handleExit();
|
||||
await appController.handleExit();
|
||||
},
|
||||
);
|
||||
menuItems.add(exitMenuItem);
|
||||
@@ -179,13 +184,9 @@ class Tray {
|
||||
await _updateSystemTray(
|
||||
isStart: trayState.isStart,
|
||||
tunEnable: trayState.tunEnable,
|
||||
force: focus,
|
||||
);
|
||||
}
|
||||
updateTrayTitle(
|
||||
showTrayTitle: trayState.showTrayTitle,
|
||||
traffic: globalState.appState.traffics.list.safeLast(Traffic()),
|
||||
);
|
||||
updateTrayTitle(showTrayTitle: trayState.showTrayTitle, traffic: traffic);
|
||||
}
|
||||
|
||||
Future<void> updateTrayTitle({
|
||||
|
||||
@@ -10,6 +10,15 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class Utils {
|
||||
static Utils? _instance;
|
||||
|
||||
Utils._internal();
|
||||
|
||||
factory Utils() {
|
||||
_instance ??= Utils._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
Color? getDelayColor(int? delay) {
|
||||
if (delay == null) return null;
|
||||
if (delay < 0) return Colors.red;
|
||||
@@ -319,7 +328,7 @@ class Utils {
|
||||
required Function function,
|
||||
required void Function(T data, int elapsedMilliseconds) onWatch,
|
||||
}) async {
|
||||
if (kDebugMode) {
|
||||
if (kDebugMode && watchExecution) {
|
||||
final stopwatch = Stopwatch()..start();
|
||||
final res = await function();
|
||||
stopwatch.stop();
|
||||
@@ -328,6 +337,21 @@ class Utils {
|
||||
}
|
||||
return await function();
|
||||
}
|
||||
|
||||
int fastHash(String string) {
|
||||
var hash = 0xcbf29ce484222325;
|
||||
|
||||
var i = 0;
|
||||
while (i < string.length) {
|
||||
final codeUnit = string.codeUnitAt(i++);
|
||||
hash ^= codeUnit >> 8;
|
||||
hash *= 0x100000001b3;
|
||||
hash ^= codeUnit & 0xFF;
|
||||
hash *= 0x100000001b3;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
final utils = Utils();
|
||||
|
||||
@@ -2,14 +2,21 @@ import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/config.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:screen_retriever/screen_retriever.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class Window {
|
||||
Future<void> init(int version) async {
|
||||
final props = globalState.config.windowProps;
|
||||
static Window? _instance;
|
||||
|
||||
Window._internal();
|
||||
|
||||
factory Window() {
|
||||
_instance ??= Window._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
Future<void> init(int version, WindowProps props) async {
|
||||
final acquire = await singleInstanceLock.acquire();
|
||||
if (!acquire) {
|
||||
exit(0);
|
||||
|
||||
1843
lib/controller.dart
1843
lib/controller.dart
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/core/core.dart';
|
||||
import 'package:fl_clash/core/interface.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
@@ -67,25 +65,23 @@ class CoreController {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> shutdown() async {
|
||||
await _interface.shutdown();
|
||||
Future<void> shutdown(bool isUser) async {
|
||||
await _interface.shutdown(isUser);
|
||||
}
|
||||
|
||||
FutureOr<bool> get isInit => _interface.isInit;
|
||||
|
||||
Future<String> validateConfig(String data) async {
|
||||
final path = await appPath.validateFilePath;
|
||||
await globalState.genValidateFile(path, data);
|
||||
Future<String> validateConfig(String path) async {
|
||||
final res = await _interface.validateConfig(path);
|
||||
await File(path).delete();
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<String> validateConfigFormBytes(Uint8List bytes) async {
|
||||
final path = await appPath.validateFilePath;
|
||||
await globalState.genValidateFileFormBytes(path, bytes);
|
||||
Future<String> validateConfigWithData(String data) async {
|
||||
final path = await appPath.tempFilePath;
|
||||
final file = File(path);
|
||||
await file.safeWriteAsString(data);
|
||||
final res = await _interface.validateConfig(path);
|
||||
await File(path).delete();
|
||||
await File(path).safeDelete();
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -111,33 +107,16 @@ class CoreController {
|
||||
required Map<String, String> selectedMap,
|
||||
required String defaultTestUrl,
|
||||
}) async {
|
||||
final proxies = await _interface.getProxies();
|
||||
return Isolate.run<List<Group>>(() {
|
||||
if (proxies.isEmpty) return [];
|
||||
final groupNames = [
|
||||
UsedProxy.GLOBAL.name,
|
||||
...(proxies[UsedProxy.GLOBAL.name]['all'] as List).where((e) {
|
||||
final proxy = proxies[e] ?? {};
|
||||
return GroupTypeExtension.valueList.contains(proxy['type']);
|
||||
}),
|
||||
];
|
||||
final groupsRaw = groupNames.map((groupName) {
|
||||
final group = proxies[groupName];
|
||||
group['all'] = ((group['all'] ?? []) as List)
|
||||
.map((name) => proxies[name])
|
||||
.where((proxy) => proxy != null)
|
||||
.toList();
|
||||
return group;
|
||||
}).toList();
|
||||
final groups = groupsRaw.map((e) => Group.fromJson(e)).toList();
|
||||
return computeSort(
|
||||
groups: groups,
|
||||
final proxiesData = await _interface.getProxies();
|
||||
return toGroupsTask(
|
||||
ComputeGroupsState(
|
||||
proxiesData: proxiesData,
|
||||
sortType: sortType,
|
||||
delayMap: delayMap,
|
||||
selectedMap: selectedMap,
|
||||
defaultTestUrl: defaultTestUrl,
|
||||
);
|
||||
});
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async {
|
||||
@@ -168,13 +147,11 @@ class CoreController {
|
||||
if (externalProvidersRawString.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
return Isolate.run<List<ExternalProvider>>(() {
|
||||
final externalProviders =
|
||||
(json.decode(externalProvidersRawString) as List<dynamic>)
|
||||
.map((item) => ExternalProvider.fromJson(item))
|
||||
.toList();
|
||||
return externalProviders;
|
||||
});
|
||||
final externalProviders =
|
||||
(await externalProvidersRawString.commonToJSON<List<dynamic>>())
|
||||
.map((item) => ExternalProvider.fromJson(item))
|
||||
.toList();
|
||||
return externalProviders;
|
||||
}
|
||||
|
||||
Future<ExternalProvider?> getExternalProvider(
|
||||
@@ -220,11 +197,14 @@ class CoreController {
|
||||
return Delay.fromJson(json.decode(data));
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getConfig(String id) async {
|
||||
final profilePath = await appPath.getProfilePath(id);
|
||||
Future<Map<String, dynamic>> getConfig(int id) async {
|
||||
final profilePath = await appPath.getProfilePath(id.toString());
|
||||
final res = await _interface.getConfig(profilePath);
|
||||
if (res.isSuccess) {
|
||||
return res.data;
|
||||
final data = Map<String, dynamic>.from(res.data);
|
||||
data['rules'] = data['rule'];
|
||||
data.remove('rule');
|
||||
return data;
|
||||
} else {
|
||||
throw res.message;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
@@ -12,7 +11,7 @@ mixin CoreInterface {
|
||||
|
||||
Future<String> preload();
|
||||
|
||||
Future<bool> shutdown();
|
||||
Future<bool> shutdown(bool isUser);
|
||||
|
||||
Future<bool> get isInit;
|
||||
|
||||
@@ -28,7 +27,7 @@ mixin CoreInterface {
|
||||
|
||||
Future<String> setupConfig(SetupParams setupParams);
|
||||
|
||||
Future<Map> getProxies();
|
||||
Future<ProxiesData> getProxies();
|
||||
|
||||
Future<String> changeProxy(ChangeProxyParams changeProxyParams);
|
||||
|
||||
@@ -87,7 +86,9 @@ abstract class CoreHandlerInterface with CoreInterface {
|
||||
Duration? timeout,
|
||||
}) async {
|
||||
try {
|
||||
await completer.future.timeout(const Duration(seconds: 10));
|
||||
if (!completer.isCompleted) {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
commonPrint.log(
|
||||
'Invoke pre ${method.name} timeout $e',
|
||||
@@ -95,13 +96,13 @@ abstract class CoreHandlerInterface with CoreInterface {
|
||||
);
|
||||
return null;
|
||||
}
|
||||
if (kDebugMode) {
|
||||
if (kDebugMode && watchExecution) {
|
||||
commonPrint.log('Invoke ${method.name} ${DateTime.now()} $data');
|
||||
}
|
||||
|
||||
return utils.handleWatch(
|
||||
return await utils.handleWatch(
|
||||
function: () async {
|
||||
return await invoke(method: method, data: data, timeout: timeout);
|
||||
return await invoke<T>(method: method, data: data, timeout: timeout);
|
||||
},
|
||||
onWatch: (data, elapsedMilliseconds) {
|
||||
commonPrint.log('Invoke ${method.name} ${elapsedMilliseconds}ms');
|
||||
@@ -132,7 +133,7 @@ abstract class CoreHandlerInterface with CoreInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> shutdown();
|
||||
Future<bool> shutdown(bool isUser);
|
||||
|
||||
@override
|
||||
Future<bool> get isInit async {
|
||||
@@ -164,16 +165,15 @@ abstract class CoreHandlerInterface with CoreInterface {
|
||||
|
||||
@override
|
||||
Future<Result> getConfig(String path) async {
|
||||
return await _invoke<Result>(method: ActionMethod.getConfig, data: path) ??
|
||||
Result.success({});
|
||||
final res = await _invoke(method: ActionMethod.getConfig, data: path);
|
||||
return res ?? Result.success({});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> setupConfig(SetupParams setupParams) async {
|
||||
final data = await Isolate.run(() => json.encode(setupParams));
|
||||
return await _invoke<String>(
|
||||
method: ActionMethod.setupConfig,
|
||||
data: data,
|
||||
data: json.encode(setupParams),
|
||||
) ??
|
||||
'';
|
||||
}
|
||||
@@ -184,9 +184,13 @@ abstract class CoreHandlerInterface with CoreInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map> getProxies() async {
|
||||
final map = await _invoke<Map>(method: ActionMethod.getProxies);
|
||||
return map ?? {};
|
||||
Future<ProxiesData> getProxies() async {
|
||||
final data = await _invoke<Map<String, dynamic>>(
|
||||
method: ActionMethod.getProxies,
|
||||
);
|
||||
return data != null
|
||||
? ProxiesData.fromJson(data)
|
||||
: ProxiesData(proxies: {}, all: []);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/core.dart';
|
||||
import 'package:fl_clash/plugins/service.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
|
||||
import 'interface.dart';
|
||||
|
||||
@@ -22,9 +22,7 @@ class CoreLib extends CoreHandlerInterface {
|
||||
return res ?? '';
|
||||
}
|
||||
_connectedCompleter.complete(true);
|
||||
final syncRes = await service?.syncAndroidState(
|
||||
globalState.getAndroidState(),
|
||||
);
|
||||
final syncRes = await service?.syncState(appController.sharedState);
|
||||
return syncRes ?? '';
|
||||
}
|
||||
|
||||
@@ -39,10 +37,12 @@ class CoreLib extends CoreHandlerInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> shutdown() async {
|
||||
await service?.shutdown();
|
||||
Future<bool> shutdown(_) async {
|
||||
if (!_connectedCompleter.isCompleted) {
|
||||
return false;
|
||||
}
|
||||
_connectedCompleter = Completer();
|
||||
return true;
|
||||
return service?.shutdown() ?? true;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -16,6 +16,8 @@ class CoreService extends CoreHandlerInterface {
|
||||
|
||||
Completer<Socket> _socketCompleter = Completer();
|
||||
|
||||
Completer<bool> _shutdownCompleter = Completer();
|
||||
|
||||
final Map<String, Completer> _callbackCompleterMap = {};
|
||||
|
||||
Process? _process;
|
||||
@@ -35,6 +37,9 @@ class CoreService extends CoreHandlerInterface {
|
||||
if (result.id?.isEmpty == true) {
|
||||
coreEventManager.sendEvent(CoreEvent.fromJson(result.data));
|
||||
}
|
||||
if (completer?.isCompleted == true) {
|
||||
return;
|
||||
}
|
||||
completer?.complete(data);
|
||||
}
|
||||
|
||||
@@ -70,11 +75,15 @@ class CoreService extends CoreHandlerInterface {
|
||||
.transform(uint8ListToListIntConverter)
|
||||
.transform(utf8.decoder)
|
||||
.transform(LineSplitter())
|
||||
.listen((data) {
|
||||
handleResult(ActionResult.fromJson(json.decode(data.trim())));
|
||||
.listen((data) async {
|
||||
final dataJson = await data.trim().commonToJSON<dynamic>();
|
||||
handleResult(ActionResult.fromJson(dataJson));
|
||||
})
|
||||
.onDone(() {
|
||||
_handleInvokeCrashEvent();
|
||||
if (!_shutdownCompleter.isCompleted) {
|
||||
_shutdownCompleter.complete(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,7 +95,7 @@ class CoreService extends CoreHandlerInterface {
|
||||
|
||||
Future<void> start() async {
|
||||
if (_process != null) {
|
||||
await shutdown();
|
||||
await shutdown(false);
|
||||
}
|
||||
final serverSocket = await _serverCompleter.future;
|
||||
final arg = system.isWindows
|
||||
@@ -112,7 +121,7 @@ class CoreService extends CoreHandlerInterface {
|
||||
@override
|
||||
destroy() async {
|
||||
final server = await _serverCompleter.future;
|
||||
await shutdown();
|
||||
await shutdown(false);
|
||||
await server.close();
|
||||
await _deleteSocketFile();
|
||||
return true;
|
||||
@@ -126,9 +135,7 @@ class CoreService extends CoreHandlerInterface {
|
||||
Future<void> _deleteSocketFile() async {
|
||||
if (!system.isWindows) {
|
||||
final file = File(unixSocketPath);
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
}
|
||||
await file.safeDelete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,12 +143,16 @@ class CoreService extends CoreHandlerInterface {
|
||||
if (_socketCompleter.isCompleted) {
|
||||
final socket = await _socketCompleter.future;
|
||||
_socketCompleter = Completer();
|
||||
socket.close();
|
||||
await socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
shutdown() async {
|
||||
shutdown(bool isUser) async {
|
||||
if (!_socketCompleter.isCompleted && _process == null) {
|
||||
return false;
|
||||
}
|
||||
_shutdownCompleter = Completer();
|
||||
await _destroySocket();
|
||||
_clearCompleter();
|
||||
if (system.isWindows) {
|
||||
@@ -149,7 +160,11 @@ class CoreService extends CoreHandlerInterface {
|
||||
}
|
||||
_process?.kill();
|
||||
_process = null;
|
||||
return true;
|
||||
if (isUser) {
|
||||
return _shutdownCompleter.future;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void _clearCompleter() {
|
||||
|
||||
78
lib/database/database.dart
Normal file
78
lib/database/database.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
|
||||
part 'generated/database.g.dart';
|
||||
part 'links.dart';
|
||||
part 'profiles.dart';
|
||||
part 'rules.dart';
|
||||
part 'scripts.dart';
|
||||
|
||||
@DriftDatabase(
|
||||
tables: [Profiles, Scripts, Rules, ProfileRuleLinks],
|
||||
daos: [ProfilesDao, ScriptsDao, RulesDao],
|
||||
)
|
||||
class Database extends _$Database {
|
||||
Database([QueryExecutor? executor]) : super(executor ?? _openConnection());
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
||||
static LazyDatabase _openConnection() {
|
||||
return LazyDatabase(() async {
|
||||
final databaseFile = File(await appPath.databasePath);
|
||||
return NativeDatabase.createInBackground(databaseFile);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> restore(
|
||||
List<Profile> profiles,
|
||||
List<Script> scripts,
|
||||
List<Rule> rules,
|
||||
List<ProfileRuleLink> links, {
|
||||
bool isOverride = false,
|
||||
}) async {
|
||||
if (profiles.isNotEmpty ||
|
||||
scripts.isNotEmpty ||
|
||||
rules.isNotEmpty ||
|
||||
links.isNotEmpty) {
|
||||
await batch((b) {
|
||||
isOverride
|
||||
? profilesDao.setAllWithBatch(b, profiles)
|
||||
: profilesDao.putAllWithBatch(
|
||||
b,
|
||||
profiles.map((item) => item.toCompanion()),
|
||||
);
|
||||
scriptsDao.setAllWithBatch(b, scripts);
|
||||
rulesDao.restoreWithBatch(b, rules, links);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TableInfoExt<Tbl extends Table, Row> on TableInfo<Tbl, Row> {
|
||||
void setAll(
|
||||
Batch batch,
|
||||
Iterable<Insertable<Row>> items, {
|
||||
required Expression<bool> Function(Tbl tbl) deleteFilter,
|
||||
}) async {
|
||||
batch.insertAllOnConflictUpdate(this, items);
|
||||
batch.deleteWhere(this, deleteFilter);
|
||||
}
|
||||
|
||||
Future<int> remove(Expression<bool> Function(Tbl tbl) filter) async {
|
||||
return await (delete()..where(filter)).go();
|
||||
}
|
||||
|
||||
Future<int> put(Insertable<Row> item) async {
|
||||
return await insertOnConflictUpdate(item);
|
||||
}
|
||||
}
|
||||
|
||||
final database = Database();
|
||||
2941
lib/database/generated/database.g.dart
Normal file
2941
lib/database/generated/database.g.dart
Normal file
File diff suppressed because it is too large
Load Diff
52
lib/database/links.dart
Normal file
52
lib/database/links.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
part of 'database.dart';
|
||||
|
||||
@DataClassName('RawProfileRuleLink')
|
||||
@TableIndex(
|
||||
name: 'idx_profile_scene_order',
|
||||
columns: {#profileId, #scene, #order},
|
||||
)
|
||||
class ProfileRuleLinks extends Table {
|
||||
@override
|
||||
String get tableName => 'profile_rule_mapping';
|
||||
|
||||
TextColumn get id => text()();
|
||||
|
||||
IntColumn get profileId => integer().nullable().references(
|
||||
Profiles,
|
||||
#id,
|
||||
onDelete: KeyAction.cascade,
|
||||
)();
|
||||
|
||||
IntColumn get ruleId =>
|
||||
integer().references(Rules, #id, onDelete: KeyAction.cascade)();
|
||||
|
||||
TextColumn get scene => textEnum<RuleScene>().nullable()();
|
||||
|
||||
TextColumn get order => text().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
extension RawProfileRuleLinkExt on RawProfileRuleLink {
|
||||
ProfileRuleLink toLink() {
|
||||
return ProfileRuleLink(
|
||||
profileId: profileId,
|
||||
ruleId: ruleId,
|
||||
scene: scene,
|
||||
order: order,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension ProfileRuleLinksCompanionExt on ProfileRuleLink {
|
||||
ProfileRuleLinksCompanion toCompanion() {
|
||||
return ProfileRuleLinksCompanion.insert(
|
||||
id: key,
|
||||
ruleId: ruleId,
|
||||
scene: Value(scene),
|
||||
profileId: Value(profileId),
|
||||
order: Value(order),
|
||||
);
|
||||
}
|
||||
}
|
||||
168
lib/database/profiles.dart
Normal file
168
lib/database/profiles.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
part of 'database.dart';
|
||||
|
||||
@DataClassName('RawProfile')
|
||||
class Profiles extends Table {
|
||||
@override
|
||||
String get tableName => 'profiles';
|
||||
|
||||
IntColumn get id => integer()();
|
||||
|
||||
TextColumn get label => text()();
|
||||
|
||||
TextColumn get currentGroupName => text().nullable()();
|
||||
|
||||
TextColumn get url => text()();
|
||||
|
||||
DateTimeColumn get lastUpdateDate => dateTime().nullable()();
|
||||
|
||||
TextColumn get overwriteType => textEnum<OverwriteType>()();
|
||||
|
||||
IntColumn get scriptId => integer().nullable()();
|
||||
|
||||
IntColumn get autoUpdateDurationMillis => integer()();
|
||||
|
||||
TextColumn get subscriptionInfo =>
|
||||
text().map(const SubscriptionInfoConverter()).nullable()();
|
||||
|
||||
BoolColumn get autoUpdate => boolean()();
|
||||
|
||||
TextColumn get selectedMap => text().map(const StringMapConverter())();
|
||||
|
||||
TextColumn get unfoldSet => text().map(const StringSetConverter())();
|
||||
|
||||
IntColumn get order => integer().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
class SubscriptionInfoConverter
|
||||
extends TypeConverter<SubscriptionInfo?, String?> {
|
||||
const SubscriptionInfoConverter();
|
||||
|
||||
@override
|
||||
SubscriptionInfo? fromSql(String? fromDb) {
|
||||
if (fromDb == null) return null;
|
||||
return SubscriptionInfo.fromJson(json.decode(fromDb));
|
||||
}
|
||||
|
||||
@override
|
||||
String? toSql(SubscriptionInfo? value) {
|
||||
if (value == null) return null;
|
||||
return json.encode(value.toJson());
|
||||
}
|
||||
}
|
||||
|
||||
@DriftAccessor(tables: [Profiles])
|
||||
class ProfilesDao extends DatabaseAccessor<Database> with _$ProfilesDaoMixin {
|
||||
ProfilesDao(super.attachedDatabase);
|
||||
|
||||
Selectable<Profile> all() {
|
||||
final stmt = profiles.select();
|
||||
stmt.orderBy([
|
||||
(t) => OrderingTerm(expression: t.order, nulls: NullsOrder.last),
|
||||
(t) => OrderingTerm.asc(t.id),
|
||||
]);
|
||||
return stmt.map((item) => item.toProfile());
|
||||
}
|
||||
|
||||
Future<void> setAll(Iterable<Profile> profiles) async {
|
||||
await batch((b) async {
|
||||
setAllWithBatch(b, profiles);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> putAll<T extends Table, D extends DataClass>(
|
||||
Iterable<Insertable<D>> items,
|
||||
) async {
|
||||
await batch((b) async {
|
||||
putAllWithBatch(b, items);
|
||||
});
|
||||
}
|
||||
|
||||
void putAllWithBatch<T extends Table, D extends DataClass>(
|
||||
Batch batch,
|
||||
Iterable<Insertable<D>> items,
|
||||
) {
|
||||
batch.insertAllOnConflictUpdate(profiles, items);
|
||||
}
|
||||
|
||||
void setAllWithBatch(Batch batch, Iterable<Profile> profiles) {
|
||||
final List<ProfilesCompanion> items = [];
|
||||
final List<int> ids = [];
|
||||
profiles.forEachIndexed((index, profile) {
|
||||
ids.add(profile.id);
|
||||
items.add(profile.toCompanion(index));
|
||||
});
|
||||
|
||||
this.profiles.setAll(batch, items, deleteFilter: (t) => t.id.isNotIn(ids));
|
||||
}
|
||||
}
|
||||
|
||||
class StringMapConverter extends TypeConverter<Map<String, String>, String> {
|
||||
const StringMapConverter();
|
||||
|
||||
@override
|
||||
Map<String, String> fromSql(String fromDb) {
|
||||
return Map<String, String>.from(json.decode(fromDb));
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(Map<String, String> value) {
|
||||
return json.encode(value);
|
||||
}
|
||||
}
|
||||
|
||||
class StringSetConverter extends TypeConverter<Set<String>, String> {
|
||||
const StringSetConverter();
|
||||
|
||||
@override
|
||||
Set<String> fromSql(String fromDb) {
|
||||
return Set<String>.from(json.decode(fromDb));
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(Set<String> value) {
|
||||
return json.encode(value.toList());
|
||||
}
|
||||
}
|
||||
|
||||
extension RawProfilExt on RawProfile {
|
||||
Profile toProfile() {
|
||||
return Profile(
|
||||
id: id,
|
||||
label: label,
|
||||
currentGroupName: currentGroupName,
|
||||
url: url,
|
||||
lastUpdateDate: lastUpdateDate,
|
||||
autoUpdateDuration: Duration(milliseconds: autoUpdateDurationMillis),
|
||||
subscriptionInfo: subscriptionInfo,
|
||||
autoUpdate: autoUpdate,
|
||||
selectedMap: selectedMap,
|
||||
unfoldSet: unfoldSet,
|
||||
overwriteType: overwriteType,
|
||||
scriptId: scriptId,
|
||||
order: order,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension ProfilesCompanionExt on Profile {
|
||||
ProfilesCompanion toCompanion([int? order]) {
|
||||
return ProfilesCompanion.insert(
|
||||
id: Value(id),
|
||||
label: label,
|
||||
currentGroupName: Value(currentGroupName),
|
||||
url: url,
|
||||
lastUpdateDate: Value(lastUpdateDate),
|
||||
autoUpdateDurationMillis: autoUpdateDuration.inMilliseconds,
|
||||
subscriptionInfo: Value(subscriptionInfo),
|
||||
autoUpdate: autoUpdate,
|
||||
selectedMap: selectedMap,
|
||||
unfoldSet: unfoldSet,
|
||||
overwriteType: overwriteType,
|
||||
scriptId: Value(scriptId),
|
||||
order: Value(order ?? this.order),
|
||||
);
|
||||
}
|
||||
}
|
||||
283
lib/database/rules.dart
Normal file
283
lib/database/rules.dart
Normal file
@@ -0,0 +1,283 @@
|
||||
part of 'database.dart';
|
||||
|
||||
@DataClassName('RawRule')
|
||||
class Rules extends Table {
|
||||
@override
|
||||
String get tableName => 'rules';
|
||||
|
||||
IntColumn get id => integer()();
|
||||
|
||||
TextColumn get value => text()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
@DriftAccessor(tables: [Rules, ProfileRuleLinks])
|
||||
class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
|
||||
RulesDao(super.attachedDatabase);
|
||||
|
||||
Selectable<Rule> allGlobalAddedRules() {
|
||||
return _get();
|
||||
}
|
||||
|
||||
Selectable<Rule> allProfileAddedRules(int profileId) {
|
||||
return _get(profileId: profileId, scene: RuleScene.added);
|
||||
}
|
||||
|
||||
Selectable<Rule> allProfileDisabledRules(int profileId) {
|
||||
return _get(profileId: profileId, scene: RuleScene.disabled);
|
||||
}
|
||||
|
||||
Selectable<Rule> allAddedRules(int profileId) {
|
||||
final disabledIdsQuery = selectOnly(profileRuleLinks)
|
||||
..addColumns([profileRuleLinks.ruleId])
|
||||
..where(
|
||||
profileRuleLinks.profileId.equals(profileId) &
|
||||
profileRuleLinks.scene.equalsValue(RuleScene.disabled),
|
||||
);
|
||||
|
||||
final query = select(rules).join([
|
||||
innerJoin(profileRuleLinks, profileRuleLinks.ruleId.equalsExp(rules.id)),
|
||||
]);
|
||||
|
||||
query.where(
|
||||
(profileRuleLinks.profileId.isNull() |
|
||||
(profileRuleLinks.profileId.equals(profileId) &
|
||||
profileRuleLinks.scene.equalsValue(RuleScene.added))) &
|
||||
profileRuleLinks.ruleId.isNotInQuery(disabledIdsQuery),
|
||||
);
|
||||
|
||||
query.orderBy([
|
||||
OrderingTerm.desc(
|
||||
profileRuleLinks.profileId.isNull().caseMatch<int>(
|
||||
when: {const Constant(true): const Constant(1)},
|
||||
orElse: const Constant(0),
|
||||
),
|
||||
),
|
||||
OrderingTerm.desc(profileRuleLinks.order),
|
||||
]);
|
||||
|
||||
return query.map((row) {
|
||||
final ruleData = row.readTable(rules);
|
||||
final order = row.read(profileRuleLinks.order);
|
||||
return ruleData.toRule(order);
|
||||
});
|
||||
}
|
||||
|
||||
void restoreWithBatch(
|
||||
Batch batch,
|
||||
Iterable<Rule> rules,
|
||||
Iterable<ProfileRuleLink> links,
|
||||
) {
|
||||
batch.insertAllOnConflictUpdate(
|
||||
this.rules,
|
||||
rules.map((item) => item.toCompanion()),
|
||||
);
|
||||
final ruleIds = rules.map((item) => item.id);
|
||||
batch.deleteWhere(this.rules, (t) => t.id.isNotIn(ruleIds));
|
||||
batch.insertAllOnConflictUpdate(
|
||||
profileRuleLinks,
|
||||
links.map((item) => item.toCompanion()),
|
||||
);
|
||||
final linkKeys = links.map((item) => item.key);
|
||||
batch.deleteWhere(profileRuleLinks, (t) => t.id.isNotIn(linkKeys));
|
||||
}
|
||||
|
||||
Future<void> delRules(Iterable<int> ruleIds) {
|
||||
return _delAll(ruleIds);
|
||||
}
|
||||
|
||||
Future<void> putGlobalRule(Rule rule) {
|
||||
return _put(rule);
|
||||
}
|
||||
|
||||
Future<void> putProfileAddedRule(int profileId, Rule rule) {
|
||||
return _put(rule, profileId: profileId, scene: RuleScene.added);
|
||||
}
|
||||
|
||||
Future<void> putProfileDisabledRule(int profileId, Rule rule) {
|
||||
return _put(rule, profileId: profileId, scene: RuleScene.added);
|
||||
}
|
||||
|
||||
Future<void> putGlobalRules(Iterable<Rule> rules) {
|
||||
return _putAll(rules);
|
||||
}
|
||||
|
||||
Future<void> setGlobalRules(Iterable<Rule> rules) {
|
||||
return _set(rules);
|
||||
}
|
||||
|
||||
Future<int> putDisabledLink(int profileId, int ruleId) async {
|
||||
return await profileRuleLinks.insertOnConflictUpdate(
|
||||
ProfileRuleLink(
|
||||
ruleId: ruleId,
|
||||
profileId: profileId,
|
||||
scene: RuleScene.disabled,
|
||||
).toCompanion(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> delDisabledLink(int profileId, int ruleId) async {
|
||||
return await profileRuleLinks.deleteOne(
|
||||
ProfileRuleLink(
|
||||
profileId: profileId,
|
||||
ruleId: ruleId,
|
||||
scene: RuleScene.disabled,
|
||||
).toCompanion(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> orderGlobalRule({
|
||||
required int ruleId,
|
||||
required String order,
|
||||
}) async {
|
||||
return await _order(ruleId: ruleId, order: order);
|
||||
}
|
||||
|
||||
Future<int> orderProfileAddedRule(
|
||||
int profileId, {
|
||||
required int ruleId,
|
||||
required String order,
|
||||
}) async {
|
||||
return await _order(
|
||||
ruleId: ruleId,
|
||||
order: order,
|
||||
profileId: profileId,
|
||||
scene: RuleScene.added,
|
||||
);
|
||||
}
|
||||
|
||||
Selectable<Rule> _get({int? profileId, RuleScene? scene}) {
|
||||
final query = select(rules).join([
|
||||
innerJoin(profileRuleLinks, profileRuleLinks.ruleId.equalsExp(rules.id)),
|
||||
]);
|
||||
|
||||
query.where(
|
||||
profileId == null
|
||||
? profileRuleLinks.profileId.isNull()
|
||||
: profileRuleLinks.profileId.equals(profileId) &
|
||||
profileRuleLinks.scene.equalsValue(scene),
|
||||
);
|
||||
|
||||
query.orderBy([
|
||||
OrderingTerm.desc(profileRuleLinks.order),
|
||||
OrderingTerm.desc(profileRuleLinks.id),
|
||||
]);
|
||||
|
||||
return query.map((row) {
|
||||
return row.readTable(rules).toRule(row.read(profileRuleLinks.order));
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> _order({
|
||||
required int ruleId,
|
||||
required String order,
|
||||
int? profileId,
|
||||
RuleScene? scene,
|
||||
}) async {
|
||||
final stmt = profileRuleLinks.update();
|
||||
stmt.where((t) {
|
||||
return (profileId == null
|
||||
? t.profileId.isNull()
|
||||
: t.profileId.equals(profileId)) &
|
||||
t.ruleId.equals(ruleId) &
|
||||
t.scene.equalsValue(scene);
|
||||
});
|
||||
return await stmt.write(ProfileRuleLinksCompanion(order: Value(order)));
|
||||
}
|
||||
|
||||
Future<int> _put(Rule rule, {int? profileId, RuleScene? scene}) async {
|
||||
return transaction(() async {
|
||||
final row = await rules.insertOnConflictUpdate(rule.toCompanion());
|
||||
if (row == 0) {
|
||||
return 0;
|
||||
}
|
||||
return await profileRuleLinks.insertOnConflictUpdate(
|
||||
ProfileRuleLink(
|
||||
ruleId: rule.id,
|
||||
profileId: profileId,
|
||||
scene: scene,
|
||||
).toCompanion(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _delAll(Iterable<int> ruleIds) async {
|
||||
await rules.deleteWhere((t) => t.id.isIn(ruleIds));
|
||||
}
|
||||
|
||||
Future<void> _putAll(
|
||||
Iterable<Rule> rules, {
|
||||
int? profileId,
|
||||
RuleScene? scene,
|
||||
}) async {
|
||||
await batch((b) {
|
||||
b.insertAllOnConflictUpdate(
|
||||
this.rules,
|
||||
rules.map((item) => item.toCompanion()),
|
||||
);
|
||||
b.insertAllOnConflictUpdate(
|
||||
profileRuleLinks,
|
||||
rules.map(
|
||||
(item) => ProfileRuleLink(
|
||||
ruleId: item.id,
|
||||
profileId: profileId,
|
||||
scene: scene,
|
||||
).toCompanion(),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _set(
|
||||
Iterable<Rule> rules, {
|
||||
int? profileId,
|
||||
RuleScene? scene,
|
||||
}) async {
|
||||
await batch((b) {
|
||||
b.insertAllOnConflictUpdate(
|
||||
this.rules,
|
||||
rules.map((item) => item.toCompanion()),
|
||||
);
|
||||
|
||||
b.deleteWhere(
|
||||
profileRuleLinks,
|
||||
(t) =>
|
||||
(profileId == null
|
||||
? t.profileId.isNull()
|
||||
: t.profileId.equals(profileId)) &
|
||||
(scene == null ? const Constant(true) : t.scene.equalsValue(scene)),
|
||||
);
|
||||
|
||||
b.insertAllOnConflictUpdate(
|
||||
profileRuleLinks,
|
||||
rules.map(
|
||||
(item) => ProfileRuleLink(
|
||||
ruleId: item.id,
|
||||
profileId: profileId,
|
||||
scene: scene,
|
||||
).toCompanion(),
|
||||
),
|
||||
);
|
||||
|
||||
b.deleteWhere(this.rules, (r) {
|
||||
final linkedIds = selectOnly(profileRuleLinks);
|
||||
linkedIds.addColumns([profileRuleLinks.ruleId]);
|
||||
return r.id.isNotInQuery(linkedIds);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension RawRuleExt on RawRule {
|
||||
Rule toRule([String? order]) {
|
||||
return Rule(id: id, value: value, order: order);
|
||||
}
|
||||
}
|
||||
|
||||
extension RulesCompanionExt on Rule {
|
||||
RulesCompanion toCompanion() {
|
||||
return RulesCompanion.insert(id: Value(id), value: value);
|
||||
}
|
||||
}
|
||||
63
lib/database/scripts.dart
Normal file
63
lib/database/scripts.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
part of 'database.dart';
|
||||
|
||||
@DataClassName('RawScript')
|
||||
class Scripts extends Table {
|
||||
@override
|
||||
String get tableName => 'scripts';
|
||||
|
||||
IntColumn get id => integer()();
|
||||
|
||||
TextColumn get label => text()();
|
||||
|
||||
DateTimeColumn get lastUpdateTime => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
@DriftAccessor(tables: [Scripts])
|
||||
class ScriptsDao extends DatabaseAccessor<Database> with _$ScriptsDaoMixin {
|
||||
ScriptsDao(super.attachedDatabase);
|
||||
|
||||
Selectable<Script> all() {
|
||||
return scripts.select().map((item) => item.toScript());
|
||||
}
|
||||
|
||||
Selectable<Script> get(int scriptId) {
|
||||
final stmt = scripts.select();
|
||||
stmt.where((t) => t.id.equals(scriptId));
|
||||
return stmt.map((it) => it.toScript());
|
||||
}
|
||||
|
||||
Future<void> setAll(Iterable<Script> scripts) async {
|
||||
await batch((b) async {
|
||||
await setAllWithBatch(b, scripts);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> setAllWithBatch(Batch batch, Iterable<Script> scripts) async {
|
||||
final List<ScriptsCompanion> items = [];
|
||||
final List<int> ids = [];
|
||||
for (final script in scripts) {
|
||||
ids.add(script.id);
|
||||
items.add(script.toCompanion());
|
||||
}
|
||||
this.scripts.setAll(batch, items, deleteFilter: (t) => t.id.isNotIn(ids));
|
||||
}
|
||||
}
|
||||
|
||||
extension RawScriptExt on RawScript {
|
||||
Script toScript() {
|
||||
return Script(id: id, label: label, lastUpdateTime: lastUpdateTime);
|
||||
}
|
||||
}
|
||||
|
||||
extension ScriptsCompanionExt on Script {
|
||||
ScriptsCompanion toCompanion() {
|
||||
return ScriptsCompanion.insert(
|
||||
id: Value(id),
|
||||
label: label,
|
||||
lastUpdateTime: lastUpdateTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ enum InvokeMessageType { protect, process }
|
||||
|
||||
enum FindProcessMode { always, off }
|
||||
|
||||
enum RecoveryOption { all, onlyProfiles }
|
||||
enum RestoreOption { all, onlyProfiles }
|
||||
|
||||
enum ChipType { action, delete }
|
||||
|
||||
@@ -260,8 +260,8 @@ enum AuthorizeCode { none, success, error }
|
||||
enum WindowsHelperServiceStatus { none, presence, running }
|
||||
|
||||
enum FunctionTag {
|
||||
updateClashConfig,
|
||||
setupClashConfig,
|
||||
updateConfig,
|
||||
setupConfig,
|
||||
updateStatus,
|
||||
updateGroups,
|
||||
addCheckIpNum,
|
||||
@@ -281,6 +281,7 @@ enum FunctionTag {
|
||||
requests,
|
||||
autoScrollToEnd,
|
||||
loadedProvider,
|
||||
saveSharedFile,
|
||||
}
|
||||
|
||||
enum DashboardWidget {
|
||||
@@ -406,7 +407,7 @@ enum OverwriteType {
|
||||
|
||||
enum RuleTarget { DIRECT, REJECT, MATCH }
|
||||
|
||||
enum RecoveryStrategy { compatible, override }
|
||||
enum RestoreStrategy { compatible, override }
|
||||
|
||||
enum CacheTag { logs, rules, requests, proxiesList }
|
||||
|
||||
@@ -418,4 +419,8 @@ enum ScrollPositionCacheKey { tools, profiles, proxiesList, proxiesTabList }
|
||||
|
||||
enum QueryTag { proxies, access }
|
||||
|
||||
enum LoadingTag { profiles, backup_restore, access, proxies }
|
||||
|
||||
enum CoreStatus { connecting, connected, disconnected }
|
||||
|
||||
enum RuleScene { added, disabled, custom }
|
||||
|
||||
@@ -14,7 +14,7 @@ class RuleItem extends StatelessWidget {
|
||||
final bool isSelected;
|
||||
final bool isEditing;
|
||||
final Rule rule;
|
||||
final void Function(String id) onSelected;
|
||||
final void Function() onSelected;
|
||||
final void Function(Rule rule) onEdit;
|
||||
|
||||
const RuleItem({
|
||||
@@ -31,7 +31,7 @@ class RuleItem extends StatelessWidget {
|
||||
return CommonSelectedListItem(
|
||||
isSelected: isSelected,
|
||||
onSelected: () {
|
||||
onSelected(rule.id);
|
||||
onSelected();
|
||||
},
|
||||
title: Text(
|
||||
rule.value,
|
||||
|
||||
@@ -81,6 +81,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
|
||||
"action_view": MessageLookupByLibrary.simpleMessage("Show/Hide"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("Add"),
|
||||
"addProfile": MessageLookupByLibrary.simpleMessage("Add Profile"),
|
||||
"addRule": MessageLookupByLibrary.simpleMessage("Add rule"),
|
||||
"addedOriginRules": MessageLookupByLibrary.simpleMessage(
|
||||
"Attach on the original rules",
|
||||
@@ -164,11 +165,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Auto update interval (minutes)",
|
||||
),
|
||||
"backup": MessageLookupByLibrary.simpleMessage("Backup"),
|
||||
"backupAndRecovery": MessageLookupByLibrary.simpleMessage(
|
||||
"Backup and Recovery",
|
||||
"backupAndRestore": MessageLookupByLibrary.simpleMessage(
|
||||
"Backup and Restore",
|
||||
),
|
||||
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Sync data via WebDAV or file",
|
||||
"backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Sync data via WebDAV or files",
|
||||
),
|
||||
"backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"),
|
||||
"basicConfig": MessageLookupByLibrary.simpleMessage("Basic configuration"),
|
||||
@@ -425,6 +426,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"internet": MessageLookupByLibrary.simpleMessage("Internet"),
|
||||
"interval": MessageLookupByLibrary.simpleMessage("Interval"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
|
||||
"invalidBackupFile": MessageLookupByLibrary.simpleMessage(
|
||||
"Invalid backup file",
|
||||
),
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
||||
"When turned on it will be able to receive IPv6 traffic",
|
||||
@@ -450,9 +454,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"localBackupDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Backup local data to local",
|
||||
),
|
||||
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Recovery data from file",
|
||||
),
|
||||
"log": MessageLookupByLibrary.simpleMessage("Log"),
|
||||
"logLevel": MessageLookupByLibrary.simpleMessage("LogLevel"),
|
||||
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
|
||||
@@ -504,6 +505,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"networkDetection": MessageLookupByLibrary.simpleMessage(
|
||||
"Network detection",
|
||||
),
|
||||
"networkException": MessageLookupByLibrary.simpleMessage(
|
||||
"Network exception, please check your connection and try again",
|
||||
),
|
||||
"networkRequestException": MessageLookupByLibrary.simpleMessage(
|
||||
"Network request exception, please try again later.",
|
||||
),
|
||||
"networkSpeed": MessageLookupByLibrary.simpleMessage("Network speed"),
|
||||
"networkType": MessageLookupByLibrary.simpleMessage("Network type"),
|
||||
"neutralScheme": MessageLookupByLibrary.simpleMessage("Neutral"),
|
||||
@@ -564,6 +571,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Override the original rule",
|
||||
),
|
||||
"overrideScript": MessageLookupByLibrary.simpleMessage("Override script"),
|
||||
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage("Custom"),
|
||||
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Custom mode, fully customize proxy groups and rules",
|
||||
),
|
||||
"palette": MessageLookupByLibrary.simpleMessage("Palette"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Password"),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("Paste"),
|
||||
@@ -636,27 +647,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Set the Clash listening port",
|
||||
),
|
||||
"proxyProviders": MessageLookupByLibrary.simpleMessage("Proxy providers"),
|
||||
"pruneCache": MessageLookupByLibrary.simpleMessage("Prune cache"),
|
||||
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Pure black mode"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Scan QR code to obtain profile",
|
||||
),
|
||||
"rainbowScheme": MessageLookupByLibrary.simpleMessage("Rainbow"),
|
||||
"recovery": MessageLookupByLibrary.simpleMessage("Recovery"),
|
||||
"recoveryAll": MessageLookupByLibrary.simpleMessage("Recovery all data"),
|
||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
|
||||
"Only recovery profiles",
|
||||
),
|
||||
"recoveryStrategy": MessageLookupByLibrary.simpleMessage(
|
||||
"Recovery strategy",
|
||||
),
|
||||
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage(
|
||||
"Compatible",
|
||||
),
|
||||
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
|
||||
"Override",
|
||||
),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("Recovery success"),
|
||||
"redirPort": MessageLookupByLibrary.simpleMessage("Redir Port"),
|
||||
"redo": MessageLookupByLibrary.simpleMessage("redo"),
|
||||
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
|
||||
@@ -668,9 +665,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"remoteDestination": MessageLookupByLibrary.simpleMessage(
|
||||
"Remote destination",
|
||||
),
|
||||
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Recovery data from WebDAV",
|
||||
),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("Remove"),
|
||||
"rename": MessageLookupByLibrary.simpleMessage("Rename"),
|
||||
"request": MessageLookupByLibrary.simpleMessage("Request"),
|
||||
@@ -695,6 +689,28 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"restartCoreTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Are you sure you want to restart the core?",
|
||||
),
|
||||
"restore": MessageLookupByLibrary.simpleMessage("Restore"),
|
||||
"restoreAllData": MessageLookupByLibrary.simpleMessage("Restore all data"),
|
||||
"restoreException": MessageLookupByLibrary.simpleMessage(
|
||||
"Recovery exception",
|
||||
),
|
||||
"restoreFromFileDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Restore data via file",
|
||||
),
|
||||
"restoreFromWebDAVDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Restore data via WebDAV",
|
||||
),
|
||||
"restoreOnlyConfig": MessageLookupByLibrary.simpleMessage(
|
||||
"Restore configuration files only",
|
||||
),
|
||||
"restoreStrategy": MessageLookupByLibrary.simpleMessage("Restore strategy"),
|
||||
"restoreStrategy_compatible": MessageLookupByLibrary.simpleMessage(
|
||||
"Compatible",
|
||||
),
|
||||
"restoreStrategy_override": MessageLookupByLibrary.simpleMessage(
|
||||
"Override",
|
||||
),
|
||||
"restoreSuccess": MessageLookupByLibrary.simpleMessage("Restore success"),
|
||||
"routeAddress": MessageLookupByLibrary.simpleMessage("Route address"),
|
||||
"routeAddressDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Config listen route address",
|
||||
@@ -806,6 +822,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Remove extra delays such as handshaking",
|
||||
),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("Unknown"),
|
||||
"unknownNetworkError": MessageLookupByLibrary.simpleMessage(
|
||||
"Unknown network error",
|
||||
),
|
||||
"unnamed": MessageLookupByLibrary.simpleMessage("Unnamed"),
|
||||
"update": MessageLookupByLibrary.simpleMessage("Update"),
|
||||
"upload": MessageLookupByLibrary.simpleMessage("Upload"),
|
||||
|
||||
@@ -72,6 +72,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
|
||||
"action_view": MessageLookupByLibrary.simpleMessage("表示/非表示"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("追加"),
|
||||
"addProfile": MessageLookupByLibrary.simpleMessage("プロファイルを追加"),
|
||||
"addRule": MessageLookupByLibrary.simpleMessage("ルールを追加"),
|
||||
"addedOriginRules": MessageLookupByLibrary.simpleMessage("元のルールに追加"),
|
||||
"addedRules": MessageLookupByLibrary.simpleMessage("追加ルール"),
|
||||
@@ -117,9 +118,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"autoUpdate": MessageLookupByLibrary.simpleMessage("自動更新"),
|
||||
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自動更新間隔(分)"),
|
||||
"backup": MessageLookupByLibrary.simpleMessage("バックアップ"),
|
||||
"backupAndRecovery": MessageLookupByLibrary.simpleMessage("バックアップと復元"),
|
||||
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"WebDAVまたはファイルでデータを同期",
|
||||
"backupAndRestore": MessageLookupByLibrary.simpleMessage("バックアップと復元"),
|
||||
"backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"WebDAVまたはファイルを介してデータを同期する",
|
||||
),
|
||||
"backupSuccess": MessageLookupByLibrary.simpleMessage("バックアップ成功"),
|
||||
"basicConfig": MessageLookupByLibrary.simpleMessage("基本設定"),
|
||||
@@ -318,6 +319,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"internet": MessageLookupByLibrary.simpleMessage("インターネット"),
|
||||
"interval": MessageLookupByLibrary.simpleMessage("インターバル"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"),
|
||||
"invalidBackupFile": MessageLookupByLibrary.simpleMessage("無効なバックアップファイル"),
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"),
|
||||
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("IPv6インバウンドを許可"),
|
||||
@@ -337,7 +339,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"loading": MessageLookupByLibrary.simpleMessage("読み込み中..."),
|
||||
"local": MessageLookupByLibrary.simpleMessage("ローカル"),
|
||||
"localBackupDesc": MessageLookupByLibrary.simpleMessage("ローカルにデータをバックアップ"),
|
||||
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("ファイルからデータを復元"),
|
||||
"log": MessageLookupByLibrary.simpleMessage("ログ"),
|
||||
"logLevel": MessageLookupByLibrary.simpleMessage("ログレベル"),
|
||||
"logcat": MessageLookupByLibrary.simpleMessage("ログキャット"),
|
||||
@@ -375,6 +376,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"network": MessageLookupByLibrary.simpleMessage("ネットワーク"),
|
||||
"networkDesc": MessageLookupByLibrary.simpleMessage("ネットワーク関連設定の変更"),
|
||||
"networkDetection": MessageLookupByLibrary.simpleMessage("ネットワーク検出"),
|
||||
"networkException": MessageLookupByLibrary.simpleMessage(
|
||||
"ネットワーク例外、接続を確認してもう一度お試しください",
|
||||
),
|
||||
"networkRequestException": MessageLookupByLibrary.simpleMessage(
|
||||
"ネットワーク要求例外、後でもう一度試してください。",
|
||||
),
|
||||
"networkSpeed": MessageLookupByLibrary.simpleMessage("ネットワーク速度"),
|
||||
"networkType": MessageLookupByLibrary.simpleMessage("ネットワーク種別"),
|
||||
"neutralScheme": MessageLookupByLibrary.simpleMessage("ニュートラル"),
|
||||
@@ -423,6 +430,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"overrideMode": MessageLookupByLibrary.simpleMessage("上書きモード"),
|
||||
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを上書き"),
|
||||
"overrideScript": MessageLookupByLibrary.simpleMessage("上書きスクリプト"),
|
||||
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage("カスタム"),
|
||||
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"カスタムモード、プロキシグループとルールを完全にカスタマイズ可能",
|
||||
),
|
||||
"palette": MessageLookupByLibrary.simpleMessage("パレット"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("パスワード"),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("貼り付け"),
|
||||
@@ -483,19 +494,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"proxyPort": MessageLookupByLibrary.simpleMessage("プロキシポート"),
|
||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("Clashのリスニングポートを設定"),
|
||||
"proxyProviders": MessageLookupByLibrary.simpleMessage("プロキシプロバイダー"),
|
||||
"pruneCache": MessageLookupByLibrary.simpleMessage("キャッシュの削除"),
|
||||
"pureBlackMode": MessageLookupByLibrary.simpleMessage("純黒モード"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("QRコード"),
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("QRコードをスキャンしてプロファイルを取得"),
|
||||
"rainbowScheme": MessageLookupByLibrary.simpleMessage("レインボー"),
|
||||
"recovery": MessageLookupByLibrary.simpleMessage("復元"),
|
||||
"recoveryAll": MessageLookupByLibrary.simpleMessage("全データ復元"),
|
||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("プロファイルのみ復元"),
|
||||
"recoveryStrategy": MessageLookupByLibrary.simpleMessage("リカバリー戦略"),
|
||||
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("互換性"),
|
||||
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
|
||||
"オーバーライド",
|
||||
),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("復元成功"),
|
||||
"redirPort": MessageLookupByLibrary.simpleMessage("Redirポート"),
|
||||
"redo": MessageLookupByLibrary.simpleMessage("やり直す"),
|
||||
"regExp": MessageLookupByLibrary.simpleMessage("正規表現"),
|
||||
@@ -505,9 +508,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"WebDAVにデータをバックアップ",
|
||||
),
|
||||
"remoteDestination": MessageLookupByLibrary.simpleMessage("リモート宛先"),
|
||||
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"WebDAVからデータを復元",
|
||||
),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("削除"),
|
||||
"rename": MessageLookupByLibrary.simpleMessage("リネーム"),
|
||||
"request": MessageLookupByLibrary.simpleMessage("リクエスト"),
|
||||
@@ -526,6 +526,20 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"restart": MessageLookupByLibrary.simpleMessage("再起動"),
|
||||
"restartCoreTip": MessageLookupByLibrary.simpleMessage("コアを再起動してもよろしいですか?"),
|
||||
"restore": MessageLookupByLibrary.simpleMessage("復元"),
|
||||
"restoreAllData": MessageLookupByLibrary.simpleMessage("すべてのデータを復元する"),
|
||||
"restoreException": MessageLookupByLibrary.simpleMessage("復元例外"),
|
||||
"restoreFromFileDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"ファイルを介してデータを復元する",
|
||||
),
|
||||
"restoreFromWebDAVDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"WebDAVを介してデータを復元する",
|
||||
),
|
||||
"restoreOnlyConfig": MessageLookupByLibrary.simpleMessage("設定ファイルのみを復元する"),
|
||||
"restoreStrategy": MessageLookupByLibrary.simpleMessage("復元ストラテジー"),
|
||||
"restoreStrategy_compatible": MessageLookupByLibrary.simpleMessage("互換"),
|
||||
"restoreStrategy_override": MessageLookupByLibrary.simpleMessage("上書き"),
|
||||
"restoreSuccess": MessageLookupByLibrary.simpleMessage("復元に成功しました"),
|
||||
"routeAddress": MessageLookupByLibrary.simpleMessage("ルートアドレス"),
|
||||
"routeAddressDesc": MessageLookupByLibrary.simpleMessage("ルートアドレスを設定"),
|
||||
"routeMode": MessageLookupByLibrary.simpleMessage("ルートモード"),
|
||||
@@ -619,6 +633,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"ハンドシェイクなどの余分な遅延を削除",
|
||||
),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("不明"),
|
||||
"unknownNetworkError": MessageLookupByLibrary.simpleMessage("不明なネットワークエラー"),
|
||||
"unnamed": MessageLookupByLibrary.simpleMessage("無題"),
|
||||
"update": MessageLookupByLibrary.simpleMessage("更新"),
|
||||
"upload": MessageLookupByLibrary.simpleMessage("アップロード"),
|
||||
|
||||
@@ -80,6 +80,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
|
||||
"action_view": MessageLookupByLibrary.simpleMessage("Показать/Скрыть"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("Добавить"),
|
||||
"addProfile": MessageLookupByLibrary.simpleMessage("Добавить профиль"),
|
||||
"addRule": MessageLookupByLibrary.simpleMessage("Добавить правило"),
|
||||
"addedOriginRules": MessageLookupByLibrary.simpleMessage(
|
||||
"Добавить к оригинальным правилам",
|
||||
@@ -161,11 +162,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Интервал автообновления (минуты)",
|
||||
),
|
||||
"backup": MessageLookupByLibrary.simpleMessage("Резервное копирование"),
|
||||
"backupAndRecovery": MessageLookupByLibrary.simpleMessage(
|
||||
"backupAndRestore": MessageLookupByLibrary.simpleMessage(
|
||||
"Резервное копирование и восстановление",
|
||||
),
|
||||
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Синхронизация данных через WebDAV или файл",
|
||||
"backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Синхронизация данных через WebDAV или файлы",
|
||||
),
|
||||
"backupSuccess": MessageLookupByLibrary.simpleMessage(
|
||||
"Резервное копирование успешно",
|
||||
@@ -444,6 +445,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"internet": MessageLookupByLibrary.simpleMessage("Интернет"),
|
||||
"interval": MessageLookupByLibrary.simpleMessage("Интервал"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("Внутренний IP"),
|
||||
"invalidBackupFile": MessageLookupByLibrary.simpleMessage(
|
||||
"Неверный файл резервной копии",
|
||||
),
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
||||
"При включении будет возможно получать IPv6 трафик",
|
||||
@@ -469,9 +473,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"localBackupDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Резервное копирование локальных данных на локальный диск",
|
||||
),
|
||||
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановление данных из файла",
|
||||
),
|
||||
"log": MessageLookupByLibrary.simpleMessage("Журнал"),
|
||||
"logLevel": MessageLookupByLibrary.simpleMessage("Уровень логов"),
|
||||
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
|
||||
@@ -527,6 +528,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"networkDetection": MessageLookupByLibrary.simpleMessage(
|
||||
"Обнаружение сети",
|
||||
),
|
||||
"networkException": MessageLookupByLibrary.simpleMessage(
|
||||
"Ошибка сети, проверьте соединение и попробуйте еще раз",
|
||||
),
|
||||
"networkRequestException": MessageLookupByLibrary.simpleMessage(
|
||||
"Исключение сетевого запроса, пожалуйста, попробуйте позже.",
|
||||
),
|
||||
"networkSpeed": MessageLookupByLibrary.simpleMessage("Скорость сети"),
|
||||
"networkType": MessageLookupByLibrary.simpleMessage("Тип сети"),
|
||||
"neutralScheme": MessageLookupByLibrary.simpleMessage("Нейтральные"),
|
||||
@@ -595,6 +602,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"overrideScript": MessageLookupByLibrary.simpleMessage(
|
||||
"Скрипт переопределения",
|
||||
),
|
||||
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage(
|
||||
"Пользовательский",
|
||||
),
|
||||
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Пользовательский режим, полная настройка групп прокси и правил",
|
||||
),
|
||||
"palette": MessageLookupByLibrary.simpleMessage("Палитра"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Пароль"),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("Вставить"),
|
||||
@@ -669,31 +682,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Установить порт прослушивания Clash",
|
||||
),
|
||||
"proxyProviders": MessageLookupByLibrary.simpleMessage("Провайдеры прокси"),
|
||||
"pruneCache": MessageLookupByLibrary.simpleMessage("Очистить кэш"),
|
||||
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Чисто черный режим"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("QR-код"),
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Сканируйте QR-код для получения профиля",
|
||||
),
|
||||
"rainbowScheme": MessageLookupByLibrary.simpleMessage("Радужные"),
|
||||
"recovery": MessageLookupByLibrary.simpleMessage("Восстановление"),
|
||||
"recoveryAll": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановить все данные",
|
||||
),
|
||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
|
||||
"Только восстановление профилей",
|
||||
),
|
||||
"recoveryStrategy": MessageLookupByLibrary.simpleMessage(
|
||||
"Стратегия восстановления",
|
||||
),
|
||||
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage(
|
||||
"Совместимый",
|
||||
),
|
||||
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
|
||||
"Переопределение",
|
||||
),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановление успешно",
|
||||
),
|
||||
"redirPort": MessageLookupByLibrary.simpleMessage("Redir-порт"),
|
||||
"redo": MessageLookupByLibrary.simpleMessage("Повторить"),
|
||||
"regExp": MessageLookupByLibrary.simpleMessage("Регулярное выражение"),
|
||||
@@ -705,9 +700,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"remoteDestination": MessageLookupByLibrary.simpleMessage(
|
||||
"Удалённое назначение",
|
||||
),
|
||||
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановление данных с WebDAV",
|
||||
),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("Удалить"),
|
||||
"rename": MessageLookupByLibrary.simpleMessage("Переименовать"),
|
||||
"request": MessageLookupByLibrary.simpleMessage("Запрос"),
|
||||
@@ -734,6 +726,34 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"restartCoreTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Вы уверены, что хотите перезапустить ядро?",
|
||||
),
|
||||
"restore": MessageLookupByLibrary.simpleMessage("Восстановить"),
|
||||
"restoreAllData": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановить все данные",
|
||||
),
|
||||
"restoreException": MessageLookupByLibrary.simpleMessage(
|
||||
"Ошибка восстановления",
|
||||
),
|
||||
"restoreFromFileDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановить данные из файла",
|
||||
),
|
||||
"restoreFromWebDAVDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановить данные через WebDAV",
|
||||
),
|
||||
"restoreOnlyConfig": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановить только файлы конфигурации",
|
||||
),
|
||||
"restoreStrategy": MessageLookupByLibrary.simpleMessage(
|
||||
"Стратегия восстановления",
|
||||
),
|
||||
"restoreStrategy_compatible": MessageLookupByLibrary.simpleMessage(
|
||||
"Совместимый",
|
||||
),
|
||||
"restoreStrategy_override": MessageLookupByLibrary.simpleMessage(
|
||||
"Перезаписать",
|
||||
),
|
||||
"restoreSuccess": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановление успешно",
|
||||
),
|
||||
"routeAddress": MessageLookupByLibrary.simpleMessage("Адрес маршрутизации"),
|
||||
"routeAddressDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Настройка адреса прослушивания маршрутизации",
|
||||
@@ -851,6 +871,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Убрать дополнительные задержки, такие как рукопожатие",
|
||||
),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("Неизвестно"),
|
||||
"unknownNetworkError": MessageLookupByLibrary.simpleMessage(
|
||||
"Неизвестная сетевая ошибка",
|
||||
),
|
||||
"unnamed": MessageLookupByLibrary.simpleMessage("Без имени"),
|
||||
"update": MessageLookupByLibrary.simpleMessage("Обновить"),
|
||||
"upload": MessageLookupByLibrary.simpleMessage("Загрузка"),
|
||||
|
||||
@@ -70,6 +70,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"action_tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
|
||||
"action_view": MessageLookupByLibrary.simpleMessage("显示/隐藏"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("添加"),
|
||||
"addProfile": MessageLookupByLibrary.simpleMessage("添加配置"),
|
||||
"addRule": MessageLookupByLibrary.simpleMessage("添加规则"),
|
||||
"addedOriginRules": MessageLookupByLibrary.simpleMessage("附加到原始规则"),
|
||||
"addedRules": MessageLookupByLibrary.simpleMessage("附加规则"),
|
||||
@@ -109,8 +110,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"autoUpdate": MessageLookupByLibrary.simpleMessage("自动更新"),
|
||||
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自动更新间隔(分钟)"),
|
||||
"backup": MessageLookupByLibrary.simpleMessage("备份"),
|
||||
"backupAndRecovery": MessageLookupByLibrary.simpleMessage("备份与恢复"),
|
||||
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"backupAndRestore": MessageLookupByLibrary.simpleMessage("备份与恢复"),
|
||||
"backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"通过WebDAV或者文件同步数据",
|
||||
),
|
||||
"backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"),
|
||||
@@ -284,6 +285,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"internet": MessageLookupByLibrary.simpleMessage("互联网"),
|
||||
"interval": MessageLookupByLibrary.simpleMessage("间隔"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
|
||||
"invalidBackupFile": MessageLookupByLibrary.simpleMessage("无效备份文件"),
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
|
||||
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"),
|
||||
@@ -301,7 +303,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"loading": MessageLookupByLibrary.simpleMessage("加载中..."),
|
||||
"local": MessageLookupByLibrary.simpleMessage("本地"),
|
||||
"localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"),
|
||||
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),
|
||||
"log": MessageLookupByLibrary.simpleMessage("日志"),
|
||||
"logLevel": MessageLookupByLibrary.simpleMessage("日志等级"),
|
||||
"logcat": MessageLookupByLibrary.simpleMessage("日志捕获"),
|
||||
@@ -335,6 +336,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"network": MessageLookupByLibrary.simpleMessage("网络"),
|
||||
"networkDesc": MessageLookupByLibrary.simpleMessage("修改网络相关设置"),
|
||||
"networkDetection": MessageLookupByLibrary.simpleMessage("网络检测"),
|
||||
"networkException": MessageLookupByLibrary.simpleMessage("网络异常,请检查连接后重试"),
|
||||
"networkRequestException": MessageLookupByLibrary.simpleMessage(
|
||||
"网络请求异常,请稍后再试。",
|
||||
),
|
||||
"networkSpeed": MessageLookupByLibrary.simpleMessage("网络速度"),
|
||||
"networkType": MessageLookupByLibrary.simpleMessage("网络类型"),
|
||||
"neutralScheme": MessageLookupByLibrary.simpleMessage("中性"),
|
||||
@@ -373,6 +378,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"overrideMode": MessageLookupByLibrary.simpleMessage("覆写模式"),
|
||||
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("覆盖原始规则"),
|
||||
"overrideScript": MessageLookupByLibrary.simpleMessage("覆写脚本"),
|
||||
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage("自定义"),
|
||||
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"自定义模式,支持完全自定义修改代理组以及规则",
|
||||
),
|
||||
"palette": MessageLookupByLibrary.simpleMessage("调色板"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("密码"),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("粘贴"),
|
||||
@@ -423,17 +432,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
|
||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
|
||||
"proxyProviders": MessageLookupByLibrary.simpleMessage("代理提供者"),
|
||||
"pruneCache": MessageLookupByLibrary.simpleMessage("修剪缓存"),
|
||||
"pureBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
|
||||
"rainbowScheme": MessageLookupByLibrary.simpleMessage("彩虹"),
|
||||
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),
|
||||
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
|
||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
|
||||
"recoveryStrategy": MessageLookupByLibrary.simpleMessage("恢复策略"),
|
||||
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("兼容"),
|
||||
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage("覆盖"),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
|
||||
"redirPort": MessageLookupByLibrary.simpleMessage("Redir端口"),
|
||||
"redo": MessageLookupByLibrary.simpleMessage("重做"),
|
||||
"regExp": MessageLookupByLibrary.simpleMessage("正则"),
|
||||
@@ -441,7 +444,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"remote": MessageLookupByLibrary.simpleMessage("远程"),
|
||||
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
|
||||
"remoteDestination": MessageLookupByLibrary.simpleMessage("远程目标"),
|
||||
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过WebDAV恢复数据"),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("移除"),
|
||||
"rename": MessageLookupByLibrary.simpleMessage("重命名"),
|
||||
"request": MessageLookupByLibrary.simpleMessage("请求"),
|
||||
@@ -460,6 +462,18 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"restart": MessageLookupByLibrary.simpleMessage("重启"),
|
||||
"restartCoreTip": MessageLookupByLibrary.simpleMessage("您确定要重启核心吗?"),
|
||||
"restore": MessageLookupByLibrary.simpleMessage("恢复"),
|
||||
"restoreAllData": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
|
||||
"restoreException": MessageLookupByLibrary.simpleMessage("恢复异常"),
|
||||
"restoreFromFileDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),
|
||||
"restoreFromWebDAVDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"通过WebDAV恢复数据",
|
||||
),
|
||||
"restoreOnlyConfig": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
|
||||
"restoreStrategy": MessageLookupByLibrary.simpleMessage("恢复策略"),
|
||||
"restoreStrategy_compatible": MessageLookupByLibrary.simpleMessage("兼容"),
|
||||
"restoreStrategy_override": MessageLookupByLibrary.simpleMessage("覆盖"),
|
||||
"restoreSuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
|
||||
"routeAddress": MessageLookupByLibrary.simpleMessage("路由地址"),
|
||||
"routeAddressDesc": MessageLookupByLibrary.simpleMessage("配置监听路由地址"),
|
||||
"routeMode": MessageLookupByLibrary.simpleMessage("路由模式"),
|
||||
@@ -547,6 +561,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"),
|
||||
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("未知"),
|
||||
"unknownNetworkError": MessageLookupByLibrary.simpleMessage("未知网络错误"),
|
||||
"unnamed": MessageLookupByLibrary.simpleMessage("未命名"),
|
||||
"update": MessageLookupByLibrary.simpleMessage("更新"),
|
||||
"upload": MessageLookupByLibrary.simpleMessage("上传"),
|
||||
|
||||
@@ -999,26 +999,6 @@ class AppLocalizations {
|
||||
return Intl.message('tip', name: 'tip', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Backup and Recovery`
|
||||
String get backupAndRecovery {
|
||||
return Intl.message(
|
||||
'Backup and Recovery',
|
||||
name: 'backupAndRecovery',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Sync data via WebDAV or file`
|
||||
String get backupAndRecoveryDesc {
|
||||
return Intl.message(
|
||||
'Sync data via WebDAV or file',
|
||||
name: 'backupAndRecoveryDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Account`
|
||||
String get account {
|
||||
return Intl.message('Account', name: 'account', desc: '', args: []);
|
||||
@@ -1029,41 +1009,6 @@ class AppLocalizations {
|
||||
return Intl.message('Backup', name: 'backup', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Recovery`
|
||||
String get recovery {
|
||||
return Intl.message('Recovery', name: 'recovery', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Only recovery profiles`
|
||||
String get recoveryProfiles {
|
||||
return Intl.message(
|
||||
'Only recovery profiles',
|
||||
name: 'recoveryProfiles',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Recovery all data`
|
||||
String get recoveryAll {
|
||||
return Intl.message(
|
||||
'Recovery all data',
|
||||
name: 'recoveryAll',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Recovery success`
|
||||
String get recoverySuccess {
|
||||
return Intl.message(
|
||||
'Recovery success',
|
||||
name: 'recoverySuccess',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Backup success`
|
||||
String get backupSuccess {
|
||||
return Intl.message(
|
||||
@@ -1689,16 +1634,6 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Recovery data from WebDAV`
|
||||
String get remoteRecoveryDesc {
|
||||
return Intl.message(
|
||||
'Recovery data from WebDAV',
|
||||
name: 'remoteRecoveryDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Backup local data to local`
|
||||
String get localBackupDesc {
|
||||
return Intl.message(
|
||||
@@ -1709,16 +1644,6 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Recovery data from file`
|
||||
String get localRecoveryDesc {
|
||||
return Intl.message(
|
||||
'Recovery data from file',
|
||||
name: 'localRecoveryDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Mode`
|
||||
String get mode {
|
||||
return Intl.message('Mode', name: 'mode', desc: '', args: []);
|
||||
@@ -2934,31 +2859,31 @@ class AppLocalizations {
|
||||
return Intl.message('Contact me', name: 'contactMe', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Recovery strategy`
|
||||
String get recoveryStrategy {
|
||||
/// `Restore strategy`
|
||||
String get restoreStrategy {
|
||||
return Intl.message(
|
||||
'Recovery strategy',
|
||||
name: 'recoveryStrategy',
|
||||
'Restore strategy',
|
||||
name: 'restoreStrategy',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Override`
|
||||
String get recoveryStrategy_override {
|
||||
String get restoreStrategy_override {
|
||||
return Intl.message(
|
||||
'Override',
|
||||
name: 'recoveryStrategy_override',
|
||||
name: 'restoreStrategy_override',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Compatible`
|
||||
String get recoveryStrategy_compatible {
|
||||
String get restoreStrategy_compatible {
|
||||
return Intl.message(
|
||||
'Compatible',
|
||||
name: 'recoveryStrategy_compatible',
|
||||
name: 'restoreStrategy_compatible',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
@@ -3663,6 +3588,161 @@ class AppLocalizations {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Custom`
|
||||
String get overwriteTypeCustom {
|
||||
return Intl.message(
|
||||
'Custom',
|
||||
name: 'overwriteTypeCustom',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Custom mode, fully customize proxy groups and rules`
|
||||
String get overwriteTypeCustomDesc {
|
||||
return Intl.message(
|
||||
'Custom mode, fully customize proxy groups and rules',
|
||||
name: 'overwriteTypeCustomDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Unknown network error`
|
||||
String get unknownNetworkError {
|
||||
return Intl.message(
|
||||
'Unknown network error',
|
||||
name: 'unknownNetworkError',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Network request exception, please try again later.`
|
||||
String get networkRequestException {
|
||||
return Intl.message(
|
||||
'Network request exception, please try again later.',
|
||||
name: 'networkRequestException',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Recovery exception`
|
||||
String get restoreException {
|
||||
return Intl.message(
|
||||
'Recovery exception',
|
||||
name: 'restoreException',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Network exception, please check your connection and try again`
|
||||
String get networkException {
|
||||
return Intl.message(
|
||||
'Network exception, please check your connection and try again',
|
||||
name: 'networkException',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Invalid backup file`
|
||||
String get invalidBackupFile {
|
||||
return Intl.message(
|
||||
'Invalid backup file',
|
||||
name: 'invalidBackupFile',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Prune cache`
|
||||
String get pruneCache {
|
||||
return Intl.message('Prune cache', name: 'pruneCache', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Backup and Restore`
|
||||
String get backupAndRestore {
|
||||
return Intl.message(
|
||||
'Backup and Restore',
|
||||
name: 'backupAndRestore',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Sync data via WebDAV or files`
|
||||
String get backupAndRestoreDesc {
|
||||
return Intl.message(
|
||||
'Sync data via WebDAV or files',
|
||||
name: 'backupAndRestoreDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Restore`
|
||||
String get restore {
|
||||
return Intl.message('Restore', name: 'restore', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Restore success`
|
||||
String get restoreSuccess {
|
||||
return Intl.message(
|
||||
'Restore success',
|
||||
name: 'restoreSuccess',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Restore data via WebDAV`
|
||||
String get restoreFromWebDAVDesc {
|
||||
return Intl.message(
|
||||
'Restore data via WebDAV',
|
||||
name: 'restoreFromWebDAVDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Restore data via file`
|
||||
String get restoreFromFileDesc {
|
||||
return Intl.message(
|
||||
'Restore data via file',
|
||||
name: 'restoreFromFileDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Restore configuration files only`
|
||||
String get restoreOnlyConfig {
|
||||
return Intl.message(
|
||||
'Restore configuration files only',
|
||||
name: 'restoreOnlyConfig',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Restore all data`
|
||||
String get restoreAllData {
|
||||
return Intl.message(
|
||||
'Restore all data',
|
||||
name: 'restoreAllData',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Add Profile`
|
||||
String get addProfile {
|
||||
return Intl.message('Add Profile', name: 'addProfile', desc: '', args: []);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -1,65 +1,31 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
import 'package:fl_clash/plugins/tile.dart';
|
||||
import 'package:fl_clash/pages/error.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'application.dart';
|
||||
import 'common/common.dart';
|
||||
import 'core/controller.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final version = await system.version;
|
||||
await globalState.initApp(version);
|
||||
HttpOverrides.global = FlClashHttpOverrides();
|
||||
runApp(ProviderScope(child: const Application()));
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> _service(List<String> flags) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
globalState.isService = true;
|
||||
await globalState.init();
|
||||
await coreController.preload();
|
||||
tile?.addListener(
|
||||
_TileListenerWithService(
|
||||
onStop: () async {
|
||||
await app?.tip(appLocalizations.stopVpn);
|
||||
await globalState.handleStop();
|
||||
},
|
||||
),
|
||||
);
|
||||
app?.tip(appLocalizations.startVpn);
|
||||
final version = await system.version;
|
||||
await coreController.init(version);
|
||||
final clashConfig = globalState.config.patchClashConfig.copyWith.tun(
|
||||
enable: false,
|
||||
);
|
||||
final setupState = globalState.getSetupState(
|
||||
globalState.config.currentProfileId,
|
||||
);
|
||||
globalState.setupConfig(
|
||||
setupState: setupState,
|
||||
patchConfig: clashConfig,
|
||||
preloadInvoke: () {
|
||||
globalState.handleStart();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@immutable
|
||||
class _TileListenerWithService with TileListener {
|
||||
final Function() _onStop;
|
||||
|
||||
const _TileListenerWithService({required Function() onStop})
|
||||
: _onStop = onStop;
|
||||
|
||||
@override
|
||||
void onStop() {
|
||||
_onStop();
|
||||
try {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final version = await system.version;
|
||||
final container = await globalState.init(version);
|
||||
HttpOverrides.global = FlClashHttpOverrides();
|
||||
runApp(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: const Application(),
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
return runApp(
|
||||
MaterialApp(
|
||||
home: InitErrorScreen(error: e, stack: s),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/core/core.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/core.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
import 'package:fl_clash/plugins/service.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
@@ -27,9 +28,14 @@ class _AndroidContainerState extends ConsumerState<AndroidManager>
|
||||
) {
|
||||
app?.updateExcludeFromRecents(next);
|
||||
}, fireImmediately: true);
|
||||
ref.listenManual(androidStateProvider, (prev, next) {
|
||||
ref.listenManual(sharedStateProvider, (prev, next) {
|
||||
if (prev != next) {
|
||||
service?.syncAndroidState(next);
|
||||
debouncer.call(FunctionTag.saveSharedFile, () async {
|
||||
preferences.saveShareState(next);
|
||||
}, duration: Duration(seconds: 1));
|
||||
if (prev?.needSyncSharedState != next.needSyncSharedState) {
|
||||
service?.syncState(next.needSyncSharedState);
|
||||
}
|
||||
}
|
||||
});
|
||||
service?.addListener(this);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/manager/window_manager.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
@@ -25,26 +26,19 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
ref.listenManual(layoutChangeProvider, (prev, next) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (prev != next) {
|
||||
globalState.computeHeightMapCache = {};
|
||||
}
|
||||
});
|
||||
});
|
||||
ref.listenManual(checkIpProvider, (prev, next) {
|
||||
if (prev != next && next.b) {
|
||||
detectionState.startCheck();
|
||||
if (prev != next && next.a && next.c) {
|
||||
ref.read(networkDetectionProvider.notifier).startCheck();
|
||||
}
|
||||
}, fireImmediately: true);
|
||||
ref.listenManual(configStateProvider, (prev, next) {
|
||||
});
|
||||
ref.listenManual(configProvider, (prev, next) {
|
||||
if (prev != next) {
|
||||
globalState.appController.savePreferencesDebounce();
|
||||
appController.savePreferencesDebounce();
|
||||
}
|
||||
});
|
||||
ref.listenManual(needUpdateGroupsProvider, (prev, next) {
|
||||
if (prev != next) {
|
||||
globalState.appController.updateGroupsDebounce();
|
||||
appController.updateGroupsDebounce();
|
||||
}
|
||||
});
|
||||
if (window == null) {
|
||||
@@ -73,20 +67,18 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
|
||||
commonPrint.log('$state');
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
render?.resume();
|
||||
}
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
detectionState.tryStartCheck();
|
||||
appController.tryCheckIp();
|
||||
if (system.isAndroid) {
|
||||
appController.tryStartCore();
|
||||
}
|
||||
});
|
||||
if (system.isAndroid) {
|
||||
globalState.appController.tryStartCore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangePlatformBrightness() {
|
||||
globalState.appController.updateBrightness();
|
||||
appController.updateBrightness();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -132,20 +124,20 @@ class AppSidebarContainer extends ConsumerWidget {
|
||||
|
||||
const AppSidebarContainer({super.key, required this.child});
|
||||
|
||||
Widget _buildLoading() {
|
||||
return Consumer(
|
||||
builder: (_, ref, _) {
|
||||
final loading = ref.watch(loadingProvider);
|
||||
final isMobileView = ref.watch(isMobileViewProvider);
|
||||
return loading && !isMobileView
|
||||
? RotatedBox(
|
||||
quarterTurns: 1,
|
||||
child: const LinearProgressIndicator(),
|
||||
)
|
||||
: Container();
|
||||
},
|
||||
);
|
||||
}
|
||||
// Widget _buildLoading() {
|
||||
// return Consumer(
|
||||
// builder: (_, ref, _) {
|
||||
// final loading = ref.watch(loadingProvider);
|
||||
// final isMobileView = ref.watch(isMobileViewProvider);
|
||||
// return loading && !isMobileView
|
||||
// ? RotatedBox(
|
||||
// quarterTurns: 1,
|
||||
// child: const LinearProgressIndicator(),
|
||||
// )
|
||||
// : Container();
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget _buildBackground({
|
||||
required BuildContext context,
|
||||
@@ -187,84 +179,74 @@ class AppSidebarContainer extends ConsumerWidget {
|
||||
_buildBackground(
|
||||
context: context,
|
||||
child: SafeArea(
|
||||
child: Stack(
|
||||
alignment: Alignment.topRight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (system.isMacOS) SizedBox(height: 22),
|
||||
SizedBox(height: 10),
|
||||
if (!system.isMacOS) ...[
|
||||
ClipRect(child: AppIcon()),
|
||||
SizedBox(height: 12),
|
||||
],
|
||||
Expanded(
|
||||
child: ScrollConfiguration(
|
||||
behavior: HiddenBarScrollBehavior(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: NavigationRail(
|
||||
scrollable: true,
|
||||
minExtendedWidth: 200,
|
||||
backgroundColor: Colors.transparent,
|
||||
selectedLabelTextStyle: context
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
),
|
||||
unselectedLabelTextStyle: context
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
),
|
||||
destinations: navigationItems
|
||||
.map(
|
||||
(e) => NavigationRailDestination(
|
||||
icon: e.icon,
|
||||
label: Text(Intl.message(e.label.name)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onDestinationSelected: (index) {
|
||||
globalState.appController.toPage(
|
||||
navigationItems[index].label,
|
||||
);
|
||||
},
|
||||
extended: false,
|
||||
selectedIndex: currentIndex,
|
||||
labelType: showLabel
|
||||
? NavigationRailLabelType.all
|
||||
: NavigationRailLabelType.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (system.isMacOS) SizedBox(height: 22),
|
||||
SizedBox(height: 10),
|
||||
if (!system.isMacOS) ...[
|
||||
ClipRect(child: AppIcon()),
|
||||
SizedBox(height: 12),
|
||||
],
|
||||
Expanded(
|
||||
child: ScrollConfiguration(
|
||||
behavior: HiddenBarScrollBehavior(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: NavigationRail(
|
||||
scrollable: true,
|
||||
minExtendedWidth: 200,
|
||||
backgroundColor: Colors.transparent,
|
||||
selectedLabelTextStyle: context
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(color: context.colorScheme.onSurface),
|
||||
unselectedLabelTextStyle: context
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(color: context.colorScheme.onSurface),
|
||||
destinations: navigationItems
|
||||
.map(
|
||||
(e) => NavigationRailDestination(
|
||||
icon: e.icon,
|
||||
label: Text(Intl.message(e.label.name)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onDestinationSelected: (index) {
|
||||
appController.toPage(
|
||||
navigationItems[index].label,
|
||||
);
|
||||
},
|
||||
extended: false,
|
||||
selectedIndex: currentIndex,
|
||||
labelType: showLabel
|
||||
? NavigationRailLabelType.all
|
||||
: NavigationRailLabelType.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(appSettingProvider.notifier)
|
||||
.updateState(
|
||||
(state) =>
|
||||
state.copyWith(showLabel: !state.showLabel),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.menu,
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildLoading(),
|
||||
const SizedBox(height: 16),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(appSettingProvider.notifier)
|
||||
.update(
|
||||
(state) =>
|
||||
state.copyWith(showLabel: !state.showLabel),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.menu,
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:fl_clash/core/core.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
@@ -29,14 +30,17 @@ class _CoreContainerState extends ConsumerState<CoreManager>
|
||||
void initState() {
|
||||
super.initState();
|
||||
coreEventManager.addListener(this);
|
||||
ref.listenManual(needSetupProvider, (prev, next) {
|
||||
if (prev != next) {
|
||||
globalState.appController.handleChangeProfile();
|
||||
}
|
||||
});
|
||||
ref.listenManual(
|
||||
currentSetupStateProvider.select((state) => state?.profileId),
|
||||
(prev, next) {
|
||||
if (prev != next) {
|
||||
appController.fullSetup();
|
||||
}
|
||||
},
|
||||
);
|
||||
ref.listenManual(updateParamsProvider, (prev, next) {
|
||||
if (prev != next) {
|
||||
globalState.appController.updateClashConfigDebounce();
|
||||
appController.updateConfigDebounce();
|
||||
}
|
||||
});
|
||||
ref.listenManual(appSettingProvider.select((state) => state.openLogs), (
|
||||
@@ -60,7 +64,6 @@ class _CoreContainerState extends ConsumerState<CoreManager>
|
||||
@override
|
||||
Future<void> onDelay(Delay delay) async {
|
||||
super.onDelay(delay);
|
||||
final appController = globalState.appController;
|
||||
appController.setDelay(delay);
|
||||
debouncer.call(FunctionTag.updateDelay, () async {
|
||||
appController.updateGroupsDebounce();
|
||||
@@ -88,23 +91,21 @@ class _CoreContainerState extends ConsumerState<CoreManager>
|
||||
.read(providersProvider.notifier)
|
||||
.setProvider(await coreController.getExternalProvider(providerName));
|
||||
debouncer.call(FunctionTag.loadedProvider, () async {
|
||||
globalState.appController.updateGroupsDebounce();
|
||||
appController.updateGroupsDebounce();
|
||||
}, duration: const Duration(milliseconds: 5000));
|
||||
super.onLoaded(providerName);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onCrash(String message) async {
|
||||
if (!globalState.isUserDisconnected &&
|
||||
WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
|
||||
context.showNotifier(message);
|
||||
}
|
||||
globalState.isUserDisconnected = false;
|
||||
if (ref.read(coreStatusProvider) != CoreStatus.connected) {
|
||||
return;
|
||||
}
|
||||
ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
|
||||
await coreController.shutdown();
|
||||
if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
|
||||
context.showNotifier(message);
|
||||
}
|
||||
await coreController.shutdown(false);
|
||||
super.onCrash(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/common.dart';
|
||||
import 'package:fl_clash/providers/config.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@@ -11,10 +11,7 @@ import 'package:hotkey_manager/hotkey_manager.dart';
|
||||
class HotKeyManager extends ConsumerStatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const HotKeyManager({
|
||||
super.key,
|
||||
required this.child,
|
||||
});
|
||||
const HotKeyManager({super.key, required this.child});
|
||||
|
||||
@override
|
||||
ConsumerState<HotKeyManager> createState() => _HotKeyManagerState();
|
||||
@@ -24,29 +21,25 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ref.listenManual(
|
||||
hotKeyActionsProvider,
|
||||
(prev, next) {
|
||||
if (!hotKeyActionListEquality.equals(prev, next)) {
|
||||
_updateHotKeys(hotKeyActions: next);
|
||||
}
|
||||
},
|
||||
fireImmediately: true,
|
||||
);
|
||||
ref.listenManual(hotKeyActionsProvider, (prev, next) {
|
||||
if (!hotKeyActionListEquality.equals(prev, next)) {
|
||||
_updateHotKeys(hotKeyActions: next);
|
||||
}
|
||||
}, fireImmediately: true);
|
||||
}
|
||||
|
||||
Future<void> _handleHotKeyAction(HotAction action) async {
|
||||
switch (action) {
|
||||
case HotAction.mode:
|
||||
globalState.appController.updateMode();
|
||||
appController.updateMode();
|
||||
case HotAction.start:
|
||||
globalState.appController.updateStart();
|
||||
appController.updateStart();
|
||||
case HotAction.view:
|
||||
globalState.appController.updateVisible();
|
||||
appController.updateVisible();
|
||||
case HotAction.proxy:
|
||||
globalState.appController.updateSystemProxy();
|
||||
appController.updateSystemProxy();
|
||||
case HotAction.tun:
|
||||
globalState.appController.updateTun();
|
||||
appController.updateTun();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,27 +47,25 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
|
||||
required List<HotKeyAction> hotKeyActions,
|
||||
}) async {
|
||||
await hotKeyManager.unregisterAll();
|
||||
final hotkeyActionHandles = hotKeyActions.where(
|
||||
(hotKeyAction) {
|
||||
return hotKeyAction.key != null && hotKeyAction.modifiers.isNotEmpty;
|
||||
},
|
||||
).map<Future>(
|
||||
(hotKeyAction) async {
|
||||
final modifiers = hotKeyAction.modifiers
|
||||
.map((item) => item.toHotKeyModifier())
|
||||
.toList();
|
||||
final hotKey = HotKey(
|
||||
key: PhysicalKeyboardKey(hotKeyAction.key!),
|
||||
modifiers: modifiers,
|
||||
);
|
||||
return await hotKeyManager.register(
|
||||
hotKey,
|
||||
keyDownHandler: (_) {
|
||||
_handleHotKeyAction(hotKeyAction.action);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
final hotkeyActionHandles = hotKeyActions
|
||||
.where((hotKeyAction) {
|
||||
return hotKeyAction.key != null && hotKeyAction.modifiers.isNotEmpty;
|
||||
})
|
||||
.map<Future>((hotKeyAction) async {
|
||||
final modifiers = hotKeyAction.modifiers
|
||||
.map((item) => item.toHotKeyModifier())
|
||||
.toList();
|
||||
final hotKey = HotKey(
|
||||
key: PhysicalKeyboardKey(hotKeyAction.key!),
|
||||
modifiers: modifiers,
|
||||
);
|
||||
return await hotKeyManager.register(
|
||||
hotKey,
|
||||
keyDownHandler: (_) {
|
||||
_handleHotKeyAction(hotKeyAction.action);
|
||||
},
|
||||
);
|
||||
});
|
||||
await Future.wait(hotkeyActionHandles);
|
||||
}
|
||||
|
||||
@@ -87,7 +78,7 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
|
||||
child: Actions(
|
||||
actions: {
|
||||
CloseWindowIntent: CallbackAction<CloseWindowIntent>(
|
||||
onInvoke: (_) => globalState.appController.handleBackOrExit(),
|
||||
onInvoke: (_) => appController.handleBackOrExit(),
|
||||
),
|
||||
DoNothingIntent: CallbackAction<DoNothingIntent>(
|
||||
onInvoke: (_) => null,
|
||||
@@ -100,8 +91,6 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _buildShortcuts(
|
||||
widget.child,
|
||||
);
|
||||
return _buildShortcuts(widget.child);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/widgets/fade_box.dart';
|
||||
import 'package:fl_clash/widgets/loading.dart';
|
||||
import 'package:fl_clash/widgets/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@@ -191,7 +190,7 @@ class StatusManagerState extends State<StatusManager> {
|
||||
),
|
||||
),
|
||||
),
|
||||
LoadingIndicator(),
|
||||
// LoadingIndicator(),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -200,63 +199,63 @@ class StatusManagerState extends State<StatusManager> {
|
||||
}
|
||||
}
|
||||
|
||||
class LoadingIndicator extends ConsumerWidget {
|
||||
const LoadingIndicator({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final loading = ref.watch(loadingProvider);
|
||||
final isMobileView = ref.watch(isMobileViewProvider);
|
||||
return AnimatedSwitcher(
|
||||
switchInCurve: Curves.easeIn,
|
||||
switchOutCurve: Curves.easeOut,
|
||||
duration: midDuration,
|
||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(1, 0),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: loading && isMobileView
|
||||
? Container(
|
||||
height: 54,
|
||||
margin: EdgeInsets.only(top: 8, left: 14, right: 14),
|
||||
child: Material(
|
||||
elevation: 3,
|
||||
color: context.colorScheme.surfaceContainer,
|
||||
surfaceTintColor: context.colorScheme.surfaceTint,
|
||||
shape: const RoundedSuperellipseBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(14)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 12,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
context.appLocalizations.loading,
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 32,
|
||||
width: 32,
|
||||
child: CommonCircleLoading(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: SizedBox(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// class LoadingIndicator extends ConsumerWidget {
|
||||
// const LoadingIndicator({super.key});
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context, ref) {
|
||||
// final loading = ref.watch(loadingProvider);
|
||||
// final isMobileView = ref.watch(isMobileViewProvider);
|
||||
// return AnimatedSwitcher(
|
||||
// switchInCurve: Curves.easeIn,
|
||||
// switchOutCurve: Curves.easeOut,
|
||||
// duration: midDuration,
|
||||
// transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
// return SlideTransition(
|
||||
// position: Tween<Offset>(
|
||||
// begin: const Offset(1, 0),
|
||||
// end: Offset.zero,
|
||||
// ).animate(animation),
|
||||
// child: child,
|
||||
// );
|
||||
// },
|
||||
// child: loading && isMobileView
|
||||
// ? Container(
|
||||
// height: 54,
|
||||
// margin: EdgeInsets.only(top: 8, left: 14, right: 14),
|
||||
// child: Material(
|
||||
// elevation: 3,
|
||||
// color: context.colorScheme.surfaceContainer,
|
||||
// surfaceTintColor: context.colorScheme.surfaceTint,
|
||||
// shape: const RoundedSuperellipseBorder(
|
||||
// borderRadius: BorderRadius.all(Radius.circular(14)),
|
||||
// ),
|
||||
// child: Padding(
|
||||
// padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
// child: Row(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// spacing: 12,
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Flexible(
|
||||
// child: Text(
|
||||
// context.appLocalizations.loading,
|
||||
// style: context.textTheme.labelLarge?.copyWith(
|
||||
// color: context.colorScheme.onSurfaceVariant,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: 32,
|
||||
// width: 32,
|
||||
// child: CommonCircleLoading(),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// : SizedBox(),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'dart:math';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/common/theme.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:fl_clash/providers/app.dart';
|
||||
import 'package:fl_clash/providers/config.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -28,17 +30,27 @@ class ThemeManager extends ConsumerWidget {
|
||||
final iconBrightness = brightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light;
|
||||
globalState.appState = globalState.appState.copyWith(
|
||||
systemUiOverlayStyle: SystemUiOverlayStyle(
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref
|
||||
.read(systemUiOverlayStyleStateProvider.notifier)
|
||||
.update(
|
||||
(state) => state.copyWith(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: iconBrightness,
|
||||
systemNavigationBarIconBrightness: iconBrightness,
|
||||
systemNavigationBarColor: context.colorScheme.surface,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
),
|
||||
);
|
||||
});
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: iconBrightness,
|
||||
systemNavigationBarIconBrightness: iconBrightness,
|
||||
systemNavigationBarColor: context.colorScheme.surface,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
),
|
||||
);
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: globalState.appState.systemUiOverlayStyle,
|
||||
sized: false,
|
||||
child: child,
|
||||
);
|
||||
@@ -98,7 +110,7 @@ class ThemeManager extends ConsumerWidget {
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (_, container) {
|
||||
globalState.appController.updateViewSize(
|
||||
appController.updateViewSize(
|
||||
Size(container.maxWidth, container.maxHeight),
|
||||
);
|
||||
return _buildSystemUi(child);
|
||||
|
||||
@@ -1,38 +1,46 @@
|
||||
import 'package:fl_clash/models/app.dart';
|
||||
import 'package:fl_clash/common/app_localizations.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:fl_clash/core/controller.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
import 'package:fl_clash/plugins/tile.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class TileManager extends StatefulWidget {
|
||||
class TileManager extends ConsumerStatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const TileManager({super.key, required this.child});
|
||||
|
||||
@override
|
||||
State<TileManager> createState() => _TileContainerState();
|
||||
ConsumerState<TileManager> createState() => _TileContainerState();
|
||||
}
|
||||
|
||||
class _TileContainerState extends State<TileManager> with TileListener {
|
||||
class _TileContainerState extends ConsumerState<TileManager> with TileListener {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
}
|
||||
|
||||
bool get isStart => ref.read(isStartProvider);
|
||||
|
||||
@override
|
||||
Future<void> onStart() async {
|
||||
if (globalState.appState.isStart) {
|
||||
if (isStart && coreController.isCompleted) {
|
||||
return;
|
||||
}
|
||||
globalState.appController.updateStatus(true);
|
||||
appController.updateStatus(true);
|
||||
app?.tip(appLocalizations.startVpn);
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onStop() async {
|
||||
if (!globalState.appState.isStart) {
|
||||
if (!isStart) {
|
||||
return;
|
||||
}
|
||||
globalState.appController.updateStatus(false);
|
||||
appController.updateStatus(false);
|
||||
app?.tip(appLocalizations.stopVpn);
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:fl_clash/providers/state.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
@@ -21,7 +21,7 @@ class _TrayContainerState extends ConsumerState<TrayManager> with TrayListener {
|
||||
trayManager.addListener(this);
|
||||
ref.listenManual(trayStateProvider, (prev, next) {
|
||||
if (prev != next) {
|
||||
globalState.appController.updateTray();
|
||||
appController.updateTray();
|
||||
}
|
||||
});
|
||||
if (system.isMacOS) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/providers/state.dart';
|
||||
@@ -39,7 +40,7 @@ class _VpnContainerState extends ConsumerState<VpnManager> {
|
||||
actionText: appLocalizations.restart,
|
||||
action: () async {
|
||||
await globalState.handleStop();
|
||||
await globalState.appController.updateStatus(true);
|
||||
await appController.updateStatus(true);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/controller.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:window_ext/window_ext.dart';
|
||||
@@ -44,7 +44,7 @@ class _WindowContainerState extends ConsumerState<WindowManager>
|
||||
|
||||
@override
|
||||
void onWindowClose() async {
|
||||
await globalState.appController.handleBackOrExit();
|
||||
await appController.handleBackOrExit();
|
||||
super.onWindowClose();
|
||||
}
|
||||
|
||||
@@ -57,19 +57,17 @@ class _WindowContainerState extends ConsumerState<WindowManager>
|
||||
|
||||
@override
|
||||
Future<void> onShouldTerminate() async {
|
||||
await globalState.appController.handleExit();
|
||||
await appController.handleExit();
|
||||
super.onShouldTerminate();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onWindowMoved() async {
|
||||
void onWindowMoved() {
|
||||
super.onWindowMoved();
|
||||
final offset = await windowManager.getPosition();
|
||||
ref
|
||||
.read(windowSettingProvider.notifier)
|
||||
.updateState(
|
||||
(state) => state.copyWith(top: offset.dy, left: offset.dx),
|
||||
);
|
||||
windowManager.getPosition().then((offset) {
|
||||
ref.read(windowSettingProvider.notifier);
|
||||
// .update((state) => state.copyWith(top: offset.dy, left: offset.dx));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -78,14 +76,14 @@ class _WindowContainerState extends ConsumerState<WindowManager>
|
||||
final size = await windowManager.getSize();
|
||||
ref
|
||||
.read(windowSettingProvider.notifier)
|
||||
.updateState(
|
||||
.update(
|
||||
(state) => state.copyWith(width: size.width, height: size.height),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowMinimize() async {
|
||||
globalState.appController.savePreferencesDebounce();
|
||||
appController.savePreferencesDebounce();
|
||||
commonPrint.log('minimize');
|
||||
render?.pause();
|
||||
super.onWindowMinimize();
|
||||
@@ -222,7 +220,7 @@ class _WindowHeaderState extends State<WindowHeader> {
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
globalState.appController.handleBackOrExit();
|
||||
appController.handleBackOrExit();
|
||||
},
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/selector.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
@@ -36,10 +35,6 @@ abstract class AppState with _$AppState {
|
||||
@Default(false) bool realTunEnable,
|
||||
@Default(false) bool loading,
|
||||
required SystemUiOverlayStyle systemUiOverlayStyle,
|
||||
ProfileOverrideModel? profileOverrideModel,
|
||||
@Default({}) Map<QueryTag, String> queryMap,
|
||||
@Default({}) Map<String, String> selectedItemMap,
|
||||
@Default({}) Map<String, Set<String>> selectedItemsMap,
|
||||
@Default(CoreStatus.connecting) CoreStatus coreStatus,
|
||||
}) = _AppState;
|
||||
}
|
||||
|
||||
@@ -375,17 +375,18 @@ extension ParsedRuleExt on ParsedRule {
|
||||
|
||||
@freezed
|
||||
abstract class Rule with _$Rule {
|
||||
const factory Rule({required String id, required String value}) = _Rule;
|
||||
const factory Rule({required int id, required String value, String? order}) =
|
||||
_Rule;
|
||||
|
||||
factory Rule.value(String value) {
|
||||
return Rule(value: value, id: utils.uuidV4);
|
||||
return Rule(value: value, id: snowflake.id);
|
||||
}
|
||||
|
||||
factory Rule.fromJson(Map<String, Object?> json) => _$RuleFromJson(json);
|
||||
}
|
||||
|
||||
extension RulesExt on List<Rule> {
|
||||
List<Rule> updateWith(Rule rule) {
|
||||
List<Rule> copyAndPut(Rule rule) {
|
||||
var newList = List<Rule>.from(this);
|
||||
final index = newList.indexWhere((item) => item.id == rule.id);
|
||||
if (index != -1) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
@@ -215,15 +217,16 @@ extension TrackerInfosStateExt on TrackerInfosState {
|
||||
const defaultDavFileName = 'backup.zip';
|
||||
|
||||
@freezed
|
||||
abstract class DAV with _$DAV {
|
||||
const factory DAV({
|
||||
abstract class DAVProps with _$DAVProps {
|
||||
const factory DAVProps({
|
||||
required String uri,
|
||||
required String user,
|
||||
required String password,
|
||||
@Default(defaultDavFileName) String fileName,
|
||||
}) = _DAV;
|
||||
}) = _DAVProps;
|
||||
|
||||
factory DAV.fromJson(Map<String, Object?> json) => _$DAVFromJson(json);
|
||||
factory DAVProps.fromJson(Map<String, Object?> json) =>
|
||||
_$DAVPropsFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -471,19 +474,6 @@ class PopupMenuItemData {
|
||||
final List<PopupMenuItemData> subItems;
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class AndroidState with _$AndroidState {
|
||||
const factory AndroidState({
|
||||
required String currentProfileName,
|
||||
required String stopText,
|
||||
required bool onlyStatisticsProxy,
|
||||
required bool crashlytics,
|
||||
}) = _AndroidState;
|
||||
|
||||
factory AndroidState.fromJson(Map<String, Object?> json) =>
|
||||
_$AndroidStateFromJson(json);
|
||||
}
|
||||
|
||||
class CloseWindowIntent extends Intent {
|
||||
const CloseWindowIntent();
|
||||
}
|
||||
@@ -512,20 +502,24 @@ extension ResultExt on Result {
|
||||
@freezed
|
||||
abstract class Script with _$Script {
|
||||
const factory Script({
|
||||
required String id,
|
||||
required int id,
|
||||
required String label,
|
||||
required String content,
|
||||
required DateTime lastUpdateTime,
|
||||
}) = _Script;
|
||||
|
||||
factory Script.create({required String label, required String content}) {
|
||||
return Script(id: utils.uuidV4, label: label, content: content);
|
||||
}
|
||||
|
||||
factory Script.fromJson(Map<String, Object?> json) => _$ScriptFromJson(json);
|
||||
|
||||
factory Script.create({required String label}) {
|
||||
return Script(
|
||||
id: snowflake.id,
|
||||
label: label,
|
||||
lastUpdateTime: DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension ScriptsExt on List<Script> {
|
||||
Script? get(String? id) {
|
||||
Script? get(int? id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -537,6 +531,38 @@ extension ScriptsExt on List<Script> {
|
||||
}
|
||||
}
|
||||
|
||||
extension ScriptExt on Script {
|
||||
String get fileName => '$id.js';
|
||||
|
||||
Future<String> get path async => await appPath.getScriptPath(id.toString());
|
||||
|
||||
Future<String?> get content async {
|
||||
final file = File(await path);
|
||||
if (await file.exists()) {
|
||||
return file.readAsString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Script> save(String content) async {
|
||||
final file = File(await path);
|
||||
if (!await file.exists()) {
|
||||
await file.create(recursive: true);
|
||||
}
|
||||
await file.writeAsString(content);
|
||||
return copyWith(lastUpdateTime: DateTime.now());
|
||||
}
|
||||
|
||||
Future<Script> saveWithPath(String copyPath) async {
|
||||
final file = File(await path);
|
||||
if (!await file.exists()) {
|
||||
await file.create(recursive: true);
|
||||
}
|
||||
await File(copyPath).copy(copyPath);
|
||||
return copyWith(lastUpdateTime: DateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class DelayState with _$DelayState {
|
||||
const factory DelayState({required int delay, required bool group}) =
|
||||
@@ -562,3 +588,11 @@ extension DelayStateExt on DelayState {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class UpdatingMessage with _$UpdatingMessage {
|
||||
const factory UpdatingMessage({
|
||||
required String label,
|
||||
required String message,
|
||||
}) = _UpdatingMessage;
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ const defaultBypassDomain = [
|
||||
const defaultAppSettingProps = AppSettingProps();
|
||||
const defaultVpnProps = VpnProps();
|
||||
const defaultNetworkProps = NetworkProps();
|
||||
const defaultProxiesStyle = ProxiesStyle();
|
||||
const defaultProxiesStyleProps = ProxiesStyleProps();
|
||||
const defaultWindowProps = WindowProps();
|
||||
const defaultAccessControl = AccessControl();
|
||||
const defaultAccessControlProps = AccessControlProps();
|
||||
final defaultThemeProps = ThemeProps(primaryColor: defaultPrimaryColor);
|
||||
|
||||
const List<DashboardWidget> defaultDashboardWidgets = [
|
||||
@@ -82,7 +82,7 @@ abstract class AppSettingProps with _$AppSettingProps {
|
||||
@Default(true) bool minimizeOnExit,
|
||||
@Default(false) bool hidden,
|
||||
@Default(false) bool developerMode,
|
||||
@Default(RecoveryStrategy.compatible) RecoveryStrategy recoveryStrategy,
|
||||
@Default(RestoreStrategy.compatible) RestoreStrategy restoreStrategy,
|
||||
@Default(true) bool showTrayTitle,
|
||||
}) = _AppSettingProps;
|
||||
|
||||
@@ -97,8 +97,8 @@ abstract class AppSettingProps with _$AppSettingProps {
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class AccessControl with _$AccessControl {
|
||||
const factory AccessControl({
|
||||
abstract class AccessControlProps with _$AccessControlProps {
|
||||
const factory AccessControlProps({
|
||||
@Default(false) bool enable,
|
||||
@Default(AccessControlMode.rejectSelected) AccessControlMode mode,
|
||||
@Default([]) List<String> acceptList,
|
||||
@@ -106,19 +106,19 @@ abstract class AccessControl with _$AccessControl {
|
||||
@Default(AccessSortType.none) AccessSortType sort,
|
||||
@Default(true) bool isFilterSystemApp,
|
||||
@Default(true) bool isFilterNonInternetApp,
|
||||
}) = _AccessControl;
|
||||
}) = _AccessControlProps;
|
||||
|
||||
factory AccessControl.fromJson(Map<String, Object?> json) =>
|
||||
_$AccessControlFromJson(json);
|
||||
factory AccessControlProps.fromJson(Map<String, Object?> json) =>
|
||||
_$AccessControlPropsFromJson(json);
|
||||
}
|
||||
|
||||
extension AccessControlExt on AccessControl {
|
||||
extension AccessControlPropsExt on AccessControlProps {
|
||||
List<String> get currentList => switch (mode) {
|
||||
AccessControlMode.acceptSelected => acceptList,
|
||||
AccessControlMode.rejectSelected => rejectList,
|
||||
};
|
||||
|
||||
AccessControl copyWithNewList(List<String> value) => switch (mode) {
|
||||
AccessControlProps copyWithNewList(List<String> value) => switch (mode) {
|
||||
AccessControlMode.acceptSelected => copyWith(acceptList: value),
|
||||
AccessControlMode.rejectSelected => copyWith(rejectList: value),
|
||||
};
|
||||
@@ -151,7 +151,7 @@ abstract class VpnProps with _$VpnProps {
|
||||
@Default(false) bool ipv6,
|
||||
@Default(true) bool allowBypass,
|
||||
@Default(false) bool dnsHijacking,
|
||||
@Default(defaultAccessControl) AccessControl accessControl,
|
||||
@Default(defaultAccessControlProps) AccessControlProps accessControlProps,
|
||||
}) = _VpnProps;
|
||||
|
||||
factory VpnProps.fromJson(Map<String, Object?>? json) =>
|
||||
@@ -173,17 +173,18 @@ abstract class NetworkProps with _$NetworkProps {
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class ProxiesStyle with _$ProxiesStyle {
|
||||
const factory ProxiesStyle({
|
||||
abstract class ProxiesStyleProps with _$ProxiesStyleProps {
|
||||
const factory ProxiesStyleProps({
|
||||
@Default(ProxiesType.tab) ProxiesType type,
|
||||
@Default(ProxiesSortType.none) ProxiesSortType sortType,
|
||||
@Default(ProxiesLayout.standard) ProxiesLayout layout,
|
||||
@Default(ProxiesIconStyle.standard) ProxiesIconStyle iconStyle,
|
||||
@Default(ProxyCardType.expand) ProxyCardType cardType,
|
||||
}) = _ProxiesStyle;
|
||||
}) = _ProxiesStyleProps;
|
||||
|
||||
factory ProxiesStyle.fromJson(Map<String, Object?>? json) =>
|
||||
json == null ? defaultProxiesStyle : _$ProxiesStyleFromJson(json);
|
||||
factory ProxiesStyleProps.fromJson(Map<String, Object?>? json) => json == null
|
||||
? defaultProxiesStyleProps
|
||||
: _$ProxiesStylePropsFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -223,63 +224,30 @@ abstract class ThemeProps with _$ThemeProps {
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class ScriptProps with _$ScriptProps {
|
||||
const factory ScriptProps({
|
||||
String? currentId,
|
||||
@Default([]) List<Script> scripts,
|
||||
}) = _ScriptProps;
|
||||
|
||||
factory ScriptProps.fromJson(Map<String, Object?> json) =>
|
||||
_$ScriptPropsFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class Config with _$Config {
|
||||
const factory Config({
|
||||
int? currentProfileId,
|
||||
@Default(false) bool overrideDns,
|
||||
@Default([]) List<HotKeyAction> hotKeyActions,
|
||||
@JsonKey(fromJson: AppSettingProps.safeFromJson)
|
||||
@Default(defaultAppSettingProps)
|
||||
AppSettingProps appSetting,
|
||||
@Default([]) List<Profile> profiles,
|
||||
@Default([]) List<HotKeyAction> hotKeyActions,
|
||||
String? currentProfileId,
|
||||
@Default(false) bool overrideDns,
|
||||
DAV? dav,
|
||||
AppSettingProps appSettingProps,
|
||||
DAVProps? davProps,
|
||||
@Default(defaultNetworkProps) NetworkProps networkProps,
|
||||
@Default(defaultVpnProps) VpnProps vpnProps,
|
||||
@JsonKey(fromJson: ThemeProps.safeFromJson) required ThemeProps themeProps,
|
||||
@Default(defaultProxiesStyle) ProxiesStyle proxiesStyle,
|
||||
@Default(defaultProxiesStyleProps) ProxiesStyleProps proxiesStyleProps,
|
||||
@Default(defaultWindowProps) WindowProps windowProps,
|
||||
@Default(defaultClashConfig) ClashConfig patchClashConfig,
|
||||
@Default([]) List<Script> scripts,
|
||||
@Default([]) List<Rule> rules,
|
||||
}) = _Config;
|
||||
|
||||
factory Config.fromJson(Map<String, Object?> json) => _$ConfigFromJson(json);
|
||||
|
||||
factory Config.compatibleFromJson(Map<String, Object?> json) {
|
||||
try {
|
||||
final accessControlMap = json['accessControl'];
|
||||
final isAccessControl = json['isAccessControl'];
|
||||
if (accessControlMap != null) {
|
||||
(accessControlMap as Map)['enable'] = isAccessControl;
|
||||
if (json['vpnProps'] != null) {
|
||||
(json['vpnProps'] as Map)['accessControl'] = accessControlMap;
|
||||
}
|
||||
}
|
||||
if (json['scripts'] == null) {
|
||||
final scriptPropsJson = json['scriptProps'] as Map<String, Object?>?;
|
||||
if (scriptPropsJson != null) {
|
||||
json['scripts'] = scriptPropsJson['scripts'];
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
return Config.fromJson(json);
|
||||
}
|
||||
}
|
||||
|
||||
extension ConfigExt on Config {
|
||||
Profile? get currentProfile {
|
||||
return profiles.getProfile(currentProfileId);
|
||||
factory Config.realFromJson(Map<String, Object?>? json) {
|
||||
if (json == null) {
|
||||
return Config(themeProps: defaultThemeProps);
|
||||
}
|
||||
return _$ConfigFromJson(json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ abstract class VpnOptions with _$VpnOptions {
|
||||
required int port,
|
||||
required bool ipv6,
|
||||
required bool dnsHijacking,
|
||||
required AccessControl accessControl,
|
||||
required AccessControlProps accessControlProps,
|
||||
required bool allowBypass,
|
||||
required bool systemProxy,
|
||||
required List<String> bypassDomain,
|
||||
@@ -154,7 +154,6 @@ abstract class ExternalProvider with _$ExternalProvider {
|
||||
required int count,
|
||||
@JsonKey(name: 'subscription-info', fromJson: subscriptionInfoFormCore)
|
||||
SubscriptionInfo? subscriptionInfo,
|
||||
@Default(false) bool isUpdating,
|
||||
@JsonKey(name: 'vehicle-type') required String vehicleType,
|
||||
@JsonKey(name: 'update-at') required DateTime updateAt,
|
||||
}) = _ExternalProvider;
|
||||
@@ -163,6 +162,10 @@ abstract class ExternalProvider with _$ExternalProvider {
|
||||
_$ExternalProviderFromJson(json);
|
||||
}
|
||||
|
||||
extension ExternalProviderExt on ExternalProvider {
|
||||
String get updatingKey => 'provider_$name';
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class Action with _$Action {
|
||||
const factory Action({
|
||||
@@ -174,6 +177,17 @@ abstract class Action with _$Action {
|
||||
factory Action.fromJson(Map<String, Object?> json) => _$ActionFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class ProxiesData with _$ProxiesData {
|
||||
const factory ProxiesData({
|
||||
required Map<String, dynamic> proxies,
|
||||
required List<String> all,
|
||||
}) = _ProxiesData;
|
||||
|
||||
factory ProxiesData.fromJson(Map<String, Object?> json) =>
|
||||
_$ProxiesDataFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class ActionResult with _$ActionResult {
|
||||
const factory ActionResult({
|
||||
|
||||
@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$AppState {
|
||||
|
||||
bool get isInit; bool get backBlock; PageLabel get pageLabel; List<Package> get packages; int get sortNum; Size get viewSize; double get sideWidth; DelayMap get delayMap; List<Group> get groups; int get checkIpNum; Brightness get brightness; int? get runTime; List<ExternalProvider> get providers; String? get localIp; FixedList<TrackerInfo> get requests; int get version; FixedList<Log> get logs; FixedList<Traffic> get traffics; Traffic get totalTraffic; bool get realTunEnable; bool get loading; SystemUiOverlayStyle get systemUiOverlayStyle; ProfileOverrideModel? get profileOverrideModel; Map<QueryTag, String> get queryMap; Map<String, String> get selectedItemMap; Map<String, Set<String>> get selectedItemsMap; CoreStatus get coreStatus;
|
||||
bool get isInit; bool get backBlock; PageLabel get pageLabel; List<Package> get packages; int get sortNum; Size get viewSize; double get sideWidth; DelayMap get delayMap; List<Group> get groups; int get checkIpNum; Brightness get brightness; int? get runTime; List<ExternalProvider> get providers; String? get localIp; FixedList<TrackerInfo> get requests; int get version; FixedList<Log> get logs; FixedList<Traffic> get traffics; Traffic get totalTraffic; bool get realTunEnable; bool get loading; SystemUiOverlayStyle get systemUiOverlayStyle; CoreStatus get coreStatus;
|
||||
/// Create a copy of AppState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -25,16 +25,16 @@ $AppStateCopyWith<AppState> get copyWith => _$AppStateCopyWithImpl<AppState>(thi
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.isInit, isInit) || other.isInit == isInit)&&(identical(other.backBlock, backBlock) || other.backBlock == backBlock)&&(identical(other.pageLabel, pageLabel) || other.pageLabel == pageLabel)&&const DeepCollectionEquality().equals(other.packages, packages)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.viewSize, viewSize) || other.viewSize == viewSize)&&(identical(other.sideWidth, sideWidth) || other.sideWidth == sideWidth)&&const DeepCollectionEquality().equals(other.delayMap, delayMap)&&const DeepCollectionEquality().equals(other.groups, groups)&&(identical(other.checkIpNum, checkIpNum) || other.checkIpNum == checkIpNum)&&(identical(other.brightness, brightness) || other.brightness == brightness)&&(identical(other.runTime, runTime) || other.runTime == runTime)&&const DeepCollectionEquality().equals(other.providers, providers)&&(identical(other.localIp, localIp) || other.localIp == localIp)&&(identical(other.requests, requests) || other.requests == requests)&&(identical(other.version, version) || other.version == version)&&(identical(other.logs, logs) || other.logs == logs)&&(identical(other.traffics, traffics) || other.traffics == traffics)&&(identical(other.totalTraffic, totalTraffic) || other.totalTraffic == totalTraffic)&&(identical(other.realTunEnable, realTunEnable) || other.realTunEnable == realTunEnable)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.systemUiOverlayStyle, systemUiOverlayStyle) || other.systemUiOverlayStyle == systemUiOverlayStyle)&&(identical(other.profileOverrideModel, profileOverrideModel) || other.profileOverrideModel == profileOverrideModel)&&const DeepCollectionEquality().equals(other.queryMap, queryMap)&&const DeepCollectionEquality().equals(other.selectedItemMap, selectedItemMap)&&const DeepCollectionEquality().equals(other.selectedItemsMap, selectedItemsMap)&&(identical(other.coreStatus, coreStatus) || other.coreStatus == coreStatus));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.isInit, isInit) || other.isInit == isInit)&&(identical(other.backBlock, backBlock) || other.backBlock == backBlock)&&(identical(other.pageLabel, pageLabel) || other.pageLabel == pageLabel)&&const DeepCollectionEquality().equals(other.packages, packages)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.viewSize, viewSize) || other.viewSize == viewSize)&&(identical(other.sideWidth, sideWidth) || other.sideWidth == sideWidth)&&const DeepCollectionEquality().equals(other.delayMap, delayMap)&&const DeepCollectionEquality().equals(other.groups, groups)&&(identical(other.checkIpNum, checkIpNum) || other.checkIpNum == checkIpNum)&&(identical(other.brightness, brightness) || other.brightness == brightness)&&(identical(other.runTime, runTime) || other.runTime == runTime)&&const DeepCollectionEquality().equals(other.providers, providers)&&(identical(other.localIp, localIp) || other.localIp == localIp)&&(identical(other.requests, requests) || other.requests == requests)&&(identical(other.version, version) || other.version == version)&&(identical(other.logs, logs) || other.logs == logs)&&(identical(other.traffics, traffics) || other.traffics == traffics)&&(identical(other.totalTraffic, totalTraffic) || other.totalTraffic == totalTraffic)&&(identical(other.realTunEnable, realTunEnable) || other.realTunEnable == realTunEnable)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.systemUiOverlayStyle, systemUiOverlayStyle) || other.systemUiOverlayStyle == systemUiOverlayStyle)&&(identical(other.coreStatus, coreStatus) || other.coreStatus == coreStatus));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll([runtimeType,isInit,backBlock,pageLabel,const DeepCollectionEquality().hash(packages),sortNum,viewSize,sideWidth,const DeepCollectionEquality().hash(delayMap),const DeepCollectionEquality().hash(groups),checkIpNum,brightness,runTime,const DeepCollectionEquality().hash(providers),localIp,requests,version,logs,traffics,totalTraffic,realTunEnable,loading,systemUiOverlayStyle,profileOverrideModel,const DeepCollectionEquality().hash(queryMap),const DeepCollectionEquality().hash(selectedItemMap),const DeepCollectionEquality().hash(selectedItemsMap),coreStatus]);
|
||||
int get hashCode => Object.hashAll([runtimeType,isInit,backBlock,pageLabel,const DeepCollectionEquality().hash(packages),sortNum,viewSize,sideWidth,const DeepCollectionEquality().hash(delayMap),const DeepCollectionEquality().hash(groups),checkIpNum,brightness,runTime,const DeepCollectionEquality().hash(providers),localIp,requests,version,logs,traffics,totalTraffic,realTunEnable,loading,systemUiOverlayStyle,coreStatus]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppState(isInit: $isInit, backBlock: $backBlock, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, sideWidth: $sideWidth, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, realTunEnable: $realTunEnable, loading: $loading, systemUiOverlayStyle: $systemUiOverlayStyle, profileOverrideModel: $profileOverrideModel, queryMap: $queryMap, selectedItemMap: $selectedItemMap, selectedItemsMap: $selectedItemsMap, coreStatus: $coreStatus)';
|
||||
return 'AppState(isInit: $isInit, backBlock: $backBlock, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, sideWidth: $sideWidth, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, realTunEnable: $realTunEnable, loading: $loading, systemUiOverlayStyle: $systemUiOverlayStyle, coreStatus: $coreStatus)';
|
||||
}
|
||||
|
||||
|
||||
@@ -45,11 +45,11 @@ abstract mixin class $AppStateCopyWith<$Res> {
|
||||
factory $AppStateCopyWith(AppState value, $Res Function(AppState) _then) = _$AppStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, ProfileOverrideModel? profileOverrideModel, Map<QueryTag, String> queryMap, Map<String, String> selectedItemMap, Map<String, Set<String>> selectedItemsMap, CoreStatus coreStatus
|
||||
bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus
|
||||
});
|
||||
|
||||
|
||||
$TrafficCopyWith<$Res> get totalTraffic;$ProfileOverrideModelCopyWith<$Res>? get profileOverrideModel;
|
||||
$TrafficCopyWith<$Res> get totalTraffic;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
@@ -62,7 +62,7 @@ class _$AppStateCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of AppState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? isInit = null,Object? backBlock = null,Object? pageLabel = null,Object? packages = null,Object? sortNum = null,Object? viewSize = null,Object? sideWidth = null,Object? delayMap = null,Object? groups = null,Object? checkIpNum = null,Object? brightness = null,Object? runTime = freezed,Object? providers = null,Object? localIp = freezed,Object? requests = null,Object? version = null,Object? logs = null,Object? traffics = null,Object? totalTraffic = null,Object? realTunEnable = null,Object? loading = null,Object? systemUiOverlayStyle = null,Object? profileOverrideModel = freezed,Object? queryMap = null,Object? selectedItemMap = null,Object? selectedItemsMap = null,Object? coreStatus = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? isInit = null,Object? backBlock = null,Object? pageLabel = null,Object? packages = null,Object? sortNum = null,Object? viewSize = null,Object? sideWidth = null,Object? delayMap = null,Object? groups = null,Object? checkIpNum = null,Object? brightness = null,Object? runTime = freezed,Object? providers = null,Object? localIp = freezed,Object? requests = null,Object? version = null,Object? logs = null,Object? traffics = null,Object? totalTraffic = null,Object? realTunEnable = null,Object? loading = null,Object? systemUiOverlayStyle = null,Object? coreStatus = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
isInit: null == isInit ? _self.isInit : isInit // ignore: cast_nullable_to_non_nullable
|
||||
as bool,backBlock: null == backBlock ? _self.backBlock : backBlock // ignore: cast_nullable_to_non_nullable
|
||||
@@ -86,11 +86,7 @@ as FixedList<Traffic>,totalTraffic: null == totalTraffic ? _self.totalTraffic :
|
||||
as Traffic,realTunEnable: null == realTunEnable ? _self.realTunEnable : realTunEnable // ignore: cast_nullable_to_non_nullable
|
||||
as bool,loading: null == loading ? _self.loading : loading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,systemUiOverlayStyle: null == systemUiOverlayStyle ? _self.systemUiOverlayStyle : systemUiOverlayStyle // ignore: cast_nullable_to_non_nullable
|
||||
as SystemUiOverlayStyle,profileOverrideModel: freezed == profileOverrideModel ? _self.profileOverrideModel : profileOverrideModel // ignore: cast_nullable_to_non_nullable
|
||||
as ProfileOverrideModel?,queryMap: null == queryMap ? _self.queryMap : queryMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<QueryTag, String>,selectedItemMap: null == selectedItemMap ? _self.selectedItemMap : selectedItemMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>,selectedItemsMap: null == selectedItemsMap ? _self.selectedItemsMap : selectedItemsMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, Set<String>>,coreStatus: null == coreStatus ? _self.coreStatus : coreStatus // ignore: cast_nullable_to_non_nullable
|
||||
as SystemUiOverlayStyle,coreStatus: null == coreStatus ? _self.coreStatus : coreStatus // ignore: cast_nullable_to_non_nullable
|
||||
as CoreStatus,
|
||||
));
|
||||
}
|
||||
@@ -103,18 +99,6 @@ $TrafficCopyWith<$Res> get totalTraffic {
|
||||
return $TrafficCopyWith<$Res>(_self.totalTraffic, (value) {
|
||||
return _then(_self.copyWith(totalTraffic: value));
|
||||
});
|
||||
}/// Create a copy of AppState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ProfileOverrideModelCopyWith<$Res>? get profileOverrideModel {
|
||||
if (_self.profileOverrideModel == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $ProfileOverrideModelCopyWith<$Res>(_self.profileOverrideModel!, (value) {
|
||||
return _then(_self.copyWith(profileOverrideModel: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,10 +181,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, ProfileOverrideModel? profileOverrideModel, Map<QueryTag, String> queryMap, Map<String, String> selectedItemMap, Map<String, Set<String>> selectedItemsMap, CoreStatus coreStatus)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppState() when $default != null:
|
||||
return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.realTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.profileOverrideModel,_that.queryMap,_that.selectedItemMap,_that.selectedItemsMap,_that.coreStatus);case _:
|
||||
return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.realTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.coreStatus);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -218,10 +202,10 @@ return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_tha
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, ProfileOverrideModel? profileOverrideModel, Map<QueryTag, String> queryMap, Map<String, String> selectedItemMap, Map<String, Set<String>> selectedItemsMap, CoreStatus coreStatus) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppState():
|
||||
return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.realTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.profileOverrideModel,_that.queryMap,_that.selectedItemMap,_that.selectedItemsMap,_that.coreStatus);case _:
|
||||
return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.realTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.coreStatus);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -238,10 +222,10 @@ return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_tha
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, ProfileOverrideModel? profileOverrideModel, Map<QueryTag, String> queryMap, Map<String, String> selectedItemMap, Map<String, Set<String>> selectedItemsMap, CoreStatus coreStatus)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppState() when $default != null:
|
||||
return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.realTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.profileOverrideModel,_that.queryMap,_that.selectedItemMap,_that.selectedItemsMap,_that.coreStatus);case _:
|
||||
return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.realTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.coreStatus);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -253,7 +237,7 @@ return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_tha
|
||||
|
||||
|
||||
class _AppState implements AppState {
|
||||
const _AppState({this.isInit = false, this.backBlock = false, this.pageLabel = PageLabel.dashboard, final List<Package> packages = const [], this.sortNum = 0, required this.viewSize, this.sideWidth = 0, final DelayMap delayMap = const {}, final List<Group> groups = const [], this.checkIpNum = 0, required this.brightness, this.runTime, final List<ExternalProvider> providers = const [], this.localIp, required this.requests, required this.version, required this.logs, required this.traffics, required this.totalTraffic, this.realTunEnable = false, this.loading = false, required this.systemUiOverlayStyle, this.profileOverrideModel, final Map<QueryTag, String> queryMap = const {}, final Map<String, String> selectedItemMap = const {}, final Map<String, Set<String>> selectedItemsMap = const {}, this.coreStatus = CoreStatus.connecting}): _packages = packages,_delayMap = delayMap,_groups = groups,_providers = providers,_queryMap = queryMap,_selectedItemMap = selectedItemMap,_selectedItemsMap = selectedItemsMap;
|
||||
const _AppState({this.isInit = false, this.backBlock = false, this.pageLabel = PageLabel.dashboard, final List<Package> packages = const [], this.sortNum = 0, required this.viewSize, this.sideWidth = 0, final DelayMap delayMap = const {}, final List<Group> groups = const [], this.checkIpNum = 0, required this.brightness, this.runTime, final List<ExternalProvider> providers = const [], this.localIp, required this.requests, required this.version, required this.logs, required this.traffics, required this.totalTraffic, this.realTunEnable = false, this.loading = false, required this.systemUiOverlayStyle, this.coreStatus = CoreStatus.connecting}): _packages = packages,_delayMap = delayMap,_groups = groups,_providers = providers;
|
||||
|
||||
|
||||
@override@JsonKey() final bool isInit;
|
||||
@@ -302,28 +286,6 @@ class _AppState implements AppState {
|
||||
@override@JsonKey() final bool realTunEnable;
|
||||
@override@JsonKey() final bool loading;
|
||||
@override final SystemUiOverlayStyle systemUiOverlayStyle;
|
||||
@override final ProfileOverrideModel? profileOverrideModel;
|
||||
final Map<QueryTag, String> _queryMap;
|
||||
@override@JsonKey() Map<QueryTag, String> get queryMap {
|
||||
if (_queryMap is EqualUnmodifiableMapView) return _queryMap;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_queryMap);
|
||||
}
|
||||
|
||||
final Map<String, String> _selectedItemMap;
|
||||
@override@JsonKey() Map<String, String> get selectedItemMap {
|
||||
if (_selectedItemMap is EqualUnmodifiableMapView) return _selectedItemMap;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_selectedItemMap);
|
||||
}
|
||||
|
||||
final Map<String, Set<String>> _selectedItemsMap;
|
||||
@override@JsonKey() Map<String, Set<String>> get selectedItemsMap {
|
||||
if (_selectedItemsMap is EqualUnmodifiableMapView) return _selectedItemsMap;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_selectedItemsMap);
|
||||
}
|
||||
|
||||
@override@JsonKey() final CoreStatus coreStatus;
|
||||
|
||||
/// Create a copy of AppState
|
||||
@@ -336,16 +298,16 @@ _$AppStateCopyWith<_AppState> get copyWith => __$AppStateCopyWithImpl<_AppState>
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.isInit, isInit) || other.isInit == isInit)&&(identical(other.backBlock, backBlock) || other.backBlock == backBlock)&&(identical(other.pageLabel, pageLabel) || other.pageLabel == pageLabel)&&const DeepCollectionEquality().equals(other._packages, _packages)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.viewSize, viewSize) || other.viewSize == viewSize)&&(identical(other.sideWidth, sideWidth) || other.sideWidth == sideWidth)&&const DeepCollectionEquality().equals(other._delayMap, _delayMap)&&const DeepCollectionEquality().equals(other._groups, _groups)&&(identical(other.checkIpNum, checkIpNum) || other.checkIpNum == checkIpNum)&&(identical(other.brightness, brightness) || other.brightness == brightness)&&(identical(other.runTime, runTime) || other.runTime == runTime)&&const DeepCollectionEquality().equals(other._providers, _providers)&&(identical(other.localIp, localIp) || other.localIp == localIp)&&(identical(other.requests, requests) || other.requests == requests)&&(identical(other.version, version) || other.version == version)&&(identical(other.logs, logs) || other.logs == logs)&&(identical(other.traffics, traffics) || other.traffics == traffics)&&(identical(other.totalTraffic, totalTraffic) || other.totalTraffic == totalTraffic)&&(identical(other.realTunEnable, realTunEnable) || other.realTunEnable == realTunEnable)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.systemUiOverlayStyle, systemUiOverlayStyle) || other.systemUiOverlayStyle == systemUiOverlayStyle)&&(identical(other.profileOverrideModel, profileOverrideModel) || other.profileOverrideModel == profileOverrideModel)&&const DeepCollectionEquality().equals(other._queryMap, _queryMap)&&const DeepCollectionEquality().equals(other._selectedItemMap, _selectedItemMap)&&const DeepCollectionEquality().equals(other._selectedItemsMap, _selectedItemsMap)&&(identical(other.coreStatus, coreStatus) || other.coreStatus == coreStatus));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.isInit, isInit) || other.isInit == isInit)&&(identical(other.backBlock, backBlock) || other.backBlock == backBlock)&&(identical(other.pageLabel, pageLabel) || other.pageLabel == pageLabel)&&const DeepCollectionEquality().equals(other._packages, _packages)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.viewSize, viewSize) || other.viewSize == viewSize)&&(identical(other.sideWidth, sideWidth) || other.sideWidth == sideWidth)&&const DeepCollectionEquality().equals(other._delayMap, _delayMap)&&const DeepCollectionEquality().equals(other._groups, _groups)&&(identical(other.checkIpNum, checkIpNum) || other.checkIpNum == checkIpNum)&&(identical(other.brightness, brightness) || other.brightness == brightness)&&(identical(other.runTime, runTime) || other.runTime == runTime)&&const DeepCollectionEquality().equals(other._providers, _providers)&&(identical(other.localIp, localIp) || other.localIp == localIp)&&(identical(other.requests, requests) || other.requests == requests)&&(identical(other.version, version) || other.version == version)&&(identical(other.logs, logs) || other.logs == logs)&&(identical(other.traffics, traffics) || other.traffics == traffics)&&(identical(other.totalTraffic, totalTraffic) || other.totalTraffic == totalTraffic)&&(identical(other.realTunEnable, realTunEnable) || other.realTunEnable == realTunEnable)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.systemUiOverlayStyle, systemUiOverlayStyle) || other.systemUiOverlayStyle == systemUiOverlayStyle)&&(identical(other.coreStatus, coreStatus) || other.coreStatus == coreStatus));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll([runtimeType,isInit,backBlock,pageLabel,const DeepCollectionEquality().hash(_packages),sortNum,viewSize,sideWidth,const DeepCollectionEquality().hash(_delayMap),const DeepCollectionEquality().hash(_groups),checkIpNum,brightness,runTime,const DeepCollectionEquality().hash(_providers),localIp,requests,version,logs,traffics,totalTraffic,realTunEnable,loading,systemUiOverlayStyle,profileOverrideModel,const DeepCollectionEquality().hash(_queryMap),const DeepCollectionEquality().hash(_selectedItemMap),const DeepCollectionEquality().hash(_selectedItemsMap),coreStatus]);
|
||||
int get hashCode => Object.hashAll([runtimeType,isInit,backBlock,pageLabel,const DeepCollectionEquality().hash(_packages),sortNum,viewSize,sideWidth,const DeepCollectionEquality().hash(_delayMap),const DeepCollectionEquality().hash(_groups),checkIpNum,brightness,runTime,const DeepCollectionEquality().hash(_providers),localIp,requests,version,logs,traffics,totalTraffic,realTunEnable,loading,systemUiOverlayStyle,coreStatus]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppState(isInit: $isInit, backBlock: $backBlock, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, sideWidth: $sideWidth, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, realTunEnable: $realTunEnable, loading: $loading, systemUiOverlayStyle: $systemUiOverlayStyle, profileOverrideModel: $profileOverrideModel, queryMap: $queryMap, selectedItemMap: $selectedItemMap, selectedItemsMap: $selectedItemsMap, coreStatus: $coreStatus)';
|
||||
return 'AppState(isInit: $isInit, backBlock: $backBlock, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, sideWidth: $sideWidth, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, realTunEnable: $realTunEnable, loading: $loading, systemUiOverlayStyle: $systemUiOverlayStyle, coreStatus: $coreStatus)';
|
||||
}
|
||||
|
||||
|
||||
@@ -356,11 +318,11 @@ abstract mixin class _$AppStateCopyWith<$Res> implements $AppStateCopyWith<$Res>
|
||||
factory _$AppStateCopyWith(_AppState value, $Res Function(_AppState) _then) = __$AppStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, ProfileOverrideModel? profileOverrideModel, Map<QueryTag, String> queryMap, Map<String, String> selectedItemMap, Map<String, Set<String>> selectedItemsMap, CoreStatus coreStatus
|
||||
bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus
|
||||
});
|
||||
|
||||
|
||||
@override $TrafficCopyWith<$Res> get totalTraffic;@override $ProfileOverrideModelCopyWith<$Res>? get profileOverrideModel;
|
||||
@override $TrafficCopyWith<$Res> get totalTraffic;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
@@ -373,7 +335,7 @@ class __$AppStateCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of AppState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? isInit = null,Object? backBlock = null,Object? pageLabel = null,Object? packages = null,Object? sortNum = null,Object? viewSize = null,Object? sideWidth = null,Object? delayMap = null,Object? groups = null,Object? checkIpNum = null,Object? brightness = null,Object? runTime = freezed,Object? providers = null,Object? localIp = freezed,Object? requests = null,Object? version = null,Object? logs = null,Object? traffics = null,Object? totalTraffic = null,Object? realTunEnable = null,Object? loading = null,Object? systemUiOverlayStyle = null,Object? profileOverrideModel = freezed,Object? queryMap = null,Object? selectedItemMap = null,Object? selectedItemsMap = null,Object? coreStatus = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? isInit = null,Object? backBlock = null,Object? pageLabel = null,Object? packages = null,Object? sortNum = null,Object? viewSize = null,Object? sideWidth = null,Object? delayMap = null,Object? groups = null,Object? checkIpNum = null,Object? brightness = null,Object? runTime = freezed,Object? providers = null,Object? localIp = freezed,Object? requests = null,Object? version = null,Object? logs = null,Object? traffics = null,Object? totalTraffic = null,Object? realTunEnable = null,Object? loading = null,Object? systemUiOverlayStyle = null,Object? coreStatus = null,}) {
|
||||
return _then(_AppState(
|
||||
isInit: null == isInit ? _self.isInit : isInit // ignore: cast_nullable_to_non_nullable
|
||||
as bool,backBlock: null == backBlock ? _self.backBlock : backBlock // ignore: cast_nullable_to_non_nullable
|
||||
@@ -397,11 +359,7 @@ as FixedList<Traffic>,totalTraffic: null == totalTraffic ? _self.totalTraffic :
|
||||
as Traffic,realTunEnable: null == realTunEnable ? _self.realTunEnable : realTunEnable // ignore: cast_nullable_to_non_nullable
|
||||
as bool,loading: null == loading ? _self.loading : loading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,systemUiOverlayStyle: null == systemUiOverlayStyle ? _self.systemUiOverlayStyle : systemUiOverlayStyle // ignore: cast_nullable_to_non_nullable
|
||||
as SystemUiOverlayStyle,profileOverrideModel: freezed == profileOverrideModel ? _self.profileOverrideModel : profileOverrideModel // ignore: cast_nullable_to_non_nullable
|
||||
as ProfileOverrideModel?,queryMap: null == queryMap ? _self._queryMap : queryMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<QueryTag, String>,selectedItemMap: null == selectedItemMap ? _self._selectedItemMap : selectedItemMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>,selectedItemsMap: null == selectedItemsMap ? _self._selectedItemsMap : selectedItemsMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, Set<String>>,coreStatus: null == coreStatus ? _self.coreStatus : coreStatus // ignore: cast_nullable_to_non_nullable
|
||||
as SystemUiOverlayStyle,coreStatus: null == coreStatus ? _self.coreStatus : coreStatus // ignore: cast_nullable_to_non_nullable
|
||||
as CoreStatus,
|
||||
));
|
||||
}
|
||||
@@ -415,18 +373,6 @@ $TrafficCopyWith<$Res> get totalTraffic {
|
||||
return $TrafficCopyWith<$Res>(_self.totalTraffic, (value) {
|
||||
return _then(_self.copyWith(totalTraffic: value));
|
||||
});
|
||||
}/// Create a copy of AppState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ProfileOverrideModelCopyWith<$Res>? get profileOverrideModel {
|
||||
if (_self.profileOverrideModel == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $ProfileOverrideModelCopyWith<$Res>(_self.profileOverrideModel!, (value) {
|
||||
return _then(_self.copyWith(profileOverrideModel: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2698,7 +2698,7 @@ as bool,
|
||||
/// @nodoc
|
||||
mixin _$Rule {
|
||||
|
||||
String get id; String get value;
|
||||
int get id; String get value; String? get order;
|
||||
/// Create a copy of Rule
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -2711,16 +2711,16 @@ $RuleCopyWith<Rule> get copyWith => _$RuleCopyWithImpl<Rule>(this as Rule, _$ide
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is Rule&&(identical(other.id, id) || other.id == id)&&(identical(other.value, value) || other.value == value));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is Rule&&(identical(other.id, id) || other.id == id)&&(identical(other.value, value) || other.value == value)&&(identical(other.order, order) || other.order == order));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,value);
|
||||
int get hashCode => Object.hash(runtimeType,id,value,order);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Rule(id: $id, value: $value)';
|
||||
return 'Rule(id: $id, value: $value, order: $order)';
|
||||
}
|
||||
|
||||
|
||||
@@ -2731,7 +2731,7 @@ abstract mixin class $RuleCopyWith<$Res> {
|
||||
factory $RuleCopyWith(Rule value, $Res Function(Rule) _then) = _$RuleCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String value
|
||||
int id, String value, String? order
|
||||
});
|
||||
|
||||
|
||||
@@ -2748,11 +2748,12 @@ class _$RuleCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of Rule
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? value = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? value = null,Object? order = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,value: null == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
as int,value: null == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
|
||||
as String,order: freezed == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -2837,10 +2838,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String value)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int id, String value, String? order)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Rule() when $default != null:
|
||||
return $default(_that.id,_that.value);case _:
|
||||
return $default(_that.id,_that.value,_that.order);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -2858,10 +2859,10 @@ return $default(_that.id,_that.value);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String value) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int id, String value, String? order) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Rule():
|
||||
return $default(_that.id,_that.value);case _:
|
||||
return $default(_that.id,_that.value,_that.order);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -2878,10 +2879,10 @@ return $default(_that.id,_that.value);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String value)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int id, String value, String? order)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Rule() when $default != null:
|
||||
return $default(_that.id,_that.value);case _:
|
||||
return $default(_that.id,_that.value,_that.order);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -2893,11 +2894,12 @@ return $default(_that.id,_that.value);case _:
|
||||
@JsonSerializable()
|
||||
|
||||
class _Rule implements Rule {
|
||||
const _Rule({required this.id, required this.value});
|
||||
const _Rule({required this.id, required this.value, this.order});
|
||||
factory _Rule.fromJson(Map<String, dynamic> json) => _$RuleFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final int id;
|
||||
@override final String value;
|
||||
@override final String? order;
|
||||
|
||||
/// Create a copy of Rule
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -2912,16 +2914,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Rule&&(identical(other.id, id) || other.id == id)&&(identical(other.value, value) || other.value == value));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Rule&&(identical(other.id, id) || other.id == id)&&(identical(other.value, value) || other.value == value)&&(identical(other.order, order) || other.order == order));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,value);
|
||||
int get hashCode => Object.hash(runtimeType,id,value,order);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Rule(id: $id, value: $value)';
|
||||
return 'Rule(id: $id, value: $value, order: $order)';
|
||||
}
|
||||
|
||||
|
||||
@@ -2932,7 +2934,7 @@ abstract mixin class _$RuleCopyWith<$Res> implements $RuleCopyWith<$Res> {
|
||||
factory _$RuleCopyWith(_Rule value, $Res Function(_Rule) _then) = __$RuleCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String value
|
||||
int id, String value, String? order
|
||||
});
|
||||
|
||||
|
||||
@@ -2949,11 +2951,12 @@ class __$RuleCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of Rule
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? value = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? value = null,Object? order = freezed,}) {
|
||||
return _then(_Rule(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,value: null == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
as int,value: null == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
|
||||
as String,order: freezed == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -285,12 +285,16 @@ Map<String, dynamic> _$GeoXUrlToJson(_GeoXUrl instance) => <String, dynamic>{
|
||||
'geosite': instance.geosite,
|
||||
};
|
||||
|
||||
_Rule _$RuleFromJson(Map<String, dynamic> json) =>
|
||||
_Rule(id: json['id'] as String, value: json['value'] as String);
|
||||
_Rule _$RuleFromJson(Map<String, dynamic> json) => _Rule(
|
||||
id: (json['id'] as num).toInt(),
|
||||
value: json['value'] as String,
|
||||
order: json['order'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$RuleToJson(_Rule instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'value': instance.value,
|
||||
'order': instance.order,
|
||||
};
|
||||
|
||||
_SubRule _$SubRuleFromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -2033,22 +2033,22 @@ as bool,
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$DAV {
|
||||
mixin _$DAVProps {
|
||||
|
||||
String get uri; String get user; String get password; String get fileName;
|
||||
/// Create a copy of DAV
|
||||
/// Create a copy of DAVProps
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$DAVCopyWith<DAV> get copyWith => _$DAVCopyWithImpl<DAV>(this as DAV, _$identity);
|
||||
$DAVPropsCopyWith<DAVProps> get copyWith => _$DAVPropsCopyWithImpl<DAVProps>(this as DAVProps, _$identity);
|
||||
|
||||
/// Serializes this DAV to a JSON map.
|
||||
/// Serializes this DAVProps to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is DAV&&(identical(other.uri, uri) || other.uri == uri)&&(identical(other.user, user) || other.user == user)&&(identical(other.password, password) || other.password == password)&&(identical(other.fileName, fileName) || other.fileName == fileName));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is DAVProps&&(identical(other.uri, uri) || other.uri == uri)&&(identical(other.user, user) || other.user == user)&&(identical(other.password, password) || other.password == password)&&(identical(other.fileName, fileName) || other.fileName == fileName));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -2057,15 +2057,15 @@ int get hashCode => Object.hash(runtimeType,uri,user,password,fileName);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DAV(uri: $uri, user: $user, password: $password, fileName: $fileName)';
|
||||
return 'DAVProps(uri: $uri, user: $user, password: $password, fileName: $fileName)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $DAVCopyWith<$Res> {
|
||||
factory $DAVCopyWith(DAV value, $Res Function(DAV) _then) = _$DAVCopyWithImpl;
|
||||
abstract mixin class $DAVPropsCopyWith<$Res> {
|
||||
factory $DAVPropsCopyWith(DAVProps value, $Res Function(DAVProps) _then) = _$DAVPropsCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String uri, String user, String password, String fileName
|
||||
@@ -2076,14 +2076,14 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$DAVCopyWithImpl<$Res>
|
||||
implements $DAVCopyWith<$Res> {
|
||||
_$DAVCopyWithImpl(this._self, this._then);
|
||||
class _$DAVPropsCopyWithImpl<$Res>
|
||||
implements $DAVPropsCopyWith<$Res> {
|
||||
_$DAVPropsCopyWithImpl(this._self, this._then);
|
||||
|
||||
final DAV _self;
|
||||
final $Res Function(DAV) _then;
|
||||
final DAVProps _self;
|
||||
final $Res Function(DAVProps) _then;
|
||||
|
||||
/// Create a copy of DAV
|
||||
/// Create a copy of DAVProps
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? uri = null,Object? user = null,Object? password = null,Object? fileName = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
@@ -2098,8 +2098,8 @@ as String,
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [DAV].
|
||||
extension DAVPatterns on DAV {
|
||||
/// Adds pattern-matching-related methods to [DAVProps].
|
||||
extension DAVPropsPatterns on DAVProps {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
@@ -2112,10 +2112,10 @@ extension DAVPatterns on DAV {
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _DAV value)? $default,{required TResult orElse(),}){
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _DAVProps value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _DAV() when $default != null:
|
||||
case _DAVProps() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -2134,10 +2134,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _DAV value) $default,){
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _DAVProps value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _DAV():
|
||||
case _DAVProps():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
@@ -2155,10 +2155,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _DAV value)? $default,){
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _DAVProps value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _DAV() when $default != null:
|
||||
case _DAVProps() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
@@ -2178,7 +2178,7 @@ return $default(_that);case _:
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String uri, String user, String password, String fileName)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _DAV() when $default != null:
|
||||
case _DAVProps() when $default != null:
|
||||
return $default(_that.uri,_that.user,_that.password,_that.fileName);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -2199,7 +2199,7 @@ return $default(_that.uri,_that.user,_that.password,_that.fileName);case _:
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String uri, String user, String password, String fileName) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _DAV():
|
||||
case _DAVProps():
|
||||
return $default(_that.uri,_that.user,_that.password,_that.fileName);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
@@ -2219,7 +2219,7 @@ return $default(_that.uri,_that.user,_that.password,_that.fileName);case _:
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String uri, String user, String password, String fileName)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _DAV() when $default != null:
|
||||
case _DAVProps() when $default != null:
|
||||
return $default(_that.uri,_that.user,_that.password,_that.fileName);case _:
|
||||
return null;
|
||||
|
||||
@@ -2231,29 +2231,29 @@ return $default(_that.uri,_that.user,_that.password,_that.fileName);case _:
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _DAV implements DAV {
|
||||
const _DAV({required this.uri, required this.user, required this.password, this.fileName = defaultDavFileName});
|
||||
factory _DAV.fromJson(Map<String, dynamic> json) => _$DAVFromJson(json);
|
||||
class _DAVProps implements DAVProps {
|
||||
const _DAVProps({required this.uri, required this.user, required this.password, this.fileName = defaultDavFileName});
|
||||
factory _DAVProps.fromJson(Map<String, dynamic> json) => _$DAVPropsFromJson(json);
|
||||
|
||||
@override final String uri;
|
||||
@override final String user;
|
||||
@override final String password;
|
||||
@override@JsonKey() final String fileName;
|
||||
|
||||
/// Create a copy of DAV
|
||||
/// Create a copy of DAVProps
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$DAVCopyWith<_DAV> get copyWith => __$DAVCopyWithImpl<_DAV>(this, _$identity);
|
||||
_$DAVPropsCopyWith<_DAVProps> get copyWith => __$DAVPropsCopyWithImpl<_DAVProps>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$DAVToJson(this, );
|
||||
return _$DAVPropsToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DAV&&(identical(other.uri, uri) || other.uri == uri)&&(identical(other.user, user) || other.user == user)&&(identical(other.password, password) || other.password == password)&&(identical(other.fileName, fileName) || other.fileName == fileName));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DAVProps&&(identical(other.uri, uri) || other.uri == uri)&&(identical(other.user, user) || other.user == user)&&(identical(other.password, password) || other.password == password)&&(identical(other.fileName, fileName) || other.fileName == fileName));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -2262,15 +2262,15 @@ int get hashCode => Object.hash(runtimeType,uri,user,password,fileName);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DAV(uri: $uri, user: $user, password: $password, fileName: $fileName)';
|
||||
return 'DAVProps(uri: $uri, user: $user, password: $password, fileName: $fileName)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$DAVCopyWith<$Res> implements $DAVCopyWith<$Res> {
|
||||
factory _$DAVCopyWith(_DAV value, $Res Function(_DAV) _then) = __$DAVCopyWithImpl;
|
||||
abstract mixin class _$DAVPropsCopyWith<$Res> implements $DAVPropsCopyWith<$Res> {
|
||||
factory _$DAVPropsCopyWith(_DAVProps value, $Res Function(_DAVProps) _then) = __$DAVPropsCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String uri, String user, String password, String fileName
|
||||
@@ -2281,17 +2281,17 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$DAVCopyWithImpl<$Res>
|
||||
implements _$DAVCopyWith<$Res> {
|
||||
__$DAVCopyWithImpl(this._self, this._then);
|
||||
class __$DAVPropsCopyWithImpl<$Res>
|
||||
implements _$DAVPropsCopyWith<$Res> {
|
||||
__$DAVPropsCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _DAV _self;
|
||||
final $Res Function(_DAV) _then;
|
||||
final _DAVProps _self;
|
||||
final $Res Function(_DAVProps) _then;
|
||||
|
||||
/// Create a copy of DAV
|
||||
/// Create a copy of DAVProps
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? uri = null,Object? user = null,Object? password = null,Object? fileName = null,}) {
|
||||
return _then(_DAV(
|
||||
return _then(_DAVProps(
|
||||
uri: null == uri ? _self.uri : uri // ignore: cast_nullable_to_non_nullable
|
||||
as String,user: null == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
|
||||
as String,password: null == password ? _self.password : password // ignore: cast_nullable_to_non_nullable
|
||||
@@ -4967,278 +4967,6 @@ as Validator?,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$AndroidState {
|
||||
|
||||
String get currentProfileName; String get stopText; bool get onlyStatisticsProxy; bool get crashlytics;
|
||||
/// Create a copy of AndroidState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$AndroidStateCopyWith<AndroidState> get copyWith => _$AndroidStateCopyWithImpl<AndroidState>(this as AndroidState, _$identity);
|
||||
|
||||
/// Serializes this AndroidState to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AndroidState&&(identical(other.currentProfileName, currentProfileName) || other.currentProfileName == currentProfileName)&&(identical(other.stopText, stopText) || other.stopText == stopText)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,currentProfileName,stopText,onlyStatisticsProxy,crashlytics);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AndroidState(currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy, crashlytics: $crashlytics)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $AndroidStateCopyWith<$Res> {
|
||||
factory $AndroidStateCopyWith(AndroidState value, $Res Function(AndroidState) _then) = _$AndroidStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$AndroidStateCopyWithImpl<$Res>
|
||||
implements $AndroidStateCopyWith<$Res> {
|
||||
_$AndroidStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final AndroidState _self;
|
||||
final $Res Function(AndroidState) _then;
|
||||
|
||||
/// Create a copy of AndroidState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? currentProfileName = null,Object? stopText = null,Object? onlyStatisticsProxy = null,Object? crashlytics = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
currentProfileName: null == currentProfileName ? _self.currentProfileName : currentProfileName // ignore: cast_nullable_to_non_nullable
|
||||
as String,stopText: null == stopText ? _self.stopText : stopText // ignore: cast_nullable_to_non_nullable
|
||||
as String,onlyStatisticsProxy: null == onlyStatisticsProxy ? _self.onlyStatisticsProxy : onlyStatisticsProxy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,crashlytics: null == crashlytics ? _self.crashlytics : crashlytics // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [AndroidState].
|
||||
extension AndroidStatePatterns on AndroidState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _AndroidState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _AndroidState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _AndroidState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _AndroidState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _AndroidState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _AndroidState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AndroidState() when $default != null:
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AndroidState():
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AndroidState() when $default != null:
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _AndroidState implements AndroidState {
|
||||
const _AndroidState({required this.currentProfileName, required this.stopText, required this.onlyStatisticsProxy, required this.crashlytics});
|
||||
factory _AndroidState.fromJson(Map<String, dynamic> json) => _$AndroidStateFromJson(json);
|
||||
|
||||
@override final String currentProfileName;
|
||||
@override final String stopText;
|
||||
@override final bool onlyStatisticsProxy;
|
||||
@override final bool crashlytics;
|
||||
|
||||
/// Create a copy of AndroidState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$AndroidStateCopyWith<_AndroidState> get copyWith => __$AndroidStateCopyWithImpl<_AndroidState>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$AndroidStateToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AndroidState&&(identical(other.currentProfileName, currentProfileName) || other.currentProfileName == currentProfileName)&&(identical(other.stopText, stopText) || other.stopText == stopText)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,currentProfileName,stopText,onlyStatisticsProxy,crashlytics);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AndroidState(currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy, crashlytics: $crashlytics)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$AndroidStateCopyWith<$Res> implements $AndroidStateCopyWith<$Res> {
|
||||
factory _$AndroidStateCopyWith(_AndroidState value, $Res Function(_AndroidState) _then) = __$AndroidStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$AndroidStateCopyWithImpl<$Res>
|
||||
implements _$AndroidStateCopyWith<$Res> {
|
||||
__$AndroidStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _AndroidState _self;
|
||||
final $Res Function(_AndroidState) _then;
|
||||
|
||||
/// Create a copy of AndroidState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? currentProfileName = null,Object? stopText = null,Object? onlyStatisticsProxy = null,Object? crashlytics = null,}) {
|
||||
return _then(_AndroidState(
|
||||
currentProfileName: null == currentProfileName ? _self.currentProfileName : currentProfileName // ignore: cast_nullable_to_non_nullable
|
||||
as String,stopText: null == stopText ? _self.stopText : stopText // ignore: cast_nullable_to_non_nullable
|
||||
as String,onlyStatisticsProxy: null == onlyStatisticsProxy ? _self.onlyStatisticsProxy : onlyStatisticsProxy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,crashlytics: null == crashlytics ? _self.crashlytics : crashlytics // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -5508,7 +5236,7 @@ as String,
|
||||
/// @nodoc
|
||||
mixin _$Script {
|
||||
|
||||
String get id; String get label; String get content;
|
||||
int get id; String get label; DateTime get lastUpdateTime;
|
||||
/// Create a copy of Script
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -5521,16 +5249,16 @@ $ScriptCopyWith<Script> get copyWith => _$ScriptCopyWithImpl<Script>(this as Scr
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is Script&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.content, content) || other.content == content));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is Script&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.lastUpdateTime, lastUpdateTime) || other.lastUpdateTime == lastUpdateTime));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,label,content);
|
||||
int get hashCode => Object.hash(runtimeType,id,label,lastUpdateTime);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Script(id: $id, label: $label, content: $content)';
|
||||
return 'Script(id: $id, label: $label, lastUpdateTime: $lastUpdateTime)';
|
||||
}
|
||||
|
||||
|
||||
@@ -5541,7 +5269,7 @@ abstract mixin class $ScriptCopyWith<$Res> {
|
||||
factory $ScriptCopyWith(Script value, $Res Function(Script) _then) = _$ScriptCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String label, String content
|
||||
int id, String label, DateTime lastUpdateTime
|
||||
});
|
||||
|
||||
|
||||
@@ -5558,12 +5286,12 @@ class _$ScriptCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of Script
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? label = null,Object? content = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? label = null,Object? lastUpdateTime = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||
as String,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
as int,label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||
as String,lastUpdateTime: null == lastUpdateTime ? _self.lastUpdateTime : lastUpdateTime // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -5648,10 +5376,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String label, String content)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int id, String label, DateTime lastUpdateTime)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Script() when $default != null:
|
||||
return $default(_that.id,_that.label,_that.content);case _:
|
||||
return $default(_that.id,_that.label,_that.lastUpdateTime);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -5669,10 +5397,10 @@ return $default(_that.id,_that.label,_that.content);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String label, String content) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int id, String label, DateTime lastUpdateTime) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Script():
|
||||
return $default(_that.id,_that.label,_that.content);case _:
|
||||
return $default(_that.id,_that.label,_that.lastUpdateTime);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -5689,10 +5417,10 @@ return $default(_that.id,_that.label,_that.content);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String label, String content)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int id, String label, DateTime lastUpdateTime)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Script() when $default != null:
|
||||
return $default(_that.id,_that.label,_that.content);case _:
|
||||
return $default(_that.id,_that.label,_that.lastUpdateTime);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -5704,12 +5432,12 @@ return $default(_that.id,_that.label,_that.content);case _:
|
||||
@JsonSerializable()
|
||||
|
||||
class _Script implements Script {
|
||||
const _Script({required this.id, required this.label, required this.content});
|
||||
const _Script({required this.id, required this.label, required this.lastUpdateTime});
|
||||
factory _Script.fromJson(Map<String, dynamic> json) => _$ScriptFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final int id;
|
||||
@override final String label;
|
||||
@override final String content;
|
||||
@override final DateTime lastUpdateTime;
|
||||
|
||||
/// Create a copy of Script
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -5724,16 +5452,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Script&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.content, content) || other.content == content));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Script&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.lastUpdateTime, lastUpdateTime) || other.lastUpdateTime == lastUpdateTime));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,label,content);
|
||||
int get hashCode => Object.hash(runtimeType,id,label,lastUpdateTime);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Script(id: $id, label: $label, content: $content)';
|
||||
return 'Script(id: $id, label: $label, lastUpdateTime: $lastUpdateTime)';
|
||||
}
|
||||
|
||||
|
||||
@@ -5744,7 +5472,7 @@ abstract mixin class _$ScriptCopyWith<$Res> implements $ScriptCopyWith<$Res> {
|
||||
factory _$ScriptCopyWith(_Script value, $Res Function(_Script) _then) = __$ScriptCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String label, String content
|
||||
int id, String label, DateTime lastUpdateTime
|
||||
});
|
||||
|
||||
|
||||
@@ -5761,12 +5489,12 @@ class __$ScriptCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of Script
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? label = null,Object? content = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? label = null,Object? lastUpdateTime = null,}) {
|
||||
return _then(_Script(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||
as String,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
as int,label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||
as String,lastUpdateTime: null == lastUpdateTime ? _self.lastUpdateTime : lastUpdateTime // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -6031,6 +5759,266 @@ as bool,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UpdatingMessage {
|
||||
|
||||
String get label; String get message;
|
||||
/// Create a copy of UpdatingMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$UpdatingMessageCopyWith<UpdatingMessage> get copyWith => _$UpdatingMessageCopyWithImpl<UpdatingMessage>(this as UpdatingMessage, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is UpdatingMessage&&(identical(other.label, label) || other.label == label)&&(identical(other.message, message) || other.message == message));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,label,message);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UpdatingMessage(label: $label, message: $message)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $UpdatingMessageCopyWith<$Res> {
|
||||
factory $UpdatingMessageCopyWith(UpdatingMessage value, $Res Function(UpdatingMessage) _then) = _$UpdatingMessageCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String label, String message
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$UpdatingMessageCopyWithImpl<$Res>
|
||||
implements $UpdatingMessageCopyWith<$Res> {
|
||||
_$UpdatingMessageCopyWithImpl(this._self, this._then);
|
||||
|
||||
final UpdatingMessage _self;
|
||||
final $Res Function(UpdatingMessage) _then;
|
||||
|
||||
/// Create a copy of UpdatingMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? label = null,Object? message = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||
as String,message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [UpdatingMessage].
|
||||
extension UpdatingMessagePatterns on UpdatingMessage {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _UpdatingMessage value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdatingMessage() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _UpdatingMessage value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdatingMessage():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _UpdatingMessage value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdatingMessage() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String label, String message)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdatingMessage() when $default != null:
|
||||
return $default(_that.label,_that.message);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String label, String message) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdatingMessage():
|
||||
return $default(_that.label,_that.message);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String label, String message)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdatingMessage() when $default != null:
|
||||
return $default(_that.label,_that.message);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _UpdatingMessage implements UpdatingMessage {
|
||||
const _UpdatingMessage({required this.label, required this.message});
|
||||
|
||||
|
||||
@override final String label;
|
||||
@override final String message;
|
||||
|
||||
/// Create a copy of UpdatingMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$UpdatingMessageCopyWith<_UpdatingMessage> get copyWith => __$UpdatingMessageCopyWithImpl<_UpdatingMessage>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UpdatingMessage&&(identical(other.label, label) || other.label == label)&&(identical(other.message, message) || other.message == message));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,label,message);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UpdatingMessage(label: $label, message: $message)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$UpdatingMessageCopyWith<$Res> implements $UpdatingMessageCopyWith<$Res> {
|
||||
factory _$UpdatingMessageCopyWith(_UpdatingMessage value, $Res Function(_UpdatingMessage) _then) = __$UpdatingMessageCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String label, String message
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$UpdatingMessageCopyWithImpl<$Res>
|
||||
implements _$UpdatingMessageCopyWith<$Res> {
|
||||
__$UpdatingMessageCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _UpdatingMessage _self;
|
||||
final $Res Function(_UpdatingMessage) _then;
|
||||
|
||||
/// Create a copy of UpdatingMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? label = null,Object? message = null,}) {
|
||||
return _then(_UpdatingMessage(
|
||||
label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||
as String,message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user