Compare commits
1 Commits
v0.8.88-pr
...
v0.8.88-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f06abecb3e |
@@ -54,7 +54,7 @@ Support the following actions
|
||||
|
||||
com.follow.clash.action.STOP
|
||||
|
||||
com.follow.clash.action.CHANGE
|
||||
com.follow.clash.action.TOGGLE
|
||||
```
|
||||
|
||||
## Download
|
||||
|
||||
@@ -54,7 +54,7 @@ on Mobile:
|
||||
|
||||
com.follow.clash.action.STOP
|
||||
|
||||
com.follow.clash.action.CHANGE
|
||||
com.follow.clash.action.TOGGLE
|
||||
```
|
||||
|
||||
## Download
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
analyzer:
|
||||
plugins:
|
||||
- custom_lint
|
||||
exclude:
|
||||
- lib/l10n/intl/**
|
||||
errors:
|
||||
invalid_annotation_target: ignore
|
||||
|
||||
linter:
|
||||
rules:
|
||||
|
||||
@@ -29,6 +29,7 @@ android {
|
||||
ndkVersion = libs.versions.ndkVersion.get()
|
||||
|
||||
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
@@ -53,6 +54,12 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
isMinifyEnabled = false
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<permission
|
||||
android:name="${applicationId}.permission.RECEIVE_BROADCASTS"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
@@ -24,28 +20,23 @@
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<application
|
||||
android:name=".Application"
|
||||
android:banner="@mipmap/ic_banner"
|
||||
android:extractNativeLibs="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="FlClash">
|
||||
<activity
|
||||
android:name="com.follow.clash.MainActivity"
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:exported="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme" />
|
||||
@@ -118,29 +109,6 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
||||
<provider
|
||||
android:name=".FilesProvider"
|
||||
android:authorities="${applicationId}.files"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS"
|
||||
android:process=":background">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileProvider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
@@ -1,28 +1,76 @@
|
||||
package com.follow.clash
|
||||
|
||||
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.util.Base64
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.follow.clash.common.GlobalState
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
suspend fun Drawable.getBase64(): String {
|
||||
val drawable = this
|
||||
return withContext(Dispatchers.IO) {
|
||||
val bitmap = drawable.toBitmap()
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
|
||||
Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.NO_WRAP)
|
||||
private const val ICON_TTL_DAYS = 1L
|
||||
|
||||
suspend fun PackageManager.getPackageIconPath(packageName: String): String =
|
||||
withContext(Dispatchers.IO) {
|
||||
val cacheDir = GlobalState.application.cacheDir
|
||||
val iconDir = File(cacheDir, "icons").apply { mkdirs() }
|
||||
return@withContext try {
|
||||
val pkgInfo = getPackageInfo(packageName, 0)
|
||||
val lastUpdateTime = pkgInfo.lastUpdateTime
|
||||
val iconFile = File(iconDir, "${packageName}_${lastUpdateTime}.webp")
|
||||
if (iconFile.exists() && !isExpired(iconFile)) {
|
||||
return@withContext iconFile.absolutePath
|
||||
}
|
||||
iconDir.listFiles()?.forEach { file ->
|
||||
if (file.name.startsWith(packageName + "_")) file.delete()
|
||||
}
|
||||
val icon = getApplicationIcon(packageName)
|
||||
saveDrawableToFile(icon, iconFile)
|
||||
iconFile.absolutePath
|
||||
} catch (_: Exception) {
|
||||
val defaultIconFile = File(iconDir, "default_icon.webp")
|
||||
if (!defaultIconFile.exists()) {
|
||||
saveDrawableToFile(defaultActivityIcon, defaultIconFile)
|
||||
}
|
||||
defaultIconFile.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveDrawableToFile(drawable: Drawable, file: File) {
|
||||
val bitmap = drawable.toBitmap()
|
||||
try {
|
||||
val format = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||
Bitmap.CompressFormat.WEBP_LOSSY
|
||||
}
|
||||
|
||||
else -> {
|
||||
Bitmap.CompressFormat.WEBP
|
||||
}
|
||||
}
|
||||
FileOutputStream(file).use { fos ->
|
||||
bitmap.compress(format, 90, fos)
|
||||
}
|
||||
} finally {
|
||||
if (!bitmap.isRecycled) bitmap.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isExpired(file: File): Boolean {
|
||||
val now = System.currentTimeMillis()
|
||||
val age = now - file.lastModified()
|
||||
return age > TimeUnit.DAYS.toMillis(ICON_TTL_DAYS)
|
||||
}
|
||||
|
||||
suspend fun <T> MethodChannel.awaitResult(
|
||||
@@ -51,15 +99,12 @@ inline fun <reified T : FlutterPlugin> FlutterEngine.plugin(): T? {
|
||||
|
||||
|
||||
fun <T> MethodChannel.invokeMethodOnMainThread(
|
||||
method: String,
|
||||
arguments: Any? = null,
|
||||
callback: ((Result<T>) -> Unit)? = null
|
||||
method: String, arguments: Any? = null, callback: ((Result<T>) -> Unit)? = null
|
||||
) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
invokeMethod(method, arguments, object : MethodChannel.Result {
|
||||
override fun success(result: Any?) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
callback?.invoke(Result.success(result as T))
|
||||
@Suppress("UNCHECKED_CAST") callback?.invoke(Result.success(result as T))
|
||||
}
|
||||
|
||||
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
|
||||
|
||||
@@ -3,45 +3,40 @@ package com.follow.clash
|
||||
import com.follow.clash.common.ServiceDelegate
|
||||
import com.follow.clash.common.intent
|
||||
import com.follow.clash.service.ICallbackInterface
|
||||
import com.follow.clash.service.IMessageInterface
|
||||
import com.follow.clash.service.IRemoteInterface
|
||||
import com.follow.clash.service.RemoteService
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
import com.follow.clash.service.models.VpnOptions
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
object Service {
|
||||
private val delegate by lazy {
|
||||
ServiceDelegate<IRemoteInterface>(
|
||||
RemoteService::class.intent, ::handleOnServiceCrash
|
||||
RemoteService::class.intent, ::handleServiceDisconnected
|
||||
) {
|
||||
IRemoteInterface.Stub.asInterface(it)
|
||||
}
|
||||
}
|
||||
|
||||
var onServiceCrash: (() -> Unit)? = null
|
||||
var onServiceDisconnected: ((String) -> Unit)? = null
|
||||
|
||||
private fun handleOnServiceCrash() {
|
||||
bindingState.set(false)
|
||||
onServiceCrash?.let {
|
||||
it()
|
||||
private fun handleServiceDisconnected(message: String) {
|
||||
onServiceDisconnected?.let {
|
||||
it(message)
|
||||
}
|
||||
}
|
||||
|
||||
private val bindingState = AtomicBoolean(false)
|
||||
|
||||
fun bind() {
|
||||
if (bindingState.compareAndSet(false, true)) {
|
||||
delegate.bind()
|
||||
}
|
||||
delegate.bind()
|
||||
}
|
||||
|
||||
suspend fun invokeAction(
|
||||
data: String, cb: (result: String?) -> Unit
|
||||
) {
|
||||
delegate.useService {
|
||||
data: String, cb: (result: ByteArray?, isSuccess: Boolean) -> Unit
|
||||
): Result<Unit> {
|
||||
return delegate.useService {
|
||||
it.invokeAction(data, object : ICallbackInterface.Stub() {
|
||||
override fun onResult(result: String?) {
|
||||
cb(result)
|
||||
override fun onResult(result: ByteArray?, isSuccess: Boolean) {
|
||||
cb(result, isSuccess)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -49,17 +44,17 @@ object Service {
|
||||
|
||||
suspend fun updateNotificationParams(
|
||||
params: NotificationParams
|
||||
) {
|
||||
delegate.useService {
|
||||
): Result<Unit> {
|
||||
return delegate.useService {
|
||||
it.updateNotificationParams(params)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setMessageCallback(
|
||||
cb: (result: String?) -> Unit
|
||||
) {
|
||||
delegate.useService {
|
||||
it.setMessageCallback(object : ICallbackInterface.Stub() {
|
||||
): Result<Unit> {
|
||||
return delegate.useService {
|
||||
it.setMessageCallback(object : IMessageInterface.Stub() {
|
||||
override fun onResult(result: String?) {
|
||||
cb(result)
|
||||
}
|
||||
|
||||
@@ -119,9 +119,9 @@ object State {
|
||||
return@launch
|
||||
}
|
||||
appPlugin?.prepare(options.enable) {
|
||||
runTime = System.currentTimeMillis()
|
||||
Service.startService(options, true)
|
||||
runStateFlow.tryEmit(RunState.START)
|
||||
runTime = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.follow.clash.common.Components
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.common.QuickAction
|
||||
import com.follow.clash.common.quickIntent
|
||||
import com.follow.clash.getBase64
|
||||
import com.follow.clash.getPackageIconPath
|
||||
import com.follow.clash.models.Package
|
||||
import com.google.gson.Gson
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
@@ -150,26 +150,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
}
|
||||
|
||||
"getPackageIcon" -> {
|
||||
scope.launch {
|
||||
val packageName = call.argument<String>("packageName")
|
||||
if (packageName == null) {
|
||||
result.success(null)
|
||||
return@launch
|
||||
}
|
||||
val packageIcon = getPackageIcon(packageName)
|
||||
packageIcon.let {
|
||||
if (it != null) {
|
||||
result.success(it)
|
||||
return@launch
|
||||
}
|
||||
if (iconMap["default"] == null) {
|
||||
iconMap["default"] =
|
||||
GlobalState.application.packageManager?.defaultActivityIcon?.getBase64()
|
||||
}
|
||||
result.success(iconMap["default"])
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
handleGetPackageIcon(call, result)
|
||||
}
|
||||
|
||||
"tip" -> {
|
||||
@@ -184,6 +165,19 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleGetPackageIcon(call: MethodCall, result: Result) {
|
||||
scope.launch {
|
||||
val packageName = call.argument<String>("packageName")
|
||||
if (packageName == null) {
|
||||
result.success("")
|
||||
return@launch
|
||||
}
|
||||
val path =
|
||||
GlobalState.application.packageManager.getPackageIconPath(packageName)
|
||||
result.success(path)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initShortcuts(label: String) {
|
||||
val shortcut = with(ShortcutInfoCompat.Builder(GlobalState.application, "toggle")) {
|
||||
setShortLabel(label)
|
||||
@@ -223,18 +217,6 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getPackageIcon(packageName: String): String? {
|
||||
val packageManager = GlobalState.application.packageManager
|
||||
if (iconMap[packageName] == null) {
|
||||
iconMap[packageName] = try {
|
||||
packageManager?.getApplicationIcon(packageName)?.getBase64()
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
}
|
||||
return iconMap[packageName]
|
||||
}
|
||||
|
||||
private fun getPackages(): List<Package> {
|
||||
val packageManager = GlobalState.application.packageManager
|
||||
|
||||
@@ -5,6 +5,7 @@ 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.formatString
|
||||
import com.follow.clash.invokeMethodOnMainThread
|
||||
import com.follow.clash.models.AppState
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
@@ -68,8 +69,12 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
private fun handleInvokeAction(call: MethodCall, result: MethodChannel.Result) {
|
||||
launch {
|
||||
val data = call.arguments<String>()!!
|
||||
Service.invokeAction(data) {
|
||||
result.success(it)
|
||||
val res = mutableListOf<ByteArray>()
|
||||
Service.invokeAction(data) { byteArray, isSuccess ->
|
||||
res.add(byteArray ?: byteArrayOf())
|
||||
if (isSuccess) {
|
||||
result.success(res.formatString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,9 +104,9 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
}
|
||||
}
|
||||
|
||||
private fun onServiceCrash() {
|
||||
private fun onServiceDisconnected(message: String) {
|
||||
State.runStateFlow.tryEmit(RunState.STOP)
|
||||
flutterMethodChannel.invokeMethodOnMainThread<Any>("crash", null)
|
||||
flutterMethodChannel.invokeMethodOnMainThread<Any>("crash", message)
|
||||
}
|
||||
|
||||
private fun handleSyncState(call: MethodCall, result: MethodChannel.Result) {
|
||||
@@ -114,10 +119,12 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
stopText = params.stopText,
|
||||
onlyStatisticsProxy = params.onlyStatisticsProxy
|
||||
)
|
||||
)
|
||||
result.success(true)
|
||||
).onSuccess {
|
||||
result.success("")
|
||||
}.onFailure {
|
||||
result.success(it.message)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun handleInit(result: MethodChannel.Result) {
|
||||
@@ -125,10 +132,14 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
launch {
|
||||
Service.setMessageCallback {
|
||||
handleSendEvent(it)
|
||||
}.onSuccess {
|
||||
result.success("")
|
||||
}.onFailure {
|
||||
result.success(it.message)
|
||||
}
|
||||
result.success(true)
|
||||
|
||||
}
|
||||
Service.onServiceCrash = ::onServiceCrash
|
||||
Service.onServiceDisconnected = ::onServiceDisconnected
|
||||
}
|
||||
|
||||
private fun handleGetRunTime(result: MethodChannel.Result) {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<permission
|
||||
android:name="${applicationId}.permission.RECEIVE_BROADCASTS"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<uses-permission android:name="${applicationId}.permission.RECEIVE_BROADCASTS" />
|
||||
</manifest>
|
||||
@@ -13,14 +13,21 @@ import android.content.Context.RECEIVER_NOT_EXPORTED
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.retryWhen
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.charset.Charset
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
//fun Context.startForegroundServiceCompat(intent: Intent?) {
|
||||
@@ -36,19 +43,23 @@ val KClass<*>.intent: Intent
|
||||
|
||||
fun Service.startForegroundCompat(id: Int, notification: Notification) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(id, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
||||
startForeground(id, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||
} else {
|
||||
startForeground(id, notification)
|
||||
}
|
||||
}
|
||||
|
||||
val ComponentName.intent: Intent
|
||||
get() = Intent().apply {
|
||||
setComponent(this@intent)
|
||||
setPackage(GlobalState.packageName)
|
||||
}
|
||||
|
||||
val QuickAction.action: String
|
||||
get() = "${GlobalState.application.packageName}.action.${this.name}"
|
||||
|
||||
val QuickAction.quickIntent: Intent
|
||||
get() = Intent().apply {
|
||||
setComponent(Components.TEMP_ACTIVITY)
|
||||
setPackage(GlobalState.packageName)
|
||||
get() = Components.TEMP_ACTIVITY.intent.apply {
|
||||
action = this@quickIntent.action
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
||||
}
|
||||
@@ -57,9 +68,7 @@ val BroadcastAction.action: String
|
||||
get() = "${GlobalState.application.packageName}.intent.action.${this.name}"
|
||||
|
||||
val BroadcastAction.quickIntent: Intent
|
||||
get() = Intent().apply {
|
||||
setComponent(Components.BROADCAST_RECEIVER)
|
||||
setPackage(GlobalState.packageName)
|
||||
get() = Components.BROADCAST_RECEIVER.intent.apply {
|
||||
action = this@quickIntent.action
|
||||
}
|
||||
|
||||
@@ -125,62 +134,54 @@ fun Context.receiveBroadcastFlow(
|
||||
}
|
||||
|
||||
|
||||
sealed class BindServiceEvent<out T : IBinder> {
|
||||
data class Connected<T : IBinder>(val binder: T) : BindServiceEvent<T>()
|
||||
object Disconnected : BindServiceEvent<Nothing>()
|
||||
object Crashed : BindServiceEvent<Nothing>()
|
||||
}
|
||||
|
||||
inline fun <reified T : IBinder> Context.bindServiceFlow(
|
||||
intent: Intent,
|
||||
flags: Int = Context.BIND_AUTO_CREATE,
|
||||
): Flow<BindServiceEvent<T>> = callbackFlow {
|
||||
var currentBinder: IBinder? = null
|
||||
val deathRecipient = IBinder.DeathRecipient {
|
||||
trySend(BindServiceEvent.Crashed)
|
||||
}
|
||||
|
||||
maxRetries: Int = 5,
|
||||
retryDelayMillis: Long = 200L
|
||||
): Flow<Pair<IBinder?, String>> = callbackFlow {
|
||||
val connection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
|
||||
if (binder != null) {
|
||||
try {
|
||||
binder.linkToDeath(deathRecipient, 0)
|
||||
currentBinder = binder
|
||||
@Suppress("UNCHECKED_CAST") val casted = binder as? T
|
||||
if (casted != null) {
|
||||
trySend(BindServiceEvent.Connected(casted))
|
||||
trySend(Pair(casted, ""))
|
||||
} else {
|
||||
GlobalState.log("Binder is not of type ${T::class.java}")
|
||||
trySend(BindServiceEvent.Disconnected)
|
||||
trySend(Pair(null, "Binder is not of type ${T::class.java}"))
|
||||
}
|
||||
} catch (e: RemoteException) {
|
||||
GlobalState.log("Failed to link to death: ${e.message}")
|
||||
binder.unlinkToDeath(deathRecipient, 0)
|
||||
trySend(BindServiceEvent.Disconnected)
|
||||
trySend(Pair(null, "Failed to link to death: ${e.message}"))
|
||||
}
|
||||
} else {
|
||||
trySend(BindServiceEvent.Disconnected)
|
||||
trySend(Pair(null, "Binder empty"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
GlobalState.log("Service disconnected")
|
||||
currentBinder?.unlinkToDeath(deathRecipient, 0)
|
||||
currentBinder = null
|
||||
trySend(BindServiceEvent.Disconnected)
|
||||
trySend(Pair(null, "Service disconnected"))
|
||||
}
|
||||
}
|
||||
|
||||
if (!bindService(intent, connection, flags)) {
|
||||
GlobalState.log("Failed to bind service")
|
||||
trySend(BindServiceEvent.Disconnected)
|
||||
close()
|
||||
return@callbackFlow
|
||||
val success = withContext(Dispatchers.Main) {
|
||||
bindService(intent, connection, flags)
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw IllegalStateException("bindService() failed, will retry")
|
||||
}
|
||||
|
||||
awaitClose {
|
||||
currentBinder?.unlinkToDeath(deathRecipient, 0)
|
||||
unbindService(connection)
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
unbindService(connection)
|
||||
}
|
||||
}
|
||||
}.retryWhen { cause, attempt ->
|
||||
if (attempt < maxRetries && cause is Exception) {
|
||||
delay(retryDelayMillis)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,4 +202,37 @@ val Long.formatBytes: String
|
||||
} else {
|
||||
"%.1f${units[unitIndex]}".format(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun String.chunkedForAidl(charset: Charset = Charsets.UTF_8): List<ByteArray> {
|
||||
val allBytes = toByteArray(charset)
|
||||
val total = allBytes.size
|
||||
|
||||
val maxBytes = when {
|
||||
total <= 100 * 1024 -> total
|
||||
total <= 1024 * 1024 -> 64 * 1024
|
||||
total <= 10 * 1024 * 1024 -> 128 * 1024
|
||||
else -> 256 * 1024
|
||||
}
|
||||
|
||||
val result = mutableListOf<ByteArray>()
|
||||
var index = 0
|
||||
while (index < total) {
|
||||
val end = minOf(index + maxBytes, total)
|
||||
result.add(allBytes.copyOfRange(index, end))
|
||||
index = end
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
fun <T : List<ByteArray>> T.formatString(charset: Charset = Charsets.UTF_8): String {
|
||||
val totalSize = this.sumOf { it.size }
|
||||
val combined = ByteArray(totalSize)
|
||||
var offset = 0
|
||||
forEach { byteArray ->
|
||||
byteArray.copyInto(combined, offset)
|
||||
offset += byteArray.size
|
||||
}
|
||||
return String(combined, charset)
|
||||
}
|
||||
@@ -6,74 +6,67 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.retryWhen
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class ServiceDelegate<T>(
|
||||
private val intent: Intent,
|
||||
private val onServiceDisconnected: (() -> Unit)? = null,
|
||||
private val onServiceCrash: (() -> Unit)? = null,
|
||||
private val onServiceDisconnected: ((String) -> Unit)? = null,
|
||||
private val interfaceCreator: (IBinder) -> T,
|
||||
) : CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
|
||||
|
||||
private val _service = MutableStateFlow<T?>(null)
|
||||
private val _bindingState = AtomicBoolean(false)
|
||||
|
||||
val service: StateFlow<T?> = _service
|
||||
private var _serviceState = MutableStateFlow<Pair<T?, String>?>(null)
|
||||
|
||||
private var bindJob: Job? = null
|
||||
private fun handleBindEvent(event: BindServiceEvent<IBinder>) {
|
||||
when (event) {
|
||||
is BindServiceEvent.Connected -> {
|
||||
_service.value = event.binder.let(interfaceCreator)
|
||||
}
|
||||
val serviceState: StateFlow<Pair<T?, String>?> = _serviceState
|
||||
private var job: Job? = null
|
||||
|
||||
is BindServiceEvent.Disconnected -> {
|
||||
_service.value = null
|
||||
onServiceDisconnected?.invoke()
|
||||
}
|
||||
|
||||
is BindServiceEvent.Crashed -> {
|
||||
_service.value = null
|
||||
onServiceCrash?.invoke()
|
||||
}
|
||||
private fun handleBind(data: Pair<IBinder?, String>) {
|
||||
data.first?.let {
|
||||
_serviceState.value = Pair(interfaceCreator(it), data.second)
|
||||
} ?: run {
|
||||
_serviceState.value = Pair(null, data.second)
|
||||
unbind()
|
||||
onServiceDisconnected?.invoke(data.second)
|
||||
_bindingState.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun bind() {
|
||||
unbind()
|
||||
bindJob = launch {
|
||||
GlobalState.application.bindServiceFlow<IBinder>(intent).collect { it ->
|
||||
handleBindEvent(it)
|
||||
if (_bindingState.compareAndSet(false, true)) {
|
||||
job?.cancel()
|
||||
job = null
|
||||
_serviceState.value = null
|
||||
job = launch {
|
||||
GlobalState.application.bindServiceFlow<IBinder>(intent).collect { handleBind(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <R> useService(
|
||||
crossinline block: (T) -> R
|
||||
timeoutMillis: Long = 5000, crossinline block: (T) -> R
|
||||
): Result<R> {
|
||||
return withTimeoutOrNull(10_000) {
|
||||
service.filterNotNull().retryWhen { _, _ ->
|
||||
delay(200)
|
||||
true
|
||||
}.first()
|
||||
}?.let { s ->
|
||||
try {
|
||||
Result.success(block(s))
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
return runCatching {
|
||||
withTimeout(timeoutMillis) {
|
||||
val state = serviceState.filterNotNull().first()
|
||||
state.first?.let {
|
||||
block(it)
|
||||
} ?: throw Exception(state.second)
|
||||
}
|
||||
} ?: Result.failure(Exception("Service connection timeout"))
|
||||
}
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
_service.value = null
|
||||
bindJob?.cancel()
|
||||
bindJob = null
|
||||
if (_bindingState.compareAndSet(true, false)) {
|
||||
job?.cancel()
|
||||
job = null
|
||||
_serviceState.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,16 +9,14 @@
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb,
|
||||
jstring address, jstring dns) {
|
||||
jstring stack, jstring address, jstring dns) {
|
||||
const auto interface = new_global(cb);
|
||||
scoped_string addressChar = get_string(address);
|
||||
scoped_string dnsChar = get_string(dns);
|
||||
startTUN(interface, fd, addressChar, dnsChar);
|
||||
startTUN(interface, fd, get_string(stack), get_string(address), get_string(dns));
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_stopTun(JNIEnv *) {
|
||||
Java_com_follow_clash_core_Core_stopTun(JNIEnv *env, jobject thiz) {
|
||||
stopTun();
|
||||
}
|
||||
|
||||
@@ -31,16 +29,14 @@ Java_com_follow_clash_core_Core_forceGC(JNIEnv *env, jobject thiz) {
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_updateDNS(JNIEnv *env, jobject thiz, jstring dns) {
|
||||
scoped_string dnsChar = get_string(dns);
|
||||
updateDns(dnsChar);
|
||||
updateDns(get_string(dns));
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_invokeAction(JNIEnv *env, jobject thiz, jstring data, jobject cb) {
|
||||
const auto interface = new_global(cb);
|
||||
scoped_string dataChar = get_string(data);
|
||||
invokeAction(interface, dataChar);
|
||||
invokeAction(interface, get_string(data));
|
||||
}
|
||||
|
||||
extern "C"
|
||||
@@ -54,16 +50,14 @@ extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_follow_clash_core_Core_getTraffic(JNIEnv *env, jobject thiz,
|
||||
const jboolean only_statistics_proxy) {
|
||||
scoped_string res = getTraffic(only_statistics_proxy);
|
||||
return new_string(res);
|
||||
return new_string(getTraffic(only_statistics_proxy));
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_follow_clash_core_Core_getTotalTraffic(JNIEnv *env, jobject thiz,
|
||||
const jboolean only_statistics_proxy) {
|
||||
scoped_string res = getTotalTraffic(only_statistics_proxy);
|
||||
return new_string(res);
|
||||
return new_string(getTotalTraffic(only_statistics_proxy));
|
||||
}
|
||||
|
||||
extern "C"
|
||||
@@ -83,6 +77,10 @@ static void release_jni_object_impl(void *obj) {
|
||||
del_global(static_cast<jobject>(obj));
|
||||
}
|
||||
|
||||
static void free_string_impl(char *str) {
|
||||
free(str);
|
||||
}
|
||||
|
||||
static void call_tun_interface_protect_impl(void *tun_interface, const int fd) {
|
||||
ATTACH_JNI();
|
||||
env->CallVoidMethod(static_cast<jobject>(tun_interface),
|
||||
@@ -103,8 +101,7 @@ call_tun_interface_resolve_process_impl(void *tun_interface, const int protocol,
|
||||
new_string(source),
|
||||
new_string(target),
|
||||
uid));
|
||||
scoped_string packageNameChar = get_string(packageName);
|
||||
return packageNameChar;
|
||||
return get_string(packageName);
|
||||
}
|
||||
|
||||
static void call_invoke_interface_result_impl(void *invoke_interface, const char *data) {
|
||||
@@ -139,6 +136,7 @@ JNI_OnLoad(JavaVM *vm, void *) {
|
||||
resolve_process_func = &call_tun_interface_resolve_process_impl;
|
||||
result_func = &call_invoke_interface_result_impl;
|
||||
release_object_func = &release_jni_object_impl;
|
||||
free_string_func = &free_string_impl;
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
@@ -146,7 +144,7 @@ JNI_OnLoad(JavaVM *vm, void *) {
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb,
|
||||
jstring address, jstring dns) {
|
||||
jstring stack, jstring address, jstring dns) {
|
||||
}
|
||||
|
||||
extern "C"
|
||||
|
||||
@@ -8,6 +8,7 @@ data object Core {
|
||||
private external fun startTun(
|
||||
fd: Int,
|
||||
cb: TunInterface,
|
||||
stack: String,
|
||||
address: String,
|
||||
dns: String,
|
||||
)
|
||||
@@ -29,6 +30,7 @@ data object Core {
|
||||
fd: Int,
|
||||
protect: (Int) -> Boolean,
|
||||
resolverProcess: (protocol: Int, source: InetSocketAddress, target: InetSocketAddress, uid: Int) -> String,
|
||||
stack: String,
|
||||
address: String,
|
||||
dns: String,
|
||||
) {
|
||||
@@ -53,6 +55,7 @@ data object Core {
|
||||
)
|
||||
}
|
||||
},
|
||||
stack,
|
||||
address,
|
||||
dns
|
||||
)
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name="com.follow.clash.service.VpnService"
|
||||
android:name=".VpnService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:process=":background">
|
||||
android:process=":remote">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
@@ -19,18 +19,31 @@
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="com.follow.clash.service.CommonService"
|
||||
android:name=".CommonService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:process=":background">
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:process=":remote">
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="service" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="com.follow.clash.service.RemoteService"
|
||||
android:name=".RemoteService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:process=":background" />
|
||||
android:process=":remote" />
|
||||
|
||||
<provider
|
||||
android:name=".FilesProvider"
|
||||
android:authorities="${applicationId}.files"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS"
|
||||
android:process=":remote">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -2,5 +2,5 @@
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface ICallbackInterface {
|
||||
void onResult(String result);
|
||||
void onResult(in byte[] result, boolean isSuccess);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// IMessageInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface IMessageInterface {
|
||||
void onResult(String result);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.follow.clash.service;
|
||||
|
||||
import com.follow.clash.service.ICallbackInterface;
|
||||
import com.follow.clash.service.IMessageInterface;
|
||||
import com.follow.clash.service.models.VpnOptions;
|
||||
import com.follow.clash.service.models.NotificationParams;
|
||||
|
||||
@@ -10,5 +11,5 @@ interface IRemoteInterface {
|
||||
void updateNotificationParams(in NotificationParams params);
|
||||
void startService(in VpnOptions options,in boolean inApp);
|
||||
void stopService();
|
||||
void setMessageCallback(in ICallbackInterface messageCallback);
|
||||
void setMessageCallback(in IMessageInterface messageCallback);
|
||||
}
|
||||
@@ -1,35 +1,33 @@
|
||||
package com.follow.clash
|
||||
package com.follow.clash.service
|
||||
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.os.CancellationSignal
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.DocumentsContract.Document
|
||||
import android.provider.DocumentsContract.Root
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.DocumentsProvider
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
|
||||
class FilesProvider : DocumentsProvider() {
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_ROOT_ID = "0"
|
||||
|
||||
private val DEFAULT_DOCUMENT_COLUMNS = arrayOf(
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_SIZE,
|
||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
||||
DocumentsContract.Document.COLUMN_FLAGS,
|
||||
DocumentsContract.Document.COLUMN_SIZE,
|
||||
)
|
||||
private val DEFAULT_ROOT_COLUMNS = arrayOf(
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.COLUMN_ICON,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_SUMMARY,
|
||||
Root.COLUMN_DOCUMENT_ID
|
||||
DocumentsContract.Root.COLUMN_ROOT_ID,
|
||||
DocumentsContract.Root.COLUMN_FLAGS,
|
||||
DocumentsContract.Root.COLUMN_ICON,
|
||||
DocumentsContract.Root.COLUMN_TITLE,
|
||||
DocumentsContract.Root.COLUMN_SUMMARY,
|
||||
DocumentsContract.Root.COLUMN_DOCUMENT_ID
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,12 +38,12 @@ class FilesProvider : DocumentsProvider() {
|
||||
override fun queryRoots(projection: Array<String>?): Cursor {
|
||||
return MatrixCursor(projection ?: DEFAULT_ROOT_COLUMNS).apply {
|
||||
newRow().apply {
|
||||
add(Root.COLUMN_ROOT_ID, DEFAULT_ROOT_ID)
|
||||
add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY)
|
||||
add(Root.COLUMN_ICON, R.mipmap.ic_launcher)
|
||||
add(Root.COLUMN_TITLE, "FlClash")
|
||||
add(Root.COLUMN_SUMMARY, "Data")
|
||||
add(Root.COLUMN_DOCUMENT_ID, "/")
|
||||
add(DocumentsContract.Root.COLUMN_ROOT_ID, DEFAULT_ROOT_ID)
|
||||
add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_LOCAL_ONLY)
|
||||
add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_service)
|
||||
add(DocumentsContract.Root.COLUMN_TITLE, "FlClash")
|
||||
add(DocumentsContract.Root.COLUMN_SUMMARY, "Data")
|
||||
add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, "/")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,20 +85,20 @@ class FilesProvider : DocumentsProvider() {
|
||||
|
||||
private fun includeFile(result: MatrixCursor, file: File) {
|
||||
result.newRow().apply {
|
||||
add(Document.COLUMN_DOCUMENT_ID, file.absolutePath)
|
||||
add(Document.COLUMN_DISPLAY_NAME, file.name)
|
||||
add(Document.COLUMN_SIZE, file.length())
|
||||
add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, file.absolutePath)
|
||||
add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, file.name)
|
||||
add(DocumentsContract.Document.COLUMN_SIZE, file.length())
|
||||
add(
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.FLAG_SUPPORTS_WRITE or Document.FLAG_SUPPORTS_DELETE
|
||||
DocumentsContract.Document.COLUMN_FLAGS,
|
||||
DocumentsContract.Document.FLAG_SUPPORTS_WRITE or DocumentsContract.Document.FLAG_SUPPORTS_DELETE
|
||||
)
|
||||
add(Document.COLUMN_MIME_TYPE, getDocumentType(file))
|
||||
add(DocumentsContract.Document.COLUMN_MIME_TYPE, getDocumentType(file))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDocumentType(file: File): String {
|
||||
return if (file.isDirectory) {
|
||||
Document.MIME_TYPE_DIR
|
||||
DocumentsContract.Document.MIME_TYPE_DIR
|
||||
} else {
|
||||
"application/octet-stream"
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import com.follow.clash.common.ServiceDelegate
|
||||
import com.follow.clash.common.chunkedForAidl
|
||||
import com.follow.clash.common.intent
|
||||
import com.follow.clash.core.Core
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
@@ -22,13 +23,14 @@ class RemoteService : Service(),
|
||||
launch {
|
||||
delegate?.useService { service ->
|
||||
service.stop()
|
||||
delegate?.unbind()
|
||||
}
|
||||
delegate?.unbind()
|
||||
}
|
||||
}
|
||||
|
||||
fun onServiceDisconnected() {
|
||||
handleStopService()
|
||||
private fun handleServiceDisconnected(message: String) {
|
||||
intent = null
|
||||
delegate = null
|
||||
}
|
||||
|
||||
private fun handleStartService() {
|
||||
@@ -39,7 +41,7 @@ class RemoteService : Service(),
|
||||
}
|
||||
if (intent != nextIntent) {
|
||||
delegate?.unbind()
|
||||
delegate = ServiceDelegate(nextIntent, ::onServiceDisconnected) { binder ->
|
||||
delegate = ServiceDelegate(nextIntent, ::handleServiceDisconnected) { binder ->
|
||||
when (binder) {
|
||||
is VpnService.LocalBinder -> binder.getService()
|
||||
is CommonService.LocalBinder -> binder.getService()
|
||||
@@ -55,9 +57,15 @@ class RemoteService : Service(),
|
||||
}
|
||||
}
|
||||
|
||||
private val binder: IRemoteInterface.Stub = object : IRemoteInterface.Stub() {
|
||||
private val binder = object : IRemoteInterface.Stub() {
|
||||
override fun invokeAction(data: String, callback: ICallbackInterface) {
|
||||
Core.invokeAction(data, callback::onResult)
|
||||
Core.invokeAction(data) {
|
||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||
val totalSize = chunks.size
|
||||
chunks.forEachIndexed { index, chunk ->
|
||||
callback.onResult(chunk, totalSize - 1 == index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateNotificationParams(params: NotificationParams?) {
|
||||
@@ -76,7 +84,7 @@ class RemoteService : Service(),
|
||||
handleStopService()
|
||||
}
|
||||
|
||||
override fun setMessageCallback(messageCallback: ICallbackInterface) {
|
||||
override fun setMessageCallback(messageCallback: IMessageInterface) {
|
||||
setMessageCallback(messageCallback::onResult)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import android.util.Log
|
||||
import androidx.core.content.getSystemService
|
||||
import com.follow.clash.common.AccessControlMode
|
||||
import com.follow.clash.common.BroadcastAction
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.common.sendBroadcast
|
||||
import com.follow.clash.core.Core
|
||||
import com.follow.clash.service.models.VpnOptions
|
||||
@@ -108,7 +107,6 @@ class VpnService : SystemVpnService(), IBaseService,
|
||||
try {
|
||||
val isSuccess = super.onTransact(code, data, reply, flags)
|
||||
if (!isSuccess) {
|
||||
GlobalState.log("onTransact error ===>")
|
||||
BroadcastAction.STOP.sendBroadcast()
|
||||
}
|
||||
return isSuccess
|
||||
@@ -222,6 +220,7 @@ class VpnService : SystemVpnService(), IBaseService,
|
||||
fd,
|
||||
protect = this::protect,
|
||||
resolverProcess = this::resolverProcess,
|
||||
options.stack,
|
||||
options.address,
|
||||
options.dns
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ data class VpnOptions(
|
||||
val allowBypass: Boolean,
|
||||
val systemProxy: Boolean,
|
||||
val bypassDomain: List<String>,
|
||||
val stack: String,
|
||||
val routeAddress: List<String>,
|
||||
) : Parcelable
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ class NetworkObserveModule(private val service: Service) : Module() {
|
||||
private val connectivity by lazy {
|
||||
service.getSystemService<ConnectivityManager>()
|
||||
}
|
||||
private var preDnsList = listOf<String>()
|
||||
|
||||
private val request = NetworkRequest.Builder().apply {
|
||||
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||
@@ -61,6 +62,7 @@ class NetworkObserveModule(private val service: Service) : Module() {
|
||||
|
||||
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
|
||||
networkInfos[network]?.dnsList = linkProperties.dnsServers
|
||||
onUpdateNetwork()
|
||||
setUnderlyingNetworks(network)
|
||||
super.onLinkPropertiesChanged(network, linkProperties)
|
||||
}
|
||||
@@ -96,6 +98,10 @@ class NetworkObserveModule(private val service: Service) : Module() {
|
||||
fun onUpdateNetwork() {
|
||||
val dnsList = (networkInfos.asSequence().minByOrNull { networkToInt(it) }?.value?.dnsList
|
||||
?: emptyList()).map { x -> x.asSocketAddressText(53) }
|
||||
if (dnsList == preDnsList) {
|
||||
return
|
||||
}
|
||||
preDnsList = dnsList
|
||||
Core.updateDNS(dnsList.joinToString { "," })
|
||||
}
|
||||
|
||||
|
||||
@@ -25,18 +25,30 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.zip
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class ExtendedNotificationParams(
|
||||
val title: String,
|
||||
val stopText: String,
|
||||
val onlyStatisticsProxy: Boolean,
|
||||
val contentText: String,
|
||||
)
|
||||
|
||||
val NotificationParams.extended: ExtendedNotificationParams
|
||||
get() = ExtendedNotificationParams(
|
||||
title, stopText, onlyStatisticsProxy, Core.getSpeedTrafficText(onlyStatisticsProxy)
|
||||
)
|
||||
|
||||
class NotificationModule(private val service: Service) : Module() {
|
||||
private val scope = CoroutineScope(Dispatchers.Default)
|
||||
|
||||
override fun onInstall() {
|
||||
State.notificationParamsFlow.value?.let {
|
||||
update(it)
|
||||
update(it.extended)
|
||||
}
|
||||
scope.launch {
|
||||
val screenFlow = service.receiveBroadcastFlow {
|
||||
@@ -48,11 +60,12 @@ class NotificationModule(private val service: Service) : Module() {
|
||||
emit(isScreenOn())
|
||||
}
|
||||
|
||||
tickerFlow(1000, 0)
|
||||
.combine(State.notificationParamsFlow.zip(screenFlow) { params, screenOn ->
|
||||
params to screenOn
|
||||
}) { _, (params, screenOn) -> params to screenOn }
|
||||
.filter { (params, screenOn) -> params != null && screenOn }
|
||||
combine(
|
||||
tickerFlow(1000, 0), State.notificationParamsFlow, screenFlow
|
||||
) { _, params, screenOn ->
|
||||
params?.extended to screenOn
|
||||
}.filter { (params, screenOn) -> params != null && screenOn }
|
||||
.distinctUntilChanged { old, new -> old.first == new.first && old.second == new.second }
|
||||
.collect { (params, _) ->
|
||||
update(params!!)
|
||||
}
|
||||
@@ -87,18 +100,15 @@ class NotificationModule(private val service: Service) : Module() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun update(params: NotificationParams) {
|
||||
val contentText = Core.getSpeedTrafficText(params.onlyStatisticsProxy)
|
||||
private fun update(params: ExtendedNotificationParams) {
|
||||
service.startForeground(
|
||||
with(notificationBuilder) {
|
||||
setContentTitle(params.title)
|
||||
setContentText(contentText)
|
||||
setContentText(params.contentText)
|
||||
setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
clearActions()
|
||||
addAction(
|
||||
0,
|
||||
params.stopText,
|
||||
QuickAction.STOP.quickIntent.toPendingIntent
|
||||
0, params.stopText, QuickAction.STOP.quickIntent.toPendingIntent
|
||||
).build()
|
||||
})
|
||||
}
|
||||
|
||||
17
android/service/src/main/res/drawable/ic_service.xml
Normal file
17
android/service/src/main/res/drawable/ic_service.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="240dp"
|
||||
android:height="240dp"
|
||||
android:viewportWidth="240"
|
||||
android:viewportHeight="240"
|
||||
tools:ignore="VectorRaster">
|
||||
<path
|
||||
android:pathData="M48.1,80.89L168.44,11.41c11.08,-6.4 25.24,-2.6 31.64,8.48 0,0 0,0 0,0h0c6.4,11.08 2.6,25.24 -8.48,31.64 0,0 0,0 0,0l-120.34,69.48c-11.08,6.4 -25.24,2.6 -31.64,-8.48 0,0 0,0 0,0h0c-6.4,-11.08 -2.6,-25.24 8.48,-31.64 0,0 0,0 0,0Z"
|
||||
android:fillColor="#6666FB"/>
|
||||
<path
|
||||
android:pathData="M78.98,134.37l60.18,-34.74c11.07,-6.39 25.23,-2.59 31.63,8.48h0c6.4,11.07 2.61,25.24 -8.47,31.64l-60.18,34.74c-11.08,6.4 -25.24,2.6 -31.64,-8.48 0,0 0,0 0,0h0c-6.4,-11.08 -2.6,-25.24 8.48,-31.64h0Z"
|
||||
android:fillColor="#336AB6"/>
|
||||
<path
|
||||
android:pathData="M109.86,187.86h0c11.08,-6.4 25.24,-2.6 31.64,8.48 0,0 0,0 0,0h0c6.4,11.08 2.6,25.24 -8.48,31.64 0,0 0,0 0,0h0c-11.08,6.4 -25.24,2.6 -31.64,-8.48 0,0 0,0 0,0h0c-6.4,-11.08 -2.6,-25.24 8.48,-31.64 0,0 0,0 0,0Z"
|
||||
android:fillColor="#5CA8E9"/>
|
||||
</vector>
|
||||
Submodule core/Clash.Meta updated: 6fe704ae11...52dfcca013
@@ -181,6 +181,10 @@ func handleAction(action *Action, result ActionResult) {
|
||||
case crashMethod:
|
||||
result.success(true)
|
||||
handleCrash()
|
||||
case deleteFile:
|
||||
path := action.Data.(string)
|
||||
handleDelFile(path, result)
|
||||
return
|
||||
default:
|
||||
nextHandle(action, result)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
void (*release_object_func)(void *obj);
|
||||
|
||||
void (*free_string_func)(char *data);
|
||||
|
||||
void (*protect_func)(void *tun_interface, int fd);
|
||||
|
||||
char* (*resolve_process_func)(void *tun_interface,int protocol, const char *source, const char *target, int uid);
|
||||
@@ -20,6 +22,10 @@ void release_object(void *obj) {
|
||||
release_object_func(obj);
|
||||
}
|
||||
|
||||
void free_string(char *data) {
|
||||
free_string_func(data);
|
||||
}
|
||||
|
||||
void result(void *invoke_Interface, const char *data) {
|
||||
return result_func(invoke_Interface, data);
|
||||
}
|
||||
@@ -16,7 +16,7 @@ func resolveProcess(callback unsafe.Pointer, protocol int, source, target string
|
||||
t := C.CString(target)
|
||||
defer C.free(unsafe.Pointer(t))
|
||||
res := C.resolve_process(callback, C.int(protocol), s, t, C.int(uid))
|
||||
return parseCString(res)
|
||||
return takeCString(res)
|
||||
}
|
||||
|
||||
func invokeResult(callback unsafe.Pointer, data string) {
|
||||
@@ -29,7 +29,7 @@ func releaseObject(callback unsafe.Pointer) {
|
||||
C.release_object(callback)
|
||||
}
|
||||
|
||||
func parseCString(s *C.char) string {
|
||||
//defer C.free(unsafe.Pointer(s))
|
||||
func takeCString(s *C.char) string {
|
||||
defer C.free_string(s)
|
||||
return C.GoString(s)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
extern void (*release_object_func)(void *obj);
|
||||
|
||||
extern void (*free_string_func)(char *data);
|
||||
|
||||
extern void (*protect_func)(void *tun_interface, int fd);
|
||||
|
||||
extern char* (*resolve_process_func)(void *tun_interface, int protocol, const char *source, const char *target, int uid);
|
||||
@@ -16,4 +18,6 @@ extern char* resolve_process(void *tun_interface, int protocol, const char *sour
|
||||
|
||||
extern void release_object(void *obj);
|
||||
|
||||
extern void free_string(char *data);
|
||||
|
||||
extern void result(void *invoke_Interface, const char *data);
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
rp "github.com/metacubex/mihomo/rules/provider"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -159,7 +160,6 @@ func patchSelectGroup(mapping map[string]string) {
|
||||
|
||||
func defaultSetupParams() *SetupParams {
|
||||
return &SetupParams{
|
||||
Config: config.DefaultRawConfig(),
|
||||
TestURL: "https://www.gstatic.com/generate_204",
|
||||
SelectedMap: map[string]string{},
|
||||
}
|
||||
@@ -235,12 +235,30 @@ 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 {
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
var err error
|
||||
constant.DefaultTestURL = params.TestURL
|
||||
currentConfig, err = config.ParseRawConfig(params.Config)
|
||||
currentConfig, err = parseWithPath(filepath.Join(constant.Path.HomeDir(), "config.json"))
|
||||
if err != nil {
|
||||
currentConfig, _ = config.ParseRawConfig(config.DefaultRawConfig())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"github.com/metacubex/mihomo/adapter/provider"
|
||||
P "github.com/metacubex/mihomo/component/process"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
@@ -18,7 +17,6 @@ type InitParams struct {
|
||||
}
|
||||
|
||||
type SetupParams struct {
|
||||
Config *config.RawConfig `json:"config"`
|
||||
SelectedMap map[string]string `json:"selected-map"`
|
||||
TestURL string `json:"test-url"`
|
||||
}
|
||||
@@ -101,6 +99,7 @@ const (
|
||||
crashMethod Method = "crash"
|
||||
setupConfigMethod Method = "setupConfig"
|
||||
getConfigMethod Method = "getConfig"
|
||||
deleteFile Method = "deleteFile"
|
||||
)
|
||||
|
||||
type Method string
|
||||
|
||||
35
core/go.mod
35
core/go.mod
@@ -10,7 +10,6 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/3andne/restls-client-go v0.1.6 // 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
|
||||
@@ -19,15 +18,15 @@ 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.8.3 // indirect
|
||||
github.com/enfein/mieru/v3 v3.16.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/enfein/mieru/v3 v3.19.1 // 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.2 // 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
|
||||
@@ -42,35 +41,36 @@ require (
|
||||
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
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a // indirect
|
||||
github.com/metacubex/ascon v0.1.0 // indirect
|
||||
github.com/metacubex/bart v0.20.5 // indirect
|
||||
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/fswatch v0.1.1 // indirect
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
|
||||
github.com/metacubex/quic-go v0.53.1-0.20250628094454-fda5262d1d9c // indirect
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 // indirect
|
||||
github.com/metacubex/randv2 v0.2.0 // indirect
|
||||
github.com/metacubex/sing v0.5.4 // indirect
|
||||
github.com/metacubex/sing-mux v0.3.2 // indirect
|
||||
github.com/metacubex/restls-client-go v0.1.7 // indirect
|
||||
github.com/metacubex/sing v0.5.5 // indirect
|
||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac // indirect
|
||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.11 // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.5 // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6 // indirect
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.7-0.20250721020617-8e7c37ed3d97 // indirect
|
||||
github.com/metacubex/sing-vmess v0.2.3 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.7 // indirect
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 // indirect
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee // indirect
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 // indirect
|
||||
github.com/metacubex/utls v1.8.0 // indirect
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
|
||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 // indirect
|
||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142 // indirect
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // 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
|
||||
@@ -110,5 +110,4 @@ require (
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
72
core/go.sum
72
core/go.sum
@@ -1,5 +1,3 @@
|
||||
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
|
||||
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
|
||||
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=
|
||||
@@ -24,10 +22,10 @@ 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.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
|
||||
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/enfein/mieru/v3 v3.16.1 h1:CfIt1pQCCQbohkw+HBD2o8V9tnhZvB5yuXGGQIXTLOs=
|
||||
github.com/enfein/mieru/v3 v3.16.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/enfein/mieru/v3 v3.19.1 h1:19b9kgFC7oJXX9RLEO5Pi1gO6yek5cWlpK7IJVUoE8I=
|
||||
github.com/enfein/mieru/v3 v3.19.1/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=
|
||||
@@ -41,8 +39,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.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
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=
|
||||
@@ -80,24 +78,24 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a h1:c1QSGpacSeQdBdWcEKZKGuWLcqIG2wxHEygAcXuDwS4=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
|
||||
github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM=
|
||||
github.com/metacubex/ascon v0.1.0/go.mod h1:eV5oim4cVPPdEL8/EYaTZ0iIKARH9pnhAK/fcT5Kacc=
|
||||
github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM=
|
||||
github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b h1:j7dadXD8I2KTmMt8jg1JcaP1ANL3JEObJPdANKcSYPY=
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b/go.mod h1:+WmP0VJZDkDszvpa83HzfUp6QzARl/IKkMorH4+nODw=
|
||||
github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cqY=
|
||||
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/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
|
||||
@@ -108,37 +106,39 @@ github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||
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.53.1-0.20250628094454-fda5262d1d9c h1:ABQzmOaZddM3q0OYeoZEc0XF+KW+dUdPNvY/c5rsunI=
|
||||
github.com/metacubex/quic-go v0.53.1-0.20250628094454-fda5262d1d9c/go.mod h1:eWlAK3zsKI0P8UhYpXlIsl3mtW4D6MpMNuYLIu8CKWI=
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 h1:8JVlYuE8uSJAvmyCd4TjvDxs57xjb0WxEoaWafK5+qs=
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c=
|
||||
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.4 h1:a4kAOZmF+OXosbzPEcrSc5QD35/ex+MNuZsrcuWskHk=
|
||||
github.com/metacubex/sing v0.5.4/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
|
||||
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||
github.com/metacubex/sing v0.5.5 h1:m5U8iHvRAUxlme3FZlE/LPIGHjU8oMCUzXWGbQQAC1E=
|
||||
github.com/metacubex/sing v0.5.5/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac h1:wDH/Jh/yqWbzPktqJP+Y1cUG8hchcrzKzUxJiSpnaQs=
|
||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb h1:U/m3h8lp/j7i8zFgfvScLdZa1/Y8dd74oO7iZaQq80s=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb/go.mod h1:B60FxaPHjR1SeQB0IiLrgwgvKsaoASfOWdiqhLjmMGA=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.11 h1:p2NGNOdF95e6XvdDKipLj1FRRqR8dnbfC/7pw2CCTlw=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.11/go.mod h1:bT1PCTV316zFnlToRMk5zt9HmIQYRBveiT71mplYPfc=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.5 h1:MnPn0hbcDkSJt6TlpI15XImHKK6IqaOwBUGPKyMnJnE=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.5/go.mod h1:Zyh+rAQRyevYfG/COCvDs1c/YMhGqCuknn7QrGmoQIw=
|
||||
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.6 h1:ZR1kYT0f0Vi64iQSS09OdhFfppiNkh7kjgRdMm0SB98=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6/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.7-0.20250721020617-8e7c37ed3d97 h1:YYpc60UZE2G0pUeHbRw9erDrUDZrPQy8QzWFqA3kHsk=
|
||||
github.com/metacubex/sing-tun v0.4.7-0.20250721020617-8e7c37ed3d97/go.mod h1:2YywXPWW8Z97kTH7RffOeykKzU+l0aiKlglWV1PAS64=
|
||||
github.com/metacubex/sing-vmess v0.2.3 h1:QKLdIk5A2FcR3Y7m2/JO1XhfzgDA8tF4W9/ffsH9opo=
|
||||
github.com/metacubex/sing-vmess v0.2.3/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
||||
github.com/metacubex/sing-tun v0.4.7 h1:ZDY/W+1c7PeWWKeKRyUo18fySF/TWjB0i5ui81Ar778=
|
||||
github.com/metacubex/sing-tun v0.4.7/go.mod h1:xHecZRwBnKWe6zG9amAK9cXf91lF6blgjBqm+VvOrmU=
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 h1:WZepq4TOZa6WewB8tGAZrrL+bL2R2ivoBzuEgAeolWc=
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0/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-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac=
|
||||
github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 h1:yN3mQ4cT9sPUciw/rO0Isc/8QlO86DB6g9SEMRgQ8Cw=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142 h1:csEbKOzRAxJXffOeZnnS3/kA/F55JiTbKv5jcYqCXms=
|
||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142/go.mod h1:67I3skhEY4Sya8f1YxELwWPoeQdXqZCrWNYLvq8gn2U=
|
||||
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/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
@@ -191,7 +191,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
@@ -271,5 +271,3 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
|
||||
28
core/hub.go
28
core/hub.go
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -454,6 +455,33 @@ func handleUpdateConfig(bytes []byte) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func handleDelFile(path string, result ActionResult) {
|
||||
go func() {
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
result.success(err.Error())
|
||||
}
|
||||
result.success("")
|
||||
return
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
err = os.RemoveAll(path)
|
||||
if err != nil {
|
||||
result.success(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = os.Remove(path)
|
||||
if err != nil {
|
||||
result.success(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
result.success("")
|
||||
}()
|
||||
}
|
||||
|
||||
func handleSetupConfig(bytes []byte) string {
|
||||
var params = defaultSetupParams()
|
||||
err := UnmarshalJson(bytes, params)
|
||||
|
||||
24
core/lib.go
24
core/lib.go
@@ -36,11 +36,11 @@ type TunHandler struct {
|
||||
limit *semaphore.Weighted
|
||||
}
|
||||
|
||||
func (th *TunHandler) start(fd int, address, dns string) {
|
||||
func (th *TunHandler) start(fd int, stack, address, dns string) {
|
||||
_ = th.limit.Acquire(context.TODO(), 4)
|
||||
defer th.limit.Release(4)
|
||||
th.initHook()
|
||||
tunListener := t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack, address, dns)
|
||||
tunListener := t.Start(fd, stack, address, dns)
|
||||
if tunListener != nil {
|
||||
log.Infoln("TUN address: %v", tunListener.Address())
|
||||
th.listener = tunListener
|
||||
@@ -136,7 +136,7 @@ func handleStopTun() {
|
||||
}
|
||||
}
|
||||
|
||||
func handleStartTun(callback unsafe.Pointer, fd int, address, dns string) {
|
||||
func handleStartTun(callback unsafe.Pointer, fd int, stack, address, dns string) {
|
||||
handleStopTun()
|
||||
tunLock.Lock()
|
||||
defer tunLock.Unlock()
|
||||
@@ -145,7 +145,7 @@ func handleStartTun(callback unsafe.Pointer, fd int, address, dns string) {
|
||||
callback: callback,
|
||||
limit: semaphore.NewWeighted(4),
|
||||
}
|
||||
tunHandler.start(fd, address, dns)
|
||||
tunHandler.start(fd, stack, address, dns)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ func nextHandle(action *Action, result ActionResult) bool {
|
||||
|
||||
//export invokeAction
|
||||
func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
|
||||
params := parseCString(paramsChar)
|
||||
params := takeCString(paramsChar)
|
||||
var action = &Action{}
|
||||
err := json.Unmarshal([]byte(params), action)
|
||||
if err != nil {
|
||||
@@ -197,8 +197,8 @@ func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
|
||||
}
|
||||
|
||||
//export startTUN
|
||||
func startTUN(callback unsafe.Pointer, fd C.int, addressChar, dnsChar *C.char) bool {
|
||||
handleStartTun(callback, int(fd), parseCString(addressChar), parseCString(dnsChar))
|
||||
func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool {
|
||||
handleStartTun(callback, int(fd), takeCString(stackChar), takeCString(addressChar), takeCString(dnsChar))
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -212,12 +212,16 @@ func setMessageCallback(callback unsafe.Pointer) {
|
||||
|
||||
//export getTotalTraffic
|
||||
func getTotalTraffic(onlyStatisticsProxy bool) *C.char {
|
||||
return C.CString(handleGetTotalTraffic(onlyStatisticsProxy))
|
||||
data := C.CString(handleGetTotalTraffic(onlyStatisticsProxy))
|
||||
defer C.free(unsafe.Pointer(data))
|
||||
return data
|
||||
}
|
||||
|
||||
//export getTraffic
|
||||
func getTraffic(onlyStatisticsProxy bool) *C.char {
|
||||
return C.CString(handleGetTraffic(onlyStatisticsProxy))
|
||||
data := C.CString(handleGetTraffic(onlyStatisticsProxy))
|
||||
defer C.free(unsafe.Pointer(data))
|
||||
return data
|
||||
}
|
||||
|
||||
func sendMessage(message Message) {
|
||||
@@ -249,5 +253,5 @@ func forceGC() {
|
||||
|
||||
//export updateDns
|
||||
func updateDns(s *C.char) {
|
||||
handleUpdateDns(parseCString(s))
|
||||
handleUpdateDns(takeCString(s))
|
||||
}
|
||||
|
||||
@@ -14,9 +14,13 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Start(fd int, device string, stack constant.TUNStack, address, dns string) *sing_tun.Listener {
|
||||
func Start(fd int, stack string, address, dns string) *sing_tun.Listener {
|
||||
var prefix4 []netip.Prefix
|
||||
var prefix6 []netip.Prefix
|
||||
tunStack, ok := constant.StackTypeMapping[strings.ToLower(stack)]
|
||||
if !ok {
|
||||
tunStack = constant.TunSystem
|
||||
}
|
||||
for _, a := range strings.Split(address, ",") {
|
||||
a = strings.TrimSpace(a)
|
||||
if len(a) == 0 {
|
||||
@@ -45,8 +49,8 @@ func Start(fd int, device string, stack constant.TUNStack, address, dns string)
|
||||
|
||||
options := LC.Tun{
|
||||
Enable: true,
|
||||
Device: device,
|
||||
Stack: stack,
|
||||
Device: "FlClash",
|
||||
Stack: tunStack,
|
||||
DNSHijack: dnsHijack,
|
||||
AutoRoute: false,
|
||||
AutoDetectInterface: false,
|
||||
|
||||
@@ -107,57 +107,57 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
|
||||
@override
|
||||
Widget build(context) {
|
||||
return _buildPlatformState(
|
||||
_buildState(
|
||||
Consumer(
|
||||
builder: (_, ref, child) {
|
||||
final locale = ref.watch(
|
||||
appSettingProvider.select((state) => state.locale),
|
||||
);
|
||||
final themeProps = ref.watch(themeSettingProvider);
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
navigatorKey: globalState.navigatorKey,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
builder: (_, child) {
|
||||
return AppEnvManager(
|
||||
child: _buildApp(
|
||||
return Consumer(
|
||||
builder: (_, ref, child) {
|
||||
final locale = ref.watch(
|
||||
appSettingProvider.select((state) => state.locale),
|
||||
);
|
||||
final themeProps = ref.watch(themeSettingProvider);
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
navigatorKey: globalState.navigatorKey,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
builder: (_, child) {
|
||||
return AppEnvManager(
|
||||
child: _buildApp(
|
||||
_buildPlatformState(
|
||||
_buildState(
|
||||
AppSidebarContainer(child: _buildPlatformApp(child!)),
|
||||
),
|
||||
);
|
||||
},
|
||||
scrollBehavior: BaseScrollBehavior(),
|
||||
title: appName,
|
||||
locale: utils.getLocaleForString(locale),
|
||||
supportedLocales: AppLocalizations.delegate.supportedLocales,
|
||||
themeMode: themeProps.themeMode,
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
colorScheme: _getAppColorScheme(
|
||||
brightness: Brightness.light,
|
||||
primaryColor: themeProps.primaryColor,
|
||||
),
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
colorScheme: _getAppColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: themeProps.primaryColor,
|
||||
).toPureBlack(themeProps.pureBlack),
|
||||
),
|
||||
home: child!,
|
||||
);
|
||||
},
|
||||
child: const HomePage(),
|
||||
),
|
||||
),
|
||||
scrollBehavior: BaseScrollBehavior(),
|
||||
title: appName,
|
||||
locale: utils.getLocaleForString(locale),
|
||||
supportedLocales: AppLocalizations.delegate.supportedLocales,
|
||||
themeMode: themeProps.themeMode,
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
colorScheme: _getAppColorScheme(
|
||||
brightness: Brightness.light,
|
||||
primaryColor: themeProps.primaryColor,
|
||||
),
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
colorScheme: _getAppColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: themeProps.primaryColor,
|
||||
).toPureBlack(themeProps.pureBlack),
|
||||
),
|
||||
home: child!,
|
||||
);
|
||||
},
|
||||
child: const HomePage(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
190
lib/common/cache.dart
Normal file
190
lib/common/cache.dart
Normal file
@@ -0,0 +1,190 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
|
||||
class LocalImageCacheManager extends CacheManager {
|
||||
static const key = 'LocalImageCacheData';
|
||||
|
||||
static final LocalImageCacheManager _instance = LocalImageCacheManager._();
|
||||
|
||||
factory LocalImageCacheManager() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
LocalImageCacheManager._()
|
||||
: super(
|
||||
Config(
|
||||
key,
|
||||
stalePeriod: Duration(days: 30),
|
||||
maxNrOfCacheObjects: 1000,
|
||||
fileService: _LocalImageCacheFileService(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _LocalImageCacheFileService extends FileService {
|
||||
_LocalImageCacheFileService();
|
||||
|
||||
@override
|
||||
Future<FileServiceResponse> get(
|
||||
String url, {
|
||||
Map<String, String>? headers,
|
||||
}) async {
|
||||
final response = await request.dio.get<ResponseBody>(
|
||||
url,
|
||||
options: Options(headers: headers, responseType: ResponseType.stream),
|
||||
);
|
||||
return _LocalImageResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
class _LocalImageResponse implements FileServiceResponse {
|
||||
_LocalImageResponse(this._response);
|
||||
|
||||
final DateTime _receivedTime = DateTime.now();
|
||||
|
||||
final Response<ResponseBody> _response;
|
||||
|
||||
@override
|
||||
int get statusCode => _response.statusCode ?? 0;
|
||||
|
||||
@override
|
||||
Stream<List<int>> get content =>
|
||||
_response.data?.stream.transform(uint8ListToListIntConverter) ??
|
||||
Stream.empty();
|
||||
|
||||
@override
|
||||
int? get contentLength => _response.data?.contentLength;
|
||||
|
||||
@override
|
||||
DateTime get validTill {
|
||||
// Without a cache-control header we keep the file for a week
|
||||
var ageDuration = const Duration(days: 7);
|
||||
final controlHeader = _response.headers.value(
|
||||
HttpHeaders.cacheControlHeader,
|
||||
);
|
||||
if (controlHeader != null) {
|
||||
final controlSettings = controlHeader.split(',');
|
||||
for (final setting in controlSettings) {
|
||||
final sanitizedSetting = setting.trim().toLowerCase();
|
||||
if (sanitizedSetting.startsWith('max-age=')) {
|
||||
final validSeconds =
|
||||
int.tryParse(sanitizedSetting.split('=')[1]) ?? 0;
|
||||
if (validSeconds > 0) {
|
||||
ageDuration = Duration(seconds: validSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ageDuration > const Duration(days: 7)) {
|
||||
return _receivedTime.add(ageDuration);
|
||||
}
|
||||
return _receivedTime.add(const Duration(days: 7));
|
||||
}
|
||||
|
||||
@override
|
||||
String? get eTag => _response.headers.value(HttpHeaders.etagHeader);
|
||||
|
||||
@override
|
||||
String get fileExtension {
|
||||
var fileExtension = '';
|
||||
final contentTypeHeader = _response.headers.value(
|
||||
HttpHeaders.contentTypeHeader,
|
||||
);
|
||||
if (contentTypeHeader != null) {
|
||||
final contentType = ContentType.parse(contentTypeHeader);
|
||||
fileExtension = contentType.fileExtension;
|
||||
}
|
||||
return fileExtension;
|
||||
}
|
||||
}
|
||||
|
||||
extension ContentTypeConverter on ContentType {
|
||||
String get fileExtension => mimeTypes[mimeType] ?? '.$subType';
|
||||
}
|
||||
|
||||
const mimeTypes = {
|
||||
'application/vnd.android.package-archive': '.apk',
|
||||
'application/epub+zip': '.epub',
|
||||
'application/gzip': '.gz',
|
||||
'application/java-archive': '.jar',
|
||||
'application/json': '.json',
|
||||
'application/ld+json': '.jsonld',
|
||||
'application/msword': '.doc',
|
||||
'application/octet-stream': '.bin',
|
||||
'application/ogg': '.ogx',
|
||||
'application/pdf': '.pdf',
|
||||
'application/php': '.php',
|
||||
'application/rtf': '.rtf',
|
||||
'application/vnd.amazon.ebook': '.azw',
|
||||
'application/vnd.apple.installer+xml': '.mpkg',
|
||||
'application/vnd.mozilla.xul+xml': '.xul',
|
||||
'application/vnd.ms-excel': '.xls',
|
||||
'application/vnd.ms-fontobject': '.eot',
|
||||
'application/vnd.ms-powerpoint': '.ppt',
|
||||
'application/vnd.oasis.opendocument.presentation': '.odp',
|
||||
'application/vnd.oasis.opendocument.spreadsheet': '.ods',
|
||||
'application/vnd.oasis.opendocument.text': '.odt',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation':
|
||||
'.pptx',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
||||
'.docx',
|
||||
'application/vnd.rar': '.rar',
|
||||
'application/vnd.visio': '.vsd',
|
||||
'application/x-7z-compressed': '.7z',
|
||||
'application/x-abiword': '.abw',
|
||||
'application/x-bzip': '.bz',
|
||||
'application/x-bzip2': '.bz2',
|
||||
'application/x-csh': '.csh',
|
||||
'application/x-freearc': '.arc',
|
||||
'application/x-sh': '.sh',
|
||||
'application/x-shockwave-flash': '.swf',
|
||||
'application/x-tar': '.tar',
|
||||
'application/xhtml+xml': '.xhtml',
|
||||
'application/xml': '.xml',
|
||||
'application/zip': '.zip',
|
||||
'audio/3gpp': '.3gp',
|
||||
'audio/3gpp2': '.3g2',
|
||||
'audio/aac': '.aac',
|
||||
'audio/x-aac': '.aac',
|
||||
'audio/midi': '.midi',
|
||||
'audio/x-midi': '.midi',
|
||||
'audio/x-m4a': '.m4a',
|
||||
'audio/m4a': '.m4a',
|
||||
'audio/mpeg': '.mp3',
|
||||
'audio/ogg': '.oga',
|
||||
'audio/opus': '.opus',
|
||||
'audio/wav': '.wav',
|
||||
'audio/x-wav': '.wav',
|
||||
'audio/webm': '.weba',
|
||||
'font/otf': '.otf',
|
||||
'font/ttf': '.ttf',
|
||||
'font/woff': '.woff',
|
||||
'font/woff2': '.woff2',
|
||||
'image/bmp': '.bmp',
|
||||
'image/gif': '.gif',
|
||||
'image/jpeg': '.jpg',
|
||||
'image/png': '.png',
|
||||
'image/svg+xml': '.svg',
|
||||
'image/tiff': '.tiff',
|
||||
'image/vnd.microsoft.icon': '.ico',
|
||||
'image/webp': '.webp',
|
||||
'text/calendar': '.ics',
|
||||
'text/css': '.css',
|
||||
'text/csv': '.csv',
|
||||
'text/html': '.html',
|
||||
'text/javascript': '.js',
|
||||
'text/plain': '.txt',
|
||||
'text/xml': '.xml',
|
||||
'video/3gpp': '.3gp',
|
||||
'video/3gpp2': '.3g2',
|
||||
'video/mp2t': '.ts',
|
||||
'video/mpeg': '.mpeg',
|
||||
'video/ogg': '.ogv',
|
||||
'video/webm': '.webm',
|
||||
'video/x-msvideo': '.avi',
|
||||
'video/quicktime': '.mov',
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
export 'android.dart';
|
||||
export 'app_localizations.dart';
|
||||
export 'cache.dart';
|
||||
export 'color.dart';
|
||||
export 'compute.dart';
|
||||
export 'constant.dart';
|
||||
export 'context.dart';
|
||||
export 'converter.dart';
|
||||
|
||||
114
lib/common/compute.dart
Normal file
114
lib/common/compute.dart
Normal file
@@ -0,0 +1,114 @@
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
|
||||
import 'string.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
List<Group> computeSort({
|
||||
required List<Group> groups,
|
||||
required ProxiesSortType sortType,
|
||||
required DelayMap delayMap,
|
||||
required SelectedMap selectedMap,
|
||||
required String defaultTestUrl,
|
||||
}) {
|
||||
return groups.map((group) {
|
||||
final proxies = group.all;
|
||||
final newProxies = switch (sortType) {
|
||||
ProxiesSortType.none => proxies,
|
||||
ProxiesSortType.delay => _sortOfDelay(
|
||||
groups: groups,
|
||||
proxies: proxies,
|
||||
delayMap: delayMap,
|
||||
selectedMap: selectedMap,
|
||||
testUrl: group.testUrl.getSafeValue(defaultTestUrl),
|
||||
),
|
||||
ProxiesSortType.name => _sortOfName(proxies),
|
||||
};
|
||||
return group.copyWith(all: newProxies);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
DelayState computeProxyDelayState({
|
||||
required String proxyName,
|
||||
required String testUrl,
|
||||
required List<Group> groups,
|
||||
required SelectedMap 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 SelectedMap selectedMap,
|
||||
}) {
|
||||
return _getRealSelectedProxyState(
|
||||
SelectedProxyState(proxyName: proxyName),
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
);
|
||||
}
|
||||
|
||||
SelectedProxyState _getRealSelectedProxyState(
|
||||
SelectedProxyState state, {
|
||||
required List<Group> groups,
|
||||
required SelectedMap selectedMap,
|
||||
}) {
|
||||
if (state.proxyName.isEmpty) return state;
|
||||
final index = groups.indexWhere((element) => element.name == state.proxyName);
|
||||
final newState = state.copyWith(group: true);
|
||||
if (index == -1) return newState;
|
||||
final group = groups[index];
|
||||
final currentSelectedName = group.getCurrentSelectedName(
|
||||
selectedMap[newState.proxyName] ?? '',
|
||||
);
|
||||
if (currentSelectedName.isEmpty) {
|
||||
return newState;
|
||||
}
|
||||
return _getRealSelectedProxyState(
|
||||
newState.copyWith(proxyName: currentSelectedName, testUrl: group.testUrl),
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
);
|
||||
}
|
||||
|
||||
List<Proxy> _sortOfDelay({
|
||||
required List<Group> groups,
|
||||
required List<Proxy> proxies,
|
||||
required DelayMap delayMap,
|
||||
required SelectedMap 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) =>
|
||||
utils.sortByChar(utils.getPinyin(a.name), utils.getPinyin(b.name)),
|
||||
);
|
||||
}
|
||||
@@ -36,8 +36,8 @@ const geoIpFileName = 'GeoIP.dat';
|
||||
const geoSiteFileName = 'GeoSite.dat';
|
||||
final double kHeaderHeight = system.isDesktop
|
||||
? !system.isMacOS
|
||||
? 40
|
||||
: 28
|
||||
? 40
|
||||
: 28
|
||||
: 0;
|
||||
const profilesDirectoryName = 'profiles';
|
||||
const localhost = '127.0.0.1';
|
||||
@@ -84,7 +84,7 @@ const profilesStoreKey = PageStorageKey<String>('profiles');
|
||||
const defaultPrimaryColor = 0XFFD8C0C3;
|
||||
|
||||
double getWidgetHeight(num lines) {
|
||||
return max(lines * 84 + (lines - 1) * 16, 0).ap;
|
||||
return max(lines * 80 + (lines - 1) * 16, 0).ap;
|
||||
}
|
||||
|
||||
const maxLength = 1000;
|
||||
|
||||
@@ -9,23 +9,17 @@ class Debouncer {
|
||||
FunctionTag tag,
|
||||
Function func, {
|
||||
List<dynamic>? args,
|
||||
Duration duration = const Duration(milliseconds: 600),
|
||||
Duration? duration,
|
||||
}) {
|
||||
final timer = _operations[tag];
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
}
|
||||
_operations[tag] = Timer(
|
||||
duration,
|
||||
() {
|
||||
_operations[tag]?.cancel();
|
||||
_operations.remove(tag);
|
||||
Function.apply(
|
||||
func,
|
||||
args,
|
||||
);
|
||||
},
|
||||
);
|
||||
_operations[tag] = Timer(duration ?? const Duration(milliseconds: 600), () {
|
||||
_operations[tag]?.cancel();
|
||||
_operations.remove(tag);
|
||||
Function.apply(func, args);
|
||||
});
|
||||
}
|
||||
|
||||
void cancel(dynamic tag) {
|
||||
@@ -47,17 +41,11 @@ class Throttler {
|
||||
if (timer != null) {
|
||||
return true;
|
||||
}
|
||||
_operations[tag] = Timer(
|
||||
duration,
|
||||
() {
|
||||
_operations[tag]?.cancel();
|
||||
_operations.remove(tag);
|
||||
Function.apply(
|
||||
func,
|
||||
args,
|
||||
);
|
||||
},
|
||||
);
|
||||
_operations[tag] = Timer(duration, () {
|
||||
_operations[tag]?.cancel();
|
||||
_operations.remove(tag);
|
||||
Function.apply(func, args);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
|
||||
set value(T value) {
|
||||
@@ -20,3 +21,26 @@ mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
|
||||
|
||||
void onUpdate(T value) {}
|
||||
}
|
||||
|
||||
mixin AnyNotifierMixin<T> on AnyNotifier<T, T> {
|
||||
T get value;
|
||||
|
||||
set value(T value) {
|
||||
if (ref.mounted) {
|
||||
state = value;
|
||||
} else {
|
||||
onUpdate(value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(previous, next) {
|
||||
final res = super.updateShouldNotify(previous, next);
|
||||
if (res) {
|
||||
onUpdate(next);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void onUpdate(T value) {}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,12 @@ class Navigation {
|
||||
keep: false,
|
||||
icon: Icon(Icons.space_dashboard),
|
||||
label: PageLabel.dashboard,
|
||||
builder: (_) =>
|
||||
const DashboardView(key: GlobalObjectKey(PageLabel.dashboard)),
|
||||
builder: (_) => const DashboardView(),
|
||||
),
|
||||
NavigationItem(
|
||||
icon: const Icon(Icons.article),
|
||||
label: PageLabel.proxies,
|
||||
builder: (_) =>
|
||||
const ProxiesView(key: GlobalObjectKey(PageLabel.proxies)),
|
||||
builder: (_) => const ProxiesView(),
|
||||
modes: hasProxies
|
||||
? [NavigationItemMode.mobile, NavigationItemMode.desktop]
|
||||
: [],
|
||||
@@ -30,22 +28,19 @@ class Navigation {
|
||||
NavigationItem(
|
||||
icon: Icon(Icons.folder),
|
||||
label: PageLabel.profiles,
|
||||
builder: (_) =>
|
||||
const ProfilesView(key: GlobalObjectKey(PageLabel.profiles)),
|
||||
builder: (_) => const ProfilesView(),
|
||||
),
|
||||
NavigationItem(
|
||||
icon: Icon(Icons.view_timeline),
|
||||
label: PageLabel.requests,
|
||||
builder: (_) =>
|
||||
const RequestsView(key: GlobalObjectKey(PageLabel.requests)),
|
||||
builder: (_) => const RequestsView(),
|
||||
description: 'requestsDesc',
|
||||
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
|
||||
),
|
||||
NavigationItem(
|
||||
icon: Icon(Icons.ballot),
|
||||
label: PageLabel.connections,
|
||||
builder: (_) =>
|
||||
const ConnectionsView(key: GlobalObjectKey(PageLabel.connections)),
|
||||
builder: (_) => const ConnectionsView(),
|
||||
description: 'connectionsDesc',
|
||||
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
|
||||
),
|
||||
@@ -53,14 +48,13 @@ class Navigation {
|
||||
icon: Icon(Icons.storage),
|
||||
label: PageLabel.resources,
|
||||
description: 'resourcesDesc',
|
||||
builder: (_) =>
|
||||
const ResourcesView(key: GlobalObjectKey(PageLabel.resources)),
|
||||
builder: (_) => const ResourcesView(),
|
||||
modes: [NavigationItemMode.more],
|
||||
),
|
||||
NavigationItem(
|
||||
icon: const Icon(Icons.adb),
|
||||
label: PageLabel.logs,
|
||||
builder: (_) => const LogsView(key: GlobalObjectKey(PageLabel.logs)),
|
||||
builder: (_) => const LogsView(),
|
||||
description: 'logsDesc',
|
||||
modes: openLogs
|
||||
? [NavigationItemMode.desktop, NavigationItemMode.more]
|
||||
@@ -69,7 +63,7 @@ class Navigation {
|
||||
NavigationItem(
|
||||
icon: Icon(Icons.construction),
|
||||
label: PageLabel.tools,
|
||||
builder: (_) => const ToolsView(key: GlobalObjectKey(PageLabel.tools)),
|
||||
builder: (_) => const ToolsView(),
|
||||
modes: [NavigationItemMode.desktop, NavigationItemMode.mobile],
|
||||
),
|
||||
];
|
||||
|
||||
@@ -10,6 +10,7 @@ class AppPath {
|
||||
Completer<Directory> dataDir = Completer();
|
||||
Completer<Directory> downloadDir = Completer();
|
||||
Completer<Directory> tempDir = Completer();
|
||||
Completer<Directory> cacheDir = Completer();
|
||||
late String appDirPath;
|
||||
|
||||
AppPath._internal() {
|
||||
@@ -23,6 +24,9 @@ class AppPath {
|
||||
getDownloadsDirectory().then((value) {
|
||||
downloadDir.complete(value);
|
||||
});
|
||||
getApplicationCacheDirectory().then((value) {
|
||||
cacheDir.complete(value);
|
||||
});
|
||||
}
|
||||
|
||||
factory AppPath() {
|
||||
@@ -58,8 +62,13 @@ class AppPath {
|
||||
}
|
||||
|
||||
Future<String> get lockFilePath async {
|
||||
final directory = await dataDir.future;
|
||||
return join(directory.path, 'FlClash.lock');
|
||||
final homeDirPath = await appPath.homeDirPath;
|
||||
return join(homeDirPath, 'FlClash.lock');
|
||||
}
|
||||
|
||||
Future<String> get configFilePath async {
|
||||
final homeDirPath = await appPath.homeDirPath;
|
||||
return join(homeDirPath, 'config.json');
|
||||
}
|
||||
|
||||
Future<String> get sharedPreferencesPath async {
|
||||
@@ -77,6 +86,11 @@ class AppPath {
|
||||
return join(directory, '$id.yaml');
|
||||
}
|
||||
|
||||
Future<String> getIconsCacheDir() async {
|
||||
final directory = await cacheDir.future;
|
||||
return join(directory.path, 'icons');
|
||||
}
|
||||
|
||||
Future<String> getProvidersRootPath() async {
|
||||
final directory = await profilesPath;
|
||||
return join(directory, 'providers');
|
||||
|
||||
@@ -11,35 +11,29 @@ import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class Request {
|
||||
late final Dio _dio;
|
||||
late final Dio dio;
|
||||
late final Dio _clashDio;
|
||||
String? userAgent;
|
||||
|
||||
Request() {
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
headers: {
|
||||
'User-Agent': browserUa,
|
||||
},
|
||||
),
|
||||
);
|
||||
dio = Dio(BaseOptions(headers: {'User-Agent': browserUa}));
|
||||
_clashDio = Dio();
|
||||
_clashDio.httpClientAdapter = IOHttpClientAdapter(createHttpClient: () {
|
||||
final client = HttpClient();
|
||||
client.findProxy = (Uri uri) {
|
||||
client.userAgent = globalState.ua;
|
||||
return FlClashHttpOverrides.handleFindProxy(uri);
|
||||
};
|
||||
return client;
|
||||
});
|
||||
_clashDio.httpClientAdapter = IOHttpClientAdapter(
|
||||
createHttpClient: () {
|
||||
final client = HttpClient();
|
||||
client.findProxy = (Uri uri) {
|
||||
client.userAgent = globalState.ua;
|
||||
return FlClashHttpOverrides.handleFindProxy(uri);
|
||||
};
|
||||
return client;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<Response> getFileResponseForUrl(String url) async {
|
||||
final response = await _clashDio.get(
|
||||
url,
|
||||
options: Options(
|
||||
responseType: ResponseType.bytes,
|
||||
),
|
||||
options: Options(responseType: ResponseType.bytes),
|
||||
);
|
||||
return response;
|
||||
}
|
||||
@@ -47,20 +41,16 @@ class Request {
|
||||
Future<Response> getTextResponseForUrl(String url) async {
|
||||
final response = await _clashDio.get(
|
||||
url,
|
||||
options: Options(
|
||||
responseType: ResponseType.plain,
|
||||
),
|
||||
options: Options(responseType: ResponseType.plain),
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<MemoryImage?> getImage(String url) async {
|
||||
if (url.isEmpty) return null;
|
||||
final response = await _dio.get<Uint8List>(
|
||||
final response = await dio.get<Uint8List>(
|
||||
url,
|
||||
options: Options(
|
||||
responseType: ResponseType.bytes,
|
||||
),
|
||||
options: Options(responseType: ResponseType.bytes),
|
||||
);
|
||||
final data = response.data;
|
||||
if (data == null) return null;
|
||||
@@ -68,11 +58,9 @@ class Request {
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> checkForUpdate() async {
|
||||
final response = await _dio.get(
|
||||
final response = await dio.get(
|
||||
'https://api.github.com/repos/$repository/releases/latest',
|
||||
options: Options(
|
||||
responseType: ResponseType.json,
|
||||
),
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
if (response.statusCode != 200) return null;
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
@@ -85,9 +73,12 @@ class Request {
|
||||
}
|
||||
|
||||
final Map<String, IpInfo Function(Map<String, dynamic>)> _ipInfoSources = {
|
||||
'https://ipwho.is/': IpInfo.fromIpwhoIsJson,
|
||||
'https://api.ip.sb/geoip/': IpInfo.fromIpSbJson,
|
||||
'https://ipwho.is/': IpInfo.fromIpWhoIsJson,
|
||||
'https://api.myip.com/': IpInfo.fromMyIpJson,
|
||||
'https://ipapi.co/json/': IpInfo.fromIpApiCoJson,
|
||||
'https://ident.me/json/': IpInfo.fromIdentMeJson,
|
||||
'http://ip-api.com/json/': IpInfo.fromIpAPIJson,
|
||||
'https://api.ip.sb/geoip/': IpInfo.fromIpSbJson,
|
||||
'https://ipinfo.io/json/': IpInfo.fromIpInfoIoJson,
|
||||
};
|
||||
|
||||
@@ -101,27 +92,27 @@ class Request {
|
||||
}
|
||||
}
|
||||
|
||||
final future = Dio().get<Map<String, dynamic>>(
|
||||
final future = dio.get<Map<String, dynamic>>(
|
||||
source.key,
|
||||
cancelToken: cancelToken,
|
||||
options: Options(
|
||||
responseType: ResponseType.json,
|
||||
),
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
future.then((res) {
|
||||
if (res.statusCode == HttpStatus.ok && res.data != null) {
|
||||
completer.complete(Result.success(source.value(res.data!)));
|
||||
} else {
|
||||
failureCount++;
|
||||
handleFailRes();
|
||||
}
|
||||
}).catchError((e) {
|
||||
failureCount++;
|
||||
if (e is DioException && e.type == DioExceptionType.cancel) {
|
||||
completer.complete(Result.error('cancelled'));
|
||||
}
|
||||
handleFailRes();
|
||||
});
|
||||
future
|
||||
.then((res) {
|
||||
if (res.statusCode == HttpStatus.ok && res.data != null) {
|
||||
completer.complete(Result.success(source.value(res.data!)));
|
||||
return;
|
||||
}
|
||||
failureCount++;
|
||||
handleFailRes();
|
||||
})
|
||||
.catchError((e) {
|
||||
failureCount++;
|
||||
if (e is DioException && e.type == DioExceptionType.cancel) {
|
||||
completer.complete(Result.error('cancelled'));
|
||||
}
|
||||
handleFailRes();
|
||||
});
|
||||
return completer.future;
|
||||
});
|
||||
final res = await Future.any(futures);
|
||||
@@ -131,18 +122,12 @@ class Request {
|
||||
|
||||
Future<bool> pingHelper() async {
|
||||
try {
|
||||
final response = await _dio
|
||||
final response = await dio
|
||||
.get(
|
||||
'http://$localhost:$helperPort/ping',
|
||||
options: Options(
|
||||
responseType: ResponseType.plain,
|
||||
),
|
||||
options: Options(responseType: ResponseType.plain),
|
||||
)
|
||||
.timeout(
|
||||
const Duration(
|
||||
milliseconds: 2000,
|
||||
),
|
||||
);
|
||||
.timeout(const Duration(milliseconds: 2000));
|
||||
if (response.statusCode != HttpStatus.ok) {
|
||||
return false;
|
||||
}
|
||||
@@ -154,22 +139,13 @@ class Request {
|
||||
|
||||
Future<bool> startCoreByHelper(String arg) async {
|
||||
try {
|
||||
final response = await _dio
|
||||
final response = await dio
|
||||
.post(
|
||||
'http://$localhost:$helperPort/start',
|
||||
data: json.encode({
|
||||
'path': appPath.corePath,
|
||||
'arg': arg,
|
||||
}),
|
||||
options: Options(
|
||||
responseType: ResponseType.plain,
|
||||
),
|
||||
data: json.encode({'path': appPath.corePath, 'arg': arg}),
|
||||
options: Options(responseType: ResponseType.plain),
|
||||
)
|
||||
.timeout(
|
||||
const Duration(
|
||||
milliseconds: 2000,
|
||||
),
|
||||
);
|
||||
.timeout(const Duration(milliseconds: 2000));
|
||||
if (response.statusCode != HttpStatus.ok) {
|
||||
return false;
|
||||
}
|
||||
@@ -182,18 +158,12 @@ class Request {
|
||||
|
||||
Future<bool> stopCoreByHelper() async {
|
||||
try {
|
||||
final response = await _dio
|
||||
final response = await dio
|
||||
.post(
|
||||
'http://$localhost:$helperPort/stop',
|
||||
options: Options(
|
||||
responseType: ResponseType.plain,
|
||||
),
|
||||
options: Options(responseType: ResponseType.plain),
|
||||
)
|
||||
.timeout(
|
||||
const Duration(
|
||||
milliseconds: 2000,
|
||||
),
|
||||
);
|
||||
.timeout(const Duration(milliseconds: 2000));
|
||||
if (response.statusCode != HttpStatus.ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -21,16 +21,15 @@ class Utils {
|
||||
String get id {
|
||||
final timestamp = DateTime.now().microsecondsSinceEpoch;
|
||||
final random = Random();
|
||||
final randomStr =
|
||||
String.fromCharCodes(List.generate(8, (_) => random.nextInt(26) + 97));
|
||||
final randomStr = String.fromCharCodes(
|
||||
List.generate(8, (_) => random.nextInt(26) + 97),
|
||||
);
|
||||
return '$timestamp$randomStr';
|
||||
}
|
||||
|
||||
String getDateStringLast2(int value) {
|
||||
var valueRaw = '0$value';
|
||||
return valueRaw.substring(
|
||||
valueRaw.length - 2,
|
||||
);
|
||||
return valueRaw.substring(valueRaw.length - 2);
|
||||
}
|
||||
|
||||
String generateRandomString({int minLength = 10, int maxLength = 100}) {
|
||||
@@ -43,8 +42,9 @@ class Utils {
|
||||
String result = '';
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (random.nextBool()) {
|
||||
result +=
|
||||
String.fromCharCode(0x4E00 + random.nextInt(0x9FA5 - 0x4E00 + 1));
|
||||
result += String.fromCharCode(
|
||||
0x4E00 + random.nextInt(0x9FA5 - 0x4E00 + 1),
|
||||
);
|
||||
} else {
|
||||
result += latinChars[random.nextInt(latinChars.length)];
|
||||
}
|
||||
@@ -60,8 +60,9 @@ class Utils {
|
||||
bytes[6] = (bytes[6] & 0x0F) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3F) | 0x80;
|
||||
|
||||
final hex =
|
||||
bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
||||
final hex = bytes
|
||||
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
|
||||
return '${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20, 32)}';
|
||||
}
|
||||
@@ -102,9 +103,10 @@ class Utils {
|
||||
}
|
||||
if (localSplit.length == 3) {
|
||||
return Locale.fromSubtags(
|
||||
languageCode: localSplit[0],
|
||||
scriptCode: localSplit[1],
|
||||
countryCode: localSplit[2]);
|
||||
languageCode: localSplit[0],
|
||||
scriptCode: localSplit[1],
|
||||
countryCode: localSplit[2],
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -141,9 +143,7 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
String getTrayIconPath({
|
||||
required Brightness brightness,
|
||||
}) {
|
||||
String getTrayIconPath({required Brightness brightness}) {
|
||||
if (system.isMacOS) {
|
||||
return 'assets/images/icon_white.png';
|
||||
}
|
||||
@@ -188,16 +188,20 @@ class Utils {
|
||||
if (disposition == null) return null;
|
||||
final parseValue = HeaderValue.parse(disposition);
|
||||
final parameters = parseValue.parameters;
|
||||
final fileNamePointKey = parameters.keys
|
||||
.firstWhere((key) => key == 'filename*', orElse: () => '');
|
||||
final fileNamePointKey = parameters.keys.firstWhere(
|
||||
(key) => key == 'filename*',
|
||||
orElse: () => '',
|
||||
);
|
||||
if (fileNamePointKey.isNotEmpty) {
|
||||
final res = parameters[fileNamePointKey]?.split("''") ?? [];
|
||||
if (res.length >= 2) {
|
||||
return Uri.decodeComponent(res[1]);
|
||||
}
|
||||
}
|
||||
final fileNameKey = parameters.keys
|
||||
.firstWhere((key) => key == 'filename', orElse: () => '');
|
||||
final fileNameKey = parameters.keys.firstWhere(
|
||||
(key) => key == 'filename',
|
||||
orElse: () => '',
|
||||
);
|
||||
if (fileNameKey.isEmpty) return null;
|
||||
return parameters[fileNameKey];
|
||||
}
|
||||
@@ -236,19 +240,7 @@ class Utils {
|
||||
return max((viewWidth / 320).floor(), 1);
|
||||
}
|
||||
|
||||
final _indexPrimary = [
|
||||
50,
|
||||
100,
|
||||
200,
|
||||
300,
|
||||
400,
|
||||
500,
|
||||
600,
|
||||
700,
|
||||
800,
|
||||
850,
|
||||
900,
|
||||
];
|
||||
final _indexPrimary = [50, 100, 200, 300, 400, 500, 600, 700, 800, 850, 900];
|
||||
|
||||
MaterialColor _createPrimarySwatch(Color color) {
|
||||
final Map<int, Color> swatch = <int, Color>{};
|
||||
@@ -302,16 +294,15 @@ class Utils {
|
||||
}
|
||||
|
||||
Future<String?> getLocalIpAddress() async {
|
||||
List<NetworkInterface> interfaces = await NetworkInterface.list(
|
||||
includeLoopback: false,
|
||||
)
|
||||
..sort((a, b) {
|
||||
if (a.isWifi && !b.isWifi) return -1;
|
||||
if (!a.isWifi && b.isWifi) return 1;
|
||||
if (a.includesIPv4 && !b.includesIPv4) return -1;
|
||||
if (!a.includesIPv4 && b.includesIPv4) return 1;
|
||||
return 0;
|
||||
});
|
||||
List<NetworkInterface> interfaces =
|
||||
await NetworkInterface.list(includeLoopback: false)
|
||||
..sort((a, b) {
|
||||
if (a.isWifi && !b.isWifi) return -1;
|
||||
if (!a.isWifi && b.isWifi) return 1;
|
||||
if (a.includesIPv4 && !b.includesIPv4) return -1;
|
||||
if (!a.includesIPv4 && b.includesIPv4) return 1;
|
||||
return 0;
|
||||
});
|
||||
for (final interface in interfaces) {
|
||||
final addresses = interface.addresses;
|
||||
if (addresses.isEmpty) {
|
||||
@@ -329,59 +320,9 @@ class Utils {
|
||||
|
||||
SingleActivator controlSingleActivator(LogicalKeyboardKey trigger) {
|
||||
final control = system.isMacOS ? false : true;
|
||||
return SingleActivator(
|
||||
trigger,
|
||||
control: control,
|
||||
meta: !control,
|
||||
);
|
||||
return SingleActivator(trigger, control: control, meta: !control);
|
||||
}
|
||||
|
||||
// dynamic convertYamlNode(dynamic node) {
|
||||
// if (node is YamlMap) {
|
||||
// final map = <String, dynamic>{};
|
||||
// YamlNode? mergeKeyNode;
|
||||
// for (final entry in node.nodes.entries) {
|
||||
// if (entry.key is YamlScalar &&
|
||||
// (entry.key as YamlScalar).value == '<<') {
|
||||
// mergeKeyNode = entry.value;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (mergeKeyNode != null) {
|
||||
// final mergeValue = mergeKeyNode.value;
|
||||
// if (mergeValue is YamlMap) {
|
||||
// map.addAll(convertYamlNode(mergeValue) as Map<String, dynamic>);
|
||||
// } else if (mergeValue is YamlList) {
|
||||
// for (final node in mergeValue.nodes) {
|
||||
// if (node.value is YamlMap) {
|
||||
// map.addAll(convertYamlNode(node.value) as Map<String, dynamic>);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// node.nodes.forEach((key, value) {
|
||||
// String stringKey;
|
||||
// if (key is YamlScalar) {
|
||||
// stringKey = key.value.toString();
|
||||
// } else {
|
||||
// stringKey = key.toString();
|
||||
// }
|
||||
// map[stringKey] = convertYamlNode(value.value);
|
||||
// });
|
||||
// return map;
|
||||
// } else if (node is YamlList) {
|
||||
// final list = <dynamic>[];
|
||||
// for (final item in node.nodes) {
|
||||
// list.add(convertYamlNode(item.value));
|
||||
// }
|
||||
// return list;
|
||||
// } else if (node is YamlScalar) {
|
||||
// return node.value;
|
||||
// }
|
||||
// return node;
|
||||
// }
|
||||
|
||||
FutureOr<T> handleWatch<T>(Function function) async {
|
||||
if (kDebugMode) {
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:fl_clash/common/archive.dart';
|
||||
import 'package:fl_clash/core/core.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
import 'package:fl_clash/plugins/service.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/dialog.dart';
|
||||
@@ -41,8 +42,8 @@ class AppController {
|
||||
});
|
||||
}
|
||||
|
||||
void updateGroupsDebounce() {
|
||||
debouncer.call(FunctionTag.updateGroups, updateGroups);
|
||||
void updateGroupsDebounce([Duration? duration]) {
|
||||
debouncer.call(FunctionTag.updateGroups, updateGroups, duration: duration);
|
||||
}
|
||||
|
||||
void addCheckIpNumDebounce() {
|
||||
@@ -77,6 +78,7 @@ class AppController {
|
||||
await coreController.preload();
|
||||
await _initCore();
|
||||
_ref.read(coreStatusProvider.notifier).value = CoreStatus.connected;
|
||||
_ref.read(initProvider.notifier).value = true;
|
||||
if (_ref.read(isStartProvider)) {
|
||||
await globalState.handleStart();
|
||||
}
|
||||
@@ -231,10 +233,6 @@ class AppController {
|
||||
return currentGroupName;
|
||||
}
|
||||
|
||||
ProxyCardState getProxyCardState(String proxyName) {
|
||||
return _ref.read(getProxyCardStateProvider(proxyName));
|
||||
}
|
||||
|
||||
String? getSelectedProxyName(String groupName) {
|
||||
return _ref.read(getSelectedProxyNameProvider(groupName));
|
||||
}
|
||||
@@ -303,10 +301,7 @@ class AppController {
|
||||
}
|
||||
final realTunEnable = _ref.read(realTunEnableProvider);
|
||||
final realPatchConfig = patchConfig.copyWith.tun(enable: realTunEnable);
|
||||
final params = await globalState.getSetupParams(
|
||||
pathConfig: realPatchConfig,
|
||||
);
|
||||
final message = await coreController.setupConfig(params);
|
||||
final message = await coreController.setupConfig(realPatchConfig);
|
||||
lastProfileModified = await _ref.read(
|
||||
currentProfileProvider.select((state) => state?.profileLastModified),
|
||||
);
|
||||
@@ -369,7 +364,22 @@ class AppController {
|
||||
try {
|
||||
_ref.read(groupsProvider.notifier).value = await retry(
|
||||
task: () async {
|
||||
return await coreController.getProxiesGroups();
|
||||
final sortType = _ref.read(
|
||||
proxiesStyleSettingProvider.select((state) => state.sortType),
|
||||
);
|
||||
final delayMap = _ref.read(delayDataSourceProvider);
|
||||
final testUrl = _ref.read(
|
||||
appSettingProvider.select((state) => state.testUrl),
|
||||
);
|
||||
final selectedMap = _ref.read(
|
||||
currentProfileProvider.select((state) => state?.selectedMap ?? {}),
|
||||
);
|
||||
return await coreController.getProxiesGroups(
|
||||
selectedMap: selectedMap,
|
||||
sortType: sortType,
|
||||
delayMap: delayMap,
|
||||
defaultTestUrl: testUrl,
|
||||
);
|
||||
},
|
||||
retryIf: (res) => res.isEmpty,
|
||||
);
|
||||
@@ -526,11 +536,9 @@ class AppController {
|
||||
}
|
||||
};
|
||||
updateTray(true);
|
||||
await _initCore();
|
||||
await _initStatus();
|
||||
autoLaunch?.updateStatus(_ref.read(appSettingProvider).autoLaunch);
|
||||
autoUpdateProfiles();
|
||||
autoCheckUpdate();
|
||||
autoLaunch?.updateStatus(_ref.read(appSettingProvider).autoLaunch);
|
||||
if (!_ref.read(appSettingProvider).silentLaunch) {
|
||||
window?.show();
|
||||
} else {
|
||||
@@ -538,6 +546,18 @@ class AppController {
|
||||
}
|
||||
await _handlePreference();
|
||||
await _handlerDisclaimer();
|
||||
final message = await coreController.preload();
|
||||
if (message.isNotEmpty) {
|
||||
_ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
|
||||
if (context.mounted) {
|
||||
context.showNotifier(message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ref.read(coreStatusProvider.notifier).value = CoreStatus.connected;
|
||||
await service?.syncAndroidState(globalState.getAndroidState());
|
||||
await _initCore();
|
||||
await _initStatus();
|
||||
_ref.read(initProvider.notifier).value = true;
|
||||
}
|
||||
|
||||
@@ -694,47 +714,7 @@ class AppController {
|
||||
_ref.read(providersProvider.notifier).setProvider(provider);
|
||||
}
|
||||
|
||||
List<Proxy> _sortOfName(List<Proxy> proxies) {
|
||||
return List.of(proxies)..sort(
|
||||
(a, b) =>
|
||||
utils.sortByChar(utils.getPinyin(a.name), utils.getPinyin(b.name)),
|
||||
);
|
||||
}
|
||||
|
||||
List<Proxy> _sortOfDelay({required List<Proxy> proxies, String? testUrl}) {
|
||||
return List.of(proxies)..sort((a, b) {
|
||||
final aDelay = _ref.read(
|
||||
getDelayProvider(proxyName: a.name, testUrl: testUrl),
|
||||
);
|
||||
final bDelay = _ref.read(
|
||||
getDelayProvider(proxyName: b.name, testUrl: testUrl),
|
||||
);
|
||||
if (aDelay == null && bDelay == null) {
|
||||
return 0;
|
||||
}
|
||||
if (aDelay == null || aDelay == -1) {
|
||||
return 1;
|
||||
}
|
||||
if (bDelay == null || bDelay == -1) {
|
||||
return -1;
|
||||
}
|
||||
return aDelay.compareTo(bDelay);
|
||||
});
|
||||
}
|
||||
|
||||
List<Proxy> getSortProxies({
|
||||
required List<Proxy> proxies,
|
||||
required ProxiesSortType sortType,
|
||||
String? testUrl,
|
||||
}) {
|
||||
return switch (sortType) {
|
||||
ProxiesSortType.none => proxies,
|
||||
ProxiesSortType.delay => _sortOfDelay(proxies: proxies, testUrl: testUrl),
|
||||
ProxiesSortType.name => _sortOfName(proxies),
|
||||
};
|
||||
}
|
||||
|
||||
Future<Null> clearEffect(String profileId) async {
|
||||
Future<void> clearEffect(String profileId) async {
|
||||
final profilePath = await appPath.getProfilePath(profileId);
|
||||
final providersDirPath = await appPath.getProvidersDirPath(profileId);
|
||||
return await Isolate.run(() async {
|
||||
@@ -743,11 +723,7 @@ class AppController {
|
||||
if (isExists) {
|
||||
profileFile.delete(recursive: true);
|
||||
}
|
||||
final providersFileDir = File(providersDirPath);
|
||||
final providersFileIsExists = await providersFileDir.exists();
|
||||
if (providersFileIsExists) {
|
||||
providersFileDir.delete(recursive: true);
|
||||
}
|
||||
await coreController.deleteFile(providersDirPath);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ 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';
|
||||
|
||||
@@ -28,7 +29,7 @@ class CoreController {
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
Future<bool> preload() {
|
||||
Future<String> preload() {
|
||||
return _interface.preload();
|
||||
}
|
||||
|
||||
@@ -83,29 +84,45 @@ class CoreController {
|
||||
return await _interface.updateConfig(updateParams);
|
||||
}
|
||||
|
||||
Future<String> setupConfig(SetupParams setupParams) async {
|
||||
return await _interface.setupConfig(setupParams);
|
||||
Future<String> setupConfig(ClashConfig clashConfig) async {
|
||||
await globalState.genConfigFile(clashConfig);
|
||||
final params = await globalState.getSetupParams();
|
||||
return await _interface.setupConfig(params);
|
||||
}
|
||||
|
||||
Future<List<Group>> getProxiesGroups() async {
|
||||
Future<List<Group>> getProxiesGroups({
|
||||
required ProxiesSortType sortType,
|
||||
required DelayMap delayMap,
|
||||
required SelectedMap selectedMap,
|
||||
required String defaultTestUrl,
|
||||
}) async {
|
||||
final proxies = await _interface.getProxies();
|
||||
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();
|
||||
return groupsRaw.map((e) => Group.fromJson(e)).toList();
|
||||
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,
|
||||
sortType: sortType,
|
||||
delayMap: delayMap,
|
||||
selectedMap: selectedMap,
|
||||
defaultTestUrl: defaultTestUrl,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async {
|
||||
@@ -154,9 +171,6 @@ class CoreController {
|
||||
if (externalProvidersRawString.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
if (externalProvidersRawString.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return ExternalProvider.fromJson(json.decode(externalProvidersRawString));
|
||||
}
|
||||
|
||||
@@ -258,6 +272,10 @@ class CoreController {
|
||||
Future<void> crash() async {
|
||||
await _interface.crash();
|
||||
}
|
||||
|
||||
Future<String> deleteFile(String path) async {
|
||||
return await _interface.deleteFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
final coreController = CoreController();
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:fl_clash/models/models.dart';
|
||||
mixin CoreInterface {
|
||||
Future<bool> init(InitParams params);
|
||||
|
||||
Future<bool> preload();
|
||||
Future<String> preload();
|
||||
|
||||
Future<bool> shutdown();
|
||||
|
||||
@@ -68,6 +68,8 @@ mixin CoreInterface {
|
||||
|
||||
FutureOr<bool> closeConnection(String id);
|
||||
|
||||
FutureOr<String> deleteFile(String path);
|
||||
|
||||
FutureOr<bool> closeConnections();
|
||||
|
||||
FutureOr<bool> resetConnections();
|
||||
@@ -163,7 +165,7 @@ abstract class CoreHandlerInterface with CoreInterface {
|
||||
|
||||
@override
|
||||
Future<Map> getProxies() async {
|
||||
var map = await _invoke<Map>(method: ActionMethod.getProxies);
|
||||
final map = await _invoke<Map>(method: ActionMethod.getProxies);
|
||||
return map ?? {};
|
||||
}
|
||||
|
||||
@@ -263,6 +265,12 @@ abstract class CoreHandlerInterface with CoreInterface {
|
||||
'';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> deleteFile(String path) async {
|
||||
return await _invoke<String>(method: ActionMethod.deleteFile, data: path) ??
|
||||
'';
|
||||
}
|
||||
|
||||
@override
|
||||
resetTraffic() {
|
||||
_invoke(method: ActionMethod.resetTraffic);
|
||||
@@ -298,6 +306,7 @@ abstract class CoreHandlerInterface with CoreInterface {
|
||||
return await _invoke<String>(
|
||||
method: ActionMethod.asyncTestDelay,
|
||||
data: json.encode(delayParams),
|
||||
timeout: Duration(seconds: 6),
|
||||
) ??
|
||||
json.encode(Delay(name: proxyName, value: -1, url: url));
|
||||
}
|
||||
|
||||
@@ -15,10 +15,12 @@ class CoreLib extends CoreHandlerInterface {
|
||||
CoreLib._internal();
|
||||
|
||||
@override
|
||||
Future<bool> preload() async {
|
||||
await service?.init();
|
||||
_connectedCompleter.complete(true);
|
||||
return true;
|
||||
Future<String> preload() async {
|
||||
final res = await service?.init();
|
||||
if (res?.isEmpty == true) {
|
||||
_connectedCompleter.complete(true);
|
||||
}
|
||||
return res ?? '';
|
||||
}
|
||||
|
||||
factory CoreLib() {
|
||||
|
||||
@@ -149,10 +149,10 @@ class CoreService extends CoreHandlerInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> preload() async {
|
||||
Future<String> preload() async {
|
||||
await _serverCompleter.future;
|
||||
await start();
|
||||
return true;
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/color.dart';
|
||||
import 'package:fl_clash/common/system.dart';
|
||||
import 'package:fl_clash/views/dashboard/widgets/widgets.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
@@ -95,7 +96,7 @@ extension LogLevelExt on LogLevel {
|
||||
LogLevel.silent => Colors.grey.shade700,
|
||||
LogLevel.debug => Colors.grey.shade400,
|
||||
LogLevel.info => null,
|
||||
LogLevel.warning => Colors.yellowAccent,
|
||||
LogLevel.warning => Colors.orangeAccent.darken(),
|
||||
LogLevel.error => Colors.redAccent,
|
||||
};
|
||||
}
|
||||
@@ -242,6 +243,7 @@ enum ActionMethod {
|
||||
getMemory,
|
||||
crash,
|
||||
setupConfig,
|
||||
deleteFile,
|
||||
|
||||
///Android,
|
||||
setState,
|
||||
|
||||
@@ -28,10 +28,9 @@ class AppLocalizations {
|
||||
static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
|
||||
|
||||
static Future<AppLocalizations> load(Locale locale) {
|
||||
final name =
|
||||
(locale.countryCode?.isEmpty ?? false)
|
||||
? locale.languageCode
|
||||
: locale.toString();
|
||||
final name = (locale.countryCode?.isEmpty ?? false)
|
||||
? locale.languageCode
|
||||
: locale.toString();
|
||||
final localeName = Intl.canonicalizedLocale(name);
|
||||
return initializeMessages(localeName).then((_) {
|
||||
Intl.defaultLocale = localeName;
|
||||
|
||||
@@ -24,13 +24,13 @@ Future<void> main() async {
|
||||
Future<void> _service(List<String> flags) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await globalState.init();
|
||||
await service?.init();
|
||||
await coreController.preload();
|
||||
await service?.syncAndroidState(globalState.getAndroidState());
|
||||
tile?.addListener(
|
||||
_TileListenerWithService(
|
||||
onStop: () async {
|
||||
await app?.tip(appLocalizations.stopVpn);
|
||||
await globalState.handleStop();
|
||||
exit(0);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -41,9 +41,8 @@ Future<void> _service(List<String> flags) async {
|
||||
final clashConfig = globalState.config.patchClashConfig.copyWith.tun(
|
||||
enable: false,
|
||||
);
|
||||
final params = await globalState.getSetupParams(pathConfig: clashConfig);
|
||||
await coreController.setupConfig(params);
|
||||
await globalState.handleStart();
|
||||
await coreController.setupConfig(clashConfig);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,11 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
|
||||
globalState.appController.savePreferencesDebounce();
|
||||
}
|
||||
});
|
||||
ref.listenManual(needUpdateGroupsProvider, (prev, next) {
|
||||
if (prev != next) {
|
||||
globalState.appController.updateGroupsDebounce();
|
||||
}
|
||||
});
|
||||
if (window == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,6 @@ class _CoreContainerState extends ConsumerState<CoreManager>
|
||||
if (ref.read(coreStatusProvider) != CoreStatus.connected) {
|
||||
return;
|
||||
}
|
||||
context.showNotifier('Core crash');
|
||||
ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
|
||||
await coreController.shutdown();
|
||||
super.onCrash();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: invalid_annotation_target
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: invalid_annotation_target
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -365,13 +363,41 @@ abstract class IpInfo with _$IpInfo {
|
||||
};
|
||||
}
|
||||
|
||||
static IpInfo fromIpwhoIsJson(Map<String, dynamic> json) {
|
||||
static IpInfo fromIpWhoIsJson(Map<String, dynamic> json) {
|
||||
return switch (json) {
|
||||
{'ip': final String ip, 'country_code': final String countryCode} =>
|
||||
IpInfo(ip: ip, countryCode: countryCode),
|
||||
_ => throw const FormatException('invalid json'),
|
||||
};
|
||||
}
|
||||
|
||||
static IpInfo fromMyIpJson(Map<String, dynamic> json) {
|
||||
return switch (json) {
|
||||
{'ip': final String ip, 'cc': final String countryCode} => IpInfo(
|
||||
ip: ip,
|
||||
countryCode: countryCode,
|
||||
),
|
||||
_ => throw const FormatException('invalid json'),
|
||||
};
|
||||
}
|
||||
|
||||
static IpInfo fromIpAPIJson(Map<String, dynamic> json) {
|
||||
return switch (json) {
|
||||
{'query': final String ip, 'countryCode': final String countryCode} =>
|
||||
IpInfo(ip: ip, countryCode: countryCode),
|
||||
_ => throw const FormatException('invalid json'),
|
||||
};
|
||||
}
|
||||
|
||||
static IpInfo fromIdentMeJson(Map<String, dynamic> json) {
|
||||
return switch (json) {
|
||||
{'ip': final String ip, 'cc': final String countryCode} => IpInfo(
|
||||
ip: ip,
|
||||
countryCode: countryCode,
|
||||
),
|
||||
_ => throw const FormatException('invalid json'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -464,3 +490,29 @@ abstract class Script with _$Script {
|
||||
|
||||
factory Script.fromJson(Map<String, Object?> json) => _$ScriptFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class DelayState with _$DelayState {
|
||||
const factory DelayState({required int delay, required bool group}) =
|
||||
_DelayState;
|
||||
}
|
||||
|
||||
extension DelayStateExt on DelayState {
|
||||
int get priority {
|
||||
if (delay > 0) return 0;
|
||||
if (delay == 0) return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
int compareTo(DelayState other) {
|
||||
if (priority != other.priority) {
|
||||
return priority.compareTo(other.priority);
|
||||
}
|
||||
if (delay != other.delay) {
|
||||
return delay.compareTo(other.delay);
|
||||
}
|
||||
if (group && !group) return -1;
|
||||
if (!group && group) return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: invalid_annotation_target
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -27,7 +25,7 @@ const defaultBypassDomain = [
|
||||
'172.2*',
|
||||
'172.30.*',
|
||||
'172.31.*',
|
||||
'192.168.*'
|
||||
'192.168.*',
|
||||
];
|
||||
|
||||
const defaultAppSettingProps = AppSettingProps();
|
||||
@@ -36,9 +34,7 @@ const defaultNetworkProps = NetworkProps();
|
||||
const defaultProxiesStyle = ProxiesStyle();
|
||||
const defaultWindowProps = WindowProps();
|
||||
const defaultAccessControl = AccessControl();
|
||||
final defaultThemeProps = ThemeProps(
|
||||
primaryColor: defaultPrimaryColor,
|
||||
);
|
||||
final defaultThemeProps = ThemeProps(primaryColor: defaultPrimaryColor);
|
||||
|
||||
const List<DashboardWidget> defaultDashboardWidgets = [
|
||||
DashboardWidget.networkSpeed,
|
||||
@@ -115,9 +111,9 @@ abstract class AccessControl with _$AccessControl {
|
||||
|
||||
extension AccessControlExt on AccessControl {
|
||||
List<String> get currentList => switch (mode) {
|
||||
AccessControlMode.acceptSelected => acceptList,
|
||||
AccessControlMode.rejectSelected => rejectList,
|
||||
};
|
||||
AccessControlMode.acceptSelected => acceptList,
|
||||
AccessControlMode.rejectSelected => rejectList,
|
||||
};
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: invalid_annotation_target
|
||||
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@@ -10,7 +8,6 @@ part 'generated/core.g.dart';
|
||||
@freezed
|
||||
abstract class SetupParams with _$SetupParams {
|
||||
const factory SetupParams({
|
||||
@JsonKey(name: 'config') required Map<String, dynamic> config,
|
||||
@JsonKey(name: 'selected-map') required Map<String, String> selectedMap,
|
||||
@JsonKey(name: 'test-url') required String testUrl,
|
||||
}) = _SetupParams;
|
||||
@@ -51,6 +48,7 @@ abstract class VpnOptions with _$VpnOptions {
|
||||
required bool allowBypass,
|
||||
required bool systemProxy,
|
||||
required List<String> bypassDomain,
|
||||
required String stack,
|
||||
@Default([]) List<String> routeAddress,
|
||||
}) = _VpnOptions;
|
||||
|
||||
|
||||
@@ -5768,6 +5768,266 @@ as String,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$DelayState {
|
||||
|
||||
int get delay; bool get group;
|
||||
/// Create a copy of DelayState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$DelayStateCopyWith<DelayState> get copyWith => _$DelayStateCopyWithImpl<DelayState>(this as DelayState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is DelayState&&(identical(other.delay, delay) || other.delay == delay)&&(identical(other.group, group) || other.group == group));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,delay,group);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DelayState(delay: $delay, group: $group)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $DelayStateCopyWith<$Res> {
|
||||
factory $DelayStateCopyWith(DelayState value, $Res Function(DelayState) _then) = _$DelayStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
int delay, bool group
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$DelayStateCopyWithImpl<$Res>
|
||||
implements $DelayStateCopyWith<$Res> {
|
||||
_$DelayStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final DelayState _self;
|
||||
final $Res Function(DelayState) _then;
|
||||
|
||||
/// Create a copy of DelayState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? delay = null,Object? group = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
delay: null == delay ? _self.delay : delay // ignore: cast_nullable_to_non_nullable
|
||||
as int,group: null == group ? _self.group : group // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [DelayState].
|
||||
extension DelayStatePatterns on DelayState {
|
||||
/// 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( _DelayState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _DelayState() 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( _DelayState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _DelayState():
|
||||
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( _DelayState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _DelayState() 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( int delay, bool group)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _DelayState() when $default != null:
|
||||
return $default(_that.delay,_that.group);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( int delay, bool group) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _DelayState():
|
||||
return $default(_that.delay,_that.group);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( int delay, bool group)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _DelayState() when $default != null:
|
||||
return $default(_that.delay,_that.group);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _DelayState implements DelayState {
|
||||
const _DelayState({required this.delay, required this.group});
|
||||
|
||||
|
||||
@override final int delay;
|
||||
@override final bool group;
|
||||
|
||||
/// Create a copy of DelayState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$DelayStateCopyWith<_DelayState> get copyWith => __$DelayStateCopyWithImpl<_DelayState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DelayState&&(identical(other.delay, delay) || other.delay == delay)&&(identical(other.group, group) || other.group == group));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,delay,group);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DelayState(delay: $delay, group: $group)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$DelayStateCopyWith<$Res> implements $DelayStateCopyWith<$Res> {
|
||||
factory _$DelayStateCopyWith(_DelayState value, $Res Function(_DelayState) _then) = __$DelayStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
int delay, bool group
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$DelayStateCopyWithImpl<$Res>
|
||||
implements _$DelayStateCopyWith<$Res> {
|
||||
__$DelayStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _DelayState _self;
|
||||
final $Res Function(_DelayState) _then;
|
||||
|
||||
/// Create a copy of DelayState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? delay = null,Object? group = null,}) {
|
||||
return _then(_DelayState(
|
||||
delay: null == delay ? _self.delay : delay // ignore: cast_nullable_to_non_nullable
|
||||
as int,group: null == group ? _self.group : group // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
|
||||
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$SetupParams {
|
||||
|
||||
@JsonKey(name: 'config') Map<String, dynamic> get config;@JsonKey(name: 'selected-map') Map<String, String> get selectedMap;@JsonKey(name: 'test-url') String get testUrl;
|
||||
@JsonKey(name: 'selected-map') Map<String, String> get selectedMap;@JsonKey(name: 'test-url') String get testUrl;
|
||||
/// Create a copy of SetupParams
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -28,16 +28,16 @@ $SetupParamsCopyWith<SetupParams> get copyWith => _$SetupParamsCopyWithImpl<Setu
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SetupParams&&const DeepCollectionEquality().equals(other.config, config)&&const DeepCollectionEquality().equals(other.selectedMap, selectedMap)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SetupParams&&const DeepCollectionEquality().equals(other.selectedMap, selectedMap)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(config),const DeepCollectionEquality().hash(selectedMap),testUrl);
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(selectedMap),testUrl);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SetupParams(config: $config, selectedMap: $selectedMap, testUrl: $testUrl)';
|
||||
return 'SetupParams(selectedMap: $selectedMap, testUrl: $testUrl)';
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ abstract mixin class $SetupParamsCopyWith<$Res> {
|
||||
factory $SetupParamsCopyWith(SetupParams value, $Res Function(SetupParams) _then) = _$SetupParamsCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
@JsonKey(name: 'config') Map<String, dynamic> config,@JsonKey(name: 'selected-map') Map<String, String> selectedMap,@JsonKey(name: 'test-url') String testUrl
|
||||
@JsonKey(name: 'selected-map') Map<String, String> selectedMap,@JsonKey(name: 'test-url') String testUrl
|
||||
});
|
||||
|
||||
|
||||
@@ -65,10 +65,9 @@ class _$SetupParamsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SetupParams
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? config = null,Object? selectedMap = null,Object? testUrl = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? selectedMap = null,Object? testUrl = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,selectedMap: null == selectedMap ? _self.selectedMap : selectedMap // ignore: cast_nullable_to_non_nullable
|
||||
selectedMap: null == selectedMap ? _self.selectedMap : selectedMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>,testUrl: null == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
@@ -155,10 +154,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(name: 'config') Map<String, dynamic> config, @JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SetupParams() when $default != null:
|
||||
return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
|
||||
return $default(_that.selectedMap,_that.testUrl);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -176,10 +175,10 @@ return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(name: 'config') Map<String, dynamic> config, @JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SetupParams():
|
||||
return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
|
||||
return $default(_that.selectedMap,_that.testUrl);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -196,10 +195,10 @@ return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(name: 'config') Map<String, dynamic> config, @JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SetupParams() when $default != null:
|
||||
return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
|
||||
return $default(_that.selectedMap,_that.testUrl);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -211,16 +210,9 @@ return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
|
||||
@JsonSerializable()
|
||||
|
||||
class _SetupParams implements SetupParams {
|
||||
const _SetupParams({@JsonKey(name: 'config') required final Map<String, dynamic> config, @JsonKey(name: 'selected-map') required final Map<String, String> selectedMap, @JsonKey(name: 'test-url') required this.testUrl}): _config = config,_selectedMap = selectedMap;
|
||||
const _SetupParams({@JsonKey(name: 'selected-map') required final Map<String, String> selectedMap, @JsonKey(name: 'test-url') required this.testUrl}): _selectedMap = selectedMap;
|
||||
factory _SetupParams.fromJson(Map<String, dynamic> json) => _$SetupParamsFromJson(json);
|
||||
|
||||
final Map<String, dynamic> _config;
|
||||
@override@JsonKey(name: 'config') Map<String, dynamic> get config {
|
||||
if (_config is EqualUnmodifiableMapView) return _config;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_config);
|
||||
}
|
||||
|
||||
final Map<String, String> _selectedMap;
|
||||
@override@JsonKey(name: 'selected-map') Map<String, String> get selectedMap {
|
||||
if (_selectedMap is EqualUnmodifiableMapView) return _selectedMap;
|
||||
@@ -243,16 +235,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SetupParams&&const DeepCollectionEquality().equals(other._config, _config)&&const DeepCollectionEquality().equals(other._selectedMap, _selectedMap)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SetupParams&&const DeepCollectionEquality().equals(other._selectedMap, _selectedMap)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_config),const DeepCollectionEquality().hash(_selectedMap),testUrl);
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_selectedMap),testUrl);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SetupParams(config: $config, selectedMap: $selectedMap, testUrl: $testUrl)';
|
||||
return 'SetupParams(selectedMap: $selectedMap, testUrl: $testUrl)';
|
||||
}
|
||||
|
||||
|
||||
@@ -263,7 +255,7 @@ abstract mixin class _$SetupParamsCopyWith<$Res> implements $SetupParamsCopyWith
|
||||
factory _$SetupParamsCopyWith(_SetupParams value, $Res Function(_SetupParams) _then) = __$SetupParamsCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
@JsonKey(name: 'config') Map<String, dynamic> config,@JsonKey(name: 'selected-map') Map<String, String> selectedMap,@JsonKey(name: 'test-url') String testUrl
|
||||
@JsonKey(name: 'selected-map') Map<String, String> selectedMap,@JsonKey(name: 'test-url') String testUrl
|
||||
});
|
||||
|
||||
|
||||
@@ -280,10 +272,9 @@ class __$SetupParamsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SetupParams
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? config = null,Object? selectedMap = null,Object? testUrl = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? selectedMap = null,Object? testUrl = null,}) {
|
||||
return _then(_SetupParams(
|
||||
config: null == config ? _self._config : config // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,selectedMap: null == selectedMap ? _self._selectedMap : selectedMap // ignore: cast_nullable_to_non_nullable
|
||||
selectedMap: null == selectedMap ? _self._selectedMap : selectedMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>,testUrl: null == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
@@ -604,7 +595,7 @@ $TunCopyWith<$Res> get tun {
|
||||
/// @nodoc
|
||||
mixin _$VpnOptions {
|
||||
|
||||
bool get enable; int get port; bool get ipv6; bool get dnsHijacking; AccessControl get accessControl; bool get allowBypass; bool get systemProxy; List<String> get bypassDomain; List<String> get routeAddress;
|
||||
bool get enable; int get port; bool get ipv6; bool get dnsHijacking; AccessControl get accessControl; bool get allowBypass; bool get systemProxy; List<String> get bypassDomain; String get stack; List<String> get routeAddress;
|
||||
/// Create a copy of VpnOptions
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -617,16 +608,16 @@ $VpnOptionsCopyWith<VpnOptions> get copyWith => _$VpnOptionsCopyWithImpl<VpnOpti
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is VpnOptions&&(identical(other.enable, enable) || other.enable == enable)&&(identical(other.port, port) || other.port == port)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.dnsHijacking, dnsHijacking) || other.dnsHijacking == dnsHijacking)&&(identical(other.accessControl, accessControl) || other.accessControl == accessControl)&&(identical(other.allowBypass, allowBypass) || other.allowBypass == allowBypass)&&(identical(other.systemProxy, systemProxy) || other.systemProxy == systemProxy)&&const DeepCollectionEquality().equals(other.bypassDomain, bypassDomain)&&const DeepCollectionEquality().equals(other.routeAddress, routeAddress));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is VpnOptions&&(identical(other.enable, enable) || other.enable == enable)&&(identical(other.port, port) || other.port == port)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.dnsHijacking, dnsHijacking) || other.dnsHijacking == dnsHijacking)&&(identical(other.accessControl, accessControl) || other.accessControl == accessControl)&&(identical(other.allowBypass, allowBypass) || other.allowBypass == allowBypass)&&(identical(other.systemProxy, systemProxy) || other.systemProxy == systemProxy)&&const DeepCollectionEquality().equals(other.bypassDomain, bypassDomain)&&(identical(other.stack, stack) || other.stack == stack)&&const DeepCollectionEquality().equals(other.routeAddress, routeAddress));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,enable,port,ipv6,dnsHijacking,accessControl,allowBypass,systemProxy,const DeepCollectionEquality().hash(bypassDomain),const DeepCollectionEquality().hash(routeAddress));
|
||||
int get hashCode => Object.hash(runtimeType,enable,port,ipv6,dnsHijacking,accessControl,allowBypass,systemProxy,const DeepCollectionEquality().hash(bypassDomain),stack,const DeepCollectionEquality().hash(routeAddress));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VpnOptions(enable: $enable, port: $port, ipv6: $ipv6, dnsHijacking: $dnsHijacking, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, routeAddress: $routeAddress)';
|
||||
return 'VpnOptions(enable: $enable, port: $port, ipv6: $ipv6, dnsHijacking: $dnsHijacking, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, stack: $stack, routeAddress: $routeAddress)';
|
||||
}
|
||||
|
||||
|
||||
@@ -637,7 +628,7 @@ abstract mixin class $VpnOptionsCopyWith<$Res> {
|
||||
factory $VpnOptionsCopyWith(VpnOptions value, $Res Function(VpnOptions) _then) = _$VpnOptionsCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress
|
||||
bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress
|
||||
});
|
||||
|
||||
|
||||
@@ -654,7 +645,7 @@ class _$VpnOptionsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of VpnOptions
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? enable = null,Object? port = null,Object? ipv6 = null,Object? dnsHijacking = null,Object? accessControl = null,Object? allowBypass = null,Object? systemProxy = null,Object? bypassDomain = null,Object? routeAddress = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? enable = null,Object? port = null,Object? ipv6 = null,Object? dnsHijacking = null,Object? accessControl = null,Object? allowBypass = null,Object? systemProxy = null,Object? bypassDomain = null,Object? stack = null,Object? routeAddress = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
enable: null == enable ? _self.enable : enable // ignore: cast_nullable_to_non_nullable
|
||||
as bool,port: null == port ? _self.port : port // ignore: cast_nullable_to_non_nullable
|
||||
@@ -664,7 +655,8 @@ as bool,accessControl: null == accessControl ? _self.accessControl : accessContr
|
||||
as AccessControl,allowBypass: null == allowBypass ? _self.allowBypass : allowBypass // ignore: cast_nullable_to_non_nullable
|
||||
as bool,systemProxy: null == systemProxy ? _self.systemProxy : systemProxy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,bypassDomain: null == bypassDomain ? _self.bypassDomain : bypassDomain // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,routeAddress: null == routeAddress ? _self.routeAddress : routeAddress // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,stack: null == stack ? _self.stack : stack // ignore: cast_nullable_to_non_nullable
|
||||
as String,routeAddress: null == routeAddress ? _self.routeAddress : routeAddress // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
));
|
||||
}
|
||||
@@ -759,10 +751,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _VpnOptions() when $default != null:
|
||||
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.routeAddress);case _:
|
||||
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.stack,_that.routeAddress);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -780,10 +772,10 @@ return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.acce
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _VpnOptions():
|
||||
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.routeAddress);case _:
|
||||
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.stack,_that.routeAddress);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -800,10 +792,10 @@ return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.acce
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _VpnOptions() when $default != null:
|
||||
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.routeAddress);case _:
|
||||
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.stack,_that.routeAddress);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -815,7 +807,7 @@ return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.acce
|
||||
@JsonSerializable()
|
||||
|
||||
class _VpnOptions implements VpnOptions {
|
||||
const _VpnOptions({required this.enable, required this.port, required this.ipv6, required this.dnsHijacking, required this.accessControl, required this.allowBypass, required this.systemProxy, required final List<String> bypassDomain, final List<String> routeAddress = const []}): _bypassDomain = bypassDomain,_routeAddress = routeAddress;
|
||||
const _VpnOptions({required this.enable, required this.port, required this.ipv6, required this.dnsHijacking, required this.accessControl, required this.allowBypass, required this.systemProxy, required final List<String> bypassDomain, required this.stack, final List<String> routeAddress = const []}): _bypassDomain = bypassDomain,_routeAddress = routeAddress;
|
||||
factory _VpnOptions.fromJson(Map<String, dynamic> json) => _$VpnOptionsFromJson(json);
|
||||
|
||||
@override final bool enable;
|
||||
@@ -832,6 +824,7 @@ class _VpnOptions implements VpnOptions {
|
||||
return EqualUnmodifiableListView(_bypassDomain);
|
||||
}
|
||||
|
||||
@override final String stack;
|
||||
final List<String> _routeAddress;
|
||||
@override@JsonKey() List<String> get routeAddress {
|
||||
if (_routeAddress is EqualUnmodifiableListView) return _routeAddress;
|
||||
@@ -853,16 +846,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _VpnOptions&&(identical(other.enable, enable) || other.enable == enable)&&(identical(other.port, port) || other.port == port)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.dnsHijacking, dnsHijacking) || other.dnsHijacking == dnsHijacking)&&(identical(other.accessControl, accessControl) || other.accessControl == accessControl)&&(identical(other.allowBypass, allowBypass) || other.allowBypass == allowBypass)&&(identical(other.systemProxy, systemProxy) || other.systemProxy == systemProxy)&&const DeepCollectionEquality().equals(other._bypassDomain, _bypassDomain)&&const DeepCollectionEquality().equals(other._routeAddress, _routeAddress));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _VpnOptions&&(identical(other.enable, enable) || other.enable == enable)&&(identical(other.port, port) || other.port == port)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.dnsHijacking, dnsHijacking) || other.dnsHijacking == dnsHijacking)&&(identical(other.accessControl, accessControl) || other.accessControl == accessControl)&&(identical(other.allowBypass, allowBypass) || other.allowBypass == allowBypass)&&(identical(other.systemProxy, systemProxy) || other.systemProxy == systemProxy)&&const DeepCollectionEquality().equals(other._bypassDomain, _bypassDomain)&&(identical(other.stack, stack) || other.stack == stack)&&const DeepCollectionEquality().equals(other._routeAddress, _routeAddress));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,enable,port,ipv6,dnsHijacking,accessControl,allowBypass,systemProxy,const DeepCollectionEquality().hash(_bypassDomain),const DeepCollectionEquality().hash(_routeAddress));
|
||||
int get hashCode => Object.hash(runtimeType,enable,port,ipv6,dnsHijacking,accessControl,allowBypass,systemProxy,const DeepCollectionEquality().hash(_bypassDomain),stack,const DeepCollectionEquality().hash(_routeAddress));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VpnOptions(enable: $enable, port: $port, ipv6: $ipv6, dnsHijacking: $dnsHijacking, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, routeAddress: $routeAddress)';
|
||||
return 'VpnOptions(enable: $enable, port: $port, ipv6: $ipv6, dnsHijacking: $dnsHijacking, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, stack: $stack, routeAddress: $routeAddress)';
|
||||
}
|
||||
|
||||
|
||||
@@ -873,7 +866,7 @@ abstract mixin class _$VpnOptionsCopyWith<$Res> implements $VpnOptionsCopyWith<$
|
||||
factory _$VpnOptionsCopyWith(_VpnOptions value, $Res Function(_VpnOptions) _then) = __$VpnOptionsCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress
|
||||
bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress
|
||||
});
|
||||
|
||||
|
||||
@@ -890,7 +883,7 @@ class __$VpnOptionsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of VpnOptions
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? enable = null,Object? port = null,Object? ipv6 = null,Object? dnsHijacking = null,Object? accessControl = null,Object? allowBypass = null,Object? systemProxy = null,Object? bypassDomain = null,Object? routeAddress = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? enable = null,Object? port = null,Object? ipv6 = null,Object? dnsHijacking = null,Object? accessControl = null,Object? allowBypass = null,Object? systemProxy = null,Object? bypassDomain = null,Object? stack = null,Object? routeAddress = null,}) {
|
||||
return _then(_VpnOptions(
|
||||
enable: null == enable ? _self.enable : enable // ignore: cast_nullable_to_non_nullable
|
||||
as bool,port: null == port ? _self.port : port // ignore: cast_nullable_to_non_nullable
|
||||
@@ -900,7 +893,8 @@ as bool,accessControl: null == accessControl ? _self.accessControl : accessContr
|
||||
as AccessControl,allowBypass: null == allowBypass ? _self.allowBypass : allowBypass // ignore: cast_nullable_to_non_nullable
|
||||
as bool,systemProxy: null == systemProxy ? _self.systemProxy : systemProxy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,bypassDomain: null == bypassDomain ? _self._bypassDomain : bypassDomain // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,routeAddress: null == routeAddress ? _self._routeAddress : routeAddress // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,stack: null == stack ? _self.stack : stack // ignore: cast_nullable_to_non_nullable
|
||||
as String,routeAddress: null == routeAddress ? _self._routeAddress : routeAddress // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -7,14 +7,12 @@ part of '../core.dart';
|
||||
// **************************************************************************
|
||||
|
||||
_SetupParams _$SetupParamsFromJson(Map<String, dynamic> json) => _SetupParams(
|
||||
config: json['config'] as Map<String, dynamic>,
|
||||
selectedMap: Map<String, String>.from(json['selected-map'] as Map),
|
||||
testUrl: json['test-url'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SetupParamsToJson(_SetupParams instance) =>
|
||||
<String, dynamic>{
|
||||
'config': instance.config,
|
||||
'selected-map': instance.selectedMap,
|
||||
'test-url': instance.testUrl,
|
||||
};
|
||||
@@ -91,6 +89,7 @@ _VpnOptions _$VpnOptionsFromJson(Map<String, dynamic> json) => _VpnOptions(
|
||||
bypassDomain: (json['bypassDomain'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList(),
|
||||
stack: json['stack'] as String,
|
||||
routeAddress:
|
||||
(json['routeAddress'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
@@ -108,6 +107,7 @@ Map<String, dynamic> _$VpnOptionsToJson(_VpnOptions instance) =>
|
||||
'allowBypass': instance.allowBypass,
|
||||
'systemProxy': instance.systemProxy,
|
||||
'bypassDomain': instance.bypassDomain,
|
||||
'stack': instance.stack,
|
||||
'routeAddress': instance.routeAddress,
|
||||
};
|
||||
|
||||
@@ -291,6 +291,7 @@ const _$ActionMethodEnumMap = {
|
||||
ActionMethod.getMemory: 'getMemory',
|
||||
ActionMethod.crash: 'crash',
|
||||
ActionMethod.setupConfig: 'setupConfig',
|
||||
ActionMethod.deleteFile: 'deleteFile',
|
||||
ActionMethod.setState: 'setState',
|
||||
ActionMethod.startTun: 'startTun',
|
||||
ActionMethod.stopTun: 'stopTun',
|
||||
|
||||
@@ -2982,7 +2982,7 @@ as List<NavigationItem>,
|
||||
/// @nodoc
|
||||
mixin _$ProxiesListState {
|
||||
|
||||
List<Group> get groups; Set<String> get currentUnfoldSet; ProxiesSortType get proxiesSortType; ProxyCardType get proxyCardType; num get sortNum; int get columns;
|
||||
List<Group> get groups; Set<String> get currentUnfoldSet; ProxyCardType get proxyCardType; int get columns;
|
||||
/// Create a copy of ProxiesListState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -2993,16 +2993,16 @@ $ProxiesListStateCopyWith<ProxiesListState> get copyWith => _$ProxiesListStateCo
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxiesListState&&const DeepCollectionEquality().equals(other.groups, groups)&&const DeepCollectionEquality().equals(other.currentUnfoldSet, currentUnfoldSet)&&(identical(other.proxiesSortType, proxiesSortType) || other.proxiesSortType == proxiesSortType)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.columns, columns) || other.columns == columns));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxiesListState&&const DeepCollectionEquality().equals(other.groups, groups)&&const DeepCollectionEquality().equals(other.currentUnfoldSet, currentUnfoldSet)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.columns, columns) || other.columns == columns));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(groups),const DeepCollectionEquality().hash(currentUnfoldSet),proxiesSortType,proxyCardType,sortNum,columns);
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(groups),const DeepCollectionEquality().hash(currentUnfoldSet),proxyCardType,columns);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProxiesListState(groups: $groups, currentUnfoldSet: $currentUnfoldSet, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, columns: $columns)';
|
||||
return 'ProxiesListState(groups: $groups, currentUnfoldSet: $currentUnfoldSet, proxyCardType: $proxyCardType, columns: $columns)';
|
||||
}
|
||||
|
||||
|
||||
@@ -3013,7 +3013,7 @@ abstract mixin class $ProxiesListStateCopyWith<$Res> {
|
||||
factory $ProxiesListStateCopyWith(ProxiesListState value, $Res Function(ProxiesListState) _then) = _$ProxiesListStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
List<Group> groups, Set<String> currentUnfoldSet, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns
|
||||
List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns
|
||||
});
|
||||
|
||||
|
||||
@@ -3030,14 +3030,12 @@ class _$ProxiesListStateCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of ProxiesListState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? groups = null,Object? currentUnfoldSet = null,Object? proxiesSortType = null,Object? proxyCardType = null,Object? sortNum = null,Object? columns = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? groups = null,Object? currentUnfoldSet = null,Object? proxyCardType = null,Object? columns = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
groups: null == groups ? _self.groups : groups // ignore: cast_nullable_to_non_nullable
|
||||
as List<Group>,currentUnfoldSet: null == currentUnfoldSet ? _self.currentUnfoldSet : currentUnfoldSet // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,proxiesSortType: null == proxiesSortType ? _self.proxiesSortType : proxiesSortType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxiesSortType,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxyCardType,sortNum: null == sortNum ? _self.sortNum : sortNum // ignore: cast_nullable_to_non_nullable
|
||||
as num,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxyCardType,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
@@ -3123,10 +3121,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Group> groups, Set<String> currentUnfoldSet, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ProxiesListState() when $default != null:
|
||||
return $default(_that.groups,_that.currentUnfoldSet,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.columns);case _:
|
||||
return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType,_that.columns);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -3144,10 +3142,10 @@ return $default(_that.groups,_that.currentUnfoldSet,_that.proxiesSortType,_that.
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Group> groups, Set<String> currentUnfoldSet, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ProxiesListState():
|
||||
return $default(_that.groups,_that.currentUnfoldSet,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.columns);case _:
|
||||
return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType,_that.columns);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -3164,10 +3162,10 @@ return $default(_that.groups,_that.currentUnfoldSet,_that.proxiesSortType,_that.
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Group> groups, Set<String> currentUnfoldSet, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ProxiesListState() when $default != null:
|
||||
return $default(_that.groups,_that.currentUnfoldSet,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.columns);case _:
|
||||
return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType,_that.columns);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -3179,7 +3177,7 @@ return $default(_that.groups,_that.currentUnfoldSet,_that.proxiesSortType,_that.
|
||||
|
||||
|
||||
class _ProxiesListState implements ProxiesListState {
|
||||
const _ProxiesListState({required final List<Group> groups, required final Set<String> currentUnfoldSet, required this.proxiesSortType, required this.proxyCardType, required this.sortNum, required this.columns}): _groups = groups,_currentUnfoldSet = currentUnfoldSet;
|
||||
const _ProxiesListState({required final List<Group> groups, required final Set<String> currentUnfoldSet, required this.proxyCardType, required this.columns}): _groups = groups,_currentUnfoldSet = currentUnfoldSet;
|
||||
|
||||
|
||||
final List<Group> _groups;
|
||||
@@ -3196,9 +3194,7 @@ class _ProxiesListState implements ProxiesListState {
|
||||
return EqualUnmodifiableSetView(_currentUnfoldSet);
|
||||
}
|
||||
|
||||
@override final ProxiesSortType proxiesSortType;
|
||||
@override final ProxyCardType proxyCardType;
|
||||
@override final num sortNum;
|
||||
@override final int columns;
|
||||
|
||||
/// Create a copy of ProxiesListState
|
||||
@@ -3211,16 +3207,16 @@ _$ProxiesListStateCopyWith<_ProxiesListState> get copyWith => __$ProxiesListStat
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxiesListState&&const DeepCollectionEquality().equals(other._groups, _groups)&&const DeepCollectionEquality().equals(other._currentUnfoldSet, _currentUnfoldSet)&&(identical(other.proxiesSortType, proxiesSortType) || other.proxiesSortType == proxiesSortType)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.columns, columns) || other.columns == columns));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxiesListState&&const DeepCollectionEquality().equals(other._groups, _groups)&&const DeepCollectionEquality().equals(other._currentUnfoldSet, _currentUnfoldSet)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.columns, columns) || other.columns == columns));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_groups),const DeepCollectionEquality().hash(_currentUnfoldSet),proxiesSortType,proxyCardType,sortNum,columns);
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_groups),const DeepCollectionEquality().hash(_currentUnfoldSet),proxyCardType,columns);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProxiesListState(groups: $groups, currentUnfoldSet: $currentUnfoldSet, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, columns: $columns)';
|
||||
return 'ProxiesListState(groups: $groups, currentUnfoldSet: $currentUnfoldSet, proxyCardType: $proxyCardType, columns: $columns)';
|
||||
}
|
||||
|
||||
|
||||
@@ -3231,7 +3227,7 @@ abstract mixin class _$ProxiesListStateCopyWith<$Res> implements $ProxiesListSta
|
||||
factory _$ProxiesListStateCopyWith(_ProxiesListState value, $Res Function(_ProxiesListState) _then) = __$ProxiesListStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
List<Group> groups, Set<String> currentUnfoldSet, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns
|
||||
List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns
|
||||
});
|
||||
|
||||
|
||||
@@ -3248,14 +3244,12 @@ class __$ProxiesListStateCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of ProxiesListState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? groups = null,Object? currentUnfoldSet = null,Object? proxiesSortType = null,Object? proxyCardType = null,Object? sortNum = null,Object? columns = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? groups = null,Object? currentUnfoldSet = null,Object? proxyCardType = null,Object? columns = null,}) {
|
||||
return _then(_ProxiesListState(
|
||||
groups: null == groups ? _self._groups : groups // ignore: cast_nullable_to_non_nullable
|
||||
as List<Group>,currentUnfoldSet: null == currentUnfoldSet ? _self._currentUnfoldSet : currentUnfoldSet // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,proxiesSortType: null == proxiesSortType ? _self.proxiesSortType : proxiesSortType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxiesSortType,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxyCardType,sortNum: null == sortNum ? _self.sortNum : sortNum // ignore: cast_nullable_to_non_nullable
|
||||
as num,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxyCardType,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
@@ -3266,7 +3260,7 @@ as int,
|
||||
/// @nodoc
|
||||
mixin _$ProxiesTabState {
|
||||
|
||||
List<Group> get groups; String? get currentGroupName; ProxiesSortType get proxiesSortType; ProxyCardType get proxyCardType; num get sortNum; int get columns;
|
||||
List<Group> get groups; String? get currentGroupName; ProxyCardType get proxyCardType; int get columns;
|
||||
/// Create a copy of ProxiesTabState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -3277,16 +3271,16 @@ $ProxiesTabStateCopyWith<ProxiesTabState> get copyWith => _$ProxiesTabStateCopyW
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxiesTabState&&const DeepCollectionEquality().equals(other.groups, groups)&&(identical(other.currentGroupName, currentGroupName) || other.currentGroupName == currentGroupName)&&(identical(other.proxiesSortType, proxiesSortType) || other.proxiesSortType == proxiesSortType)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.columns, columns) || other.columns == columns));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxiesTabState&&const DeepCollectionEquality().equals(other.groups, groups)&&(identical(other.currentGroupName, currentGroupName) || other.currentGroupName == currentGroupName)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.columns, columns) || other.columns == columns));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(groups),currentGroupName,proxiesSortType,proxyCardType,sortNum,columns);
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(groups),currentGroupName,proxyCardType,columns);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProxiesTabState(groups: $groups, currentGroupName: $currentGroupName, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, columns: $columns)';
|
||||
return 'ProxiesTabState(groups: $groups, currentGroupName: $currentGroupName, proxyCardType: $proxyCardType, columns: $columns)';
|
||||
}
|
||||
|
||||
|
||||
@@ -3297,7 +3291,7 @@ abstract mixin class $ProxiesTabStateCopyWith<$Res> {
|
||||
factory $ProxiesTabStateCopyWith(ProxiesTabState value, $Res Function(ProxiesTabState) _then) = _$ProxiesTabStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
List<Group> groups, String? currentGroupName, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns
|
||||
List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns
|
||||
});
|
||||
|
||||
|
||||
@@ -3314,14 +3308,12 @@ class _$ProxiesTabStateCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of ProxiesTabState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? groups = null,Object? currentGroupName = freezed,Object? proxiesSortType = null,Object? proxyCardType = null,Object? sortNum = null,Object? columns = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? groups = null,Object? currentGroupName = freezed,Object? proxyCardType = null,Object? columns = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
groups: null == groups ? _self.groups : groups // ignore: cast_nullable_to_non_nullable
|
||||
as List<Group>,currentGroupName: freezed == currentGroupName ? _self.currentGroupName : currentGroupName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,proxiesSortType: null == proxiesSortType ? _self.proxiesSortType : proxiesSortType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxiesSortType,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxyCardType,sortNum: null == sortNum ? _self.sortNum : sortNum // ignore: cast_nullable_to_non_nullable
|
||||
as num,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
|
||||
as String?,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxyCardType,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
@@ -3407,10 +3399,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Group> groups, String? currentGroupName, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ProxiesTabState() when $default != null:
|
||||
return $default(_that.groups,_that.currentGroupName,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.columns);case _:
|
||||
return $default(_that.groups,_that.currentGroupName,_that.proxyCardType,_that.columns);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -3428,10 +3420,10 @@ return $default(_that.groups,_that.currentGroupName,_that.proxiesSortType,_that.
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Group> groups, String? currentGroupName, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ProxiesTabState():
|
||||
return $default(_that.groups,_that.currentGroupName,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.columns);case _:
|
||||
return $default(_that.groups,_that.currentGroupName,_that.proxyCardType,_that.columns);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -3448,10 +3440,10 @@ return $default(_that.groups,_that.currentGroupName,_that.proxiesSortType,_that.
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Group> groups, String? currentGroupName, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ProxiesTabState() when $default != null:
|
||||
return $default(_that.groups,_that.currentGroupName,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.columns);case _:
|
||||
return $default(_that.groups,_that.currentGroupName,_that.proxyCardType,_that.columns);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -3463,7 +3455,7 @@ return $default(_that.groups,_that.currentGroupName,_that.proxiesSortType,_that.
|
||||
|
||||
|
||||
class _ProxiesTabState implements ProxiesTabState {
|
||||
const _ProxiesTabState({required final List<Group> groups, required this.currentGroupName, required this.proxiesSortType, required this.proxyCardType, required this.sortNum, required this.columns}): _groups = groups;
|
||||
const _ProxiesTabState({required final List<Group> groups, required this.currentGroupName, required this.proxyCardType, required this.columns}): _groups = groups;
|
||||
|
||||
|
||||
final List<Group> _groups;
|
||||
@@ -3474,9 +3466,7 @@ class _ProxiesTabState implements ProxiesTabState {
|
||||
}
|
||||
|
||||
@override final String? currentGroupName;
|
||||
@override final ProxiesSortType proxiesSortType;
|
||||
@override final ProxyCardType proxyCardType;
|
||||
@override final num sortNum;
|
||||
@override final int columns;
|
||||
|
||||
/// Create a copy of ProxiesTabState
|
||||
@@ -3489,16 +3479,16 @@ _$ProxiesTabStateCopyWith<_ProxiesTabState> get copyWith => __$ProxiesTabStateCo
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxiesTabState&&const DeepCollectionEquality().equals(other._groups, _groups)&&(identical(other.currentGroupName, currentGroupName) || other.currentGroupName == currentGroupName)&&(identical(other.proxiesSortType, proxiesSortType) || other.proxiesSortType == proxiesSortType)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.columns, columns) || other.columns == columns));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxiesTabState&&const DeepCollectionEquality().equals(other._groups, _groups)&&(identical(other.currentGroupName, currentGroupName) || other.currentGroupName == currentGroupName)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.columns, columns) || other.columns == columns));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_groups),currentGroupName,proxiesSortType,proxyCardType,sortNum,columns);
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_groups),currentGroupName,proxyCardType,columns);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProxiesTabState(groups: $groups, currentGroupName: $currentGroupName, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, columns: $columns)';
|
||||
return 'ProxiesTabState(groups: $groups, currentGroupName: $currentGroupName, proxyCardType: $proxyCardType, columns: $columns)';
|
||||
}
|
||||
|
||||
|
||||
@@ -3509,7 +3499,7 @@ abstract mixin class _$ProxiesTabStateCopyWith<$Res> implements $ProxiesTabState
|
||||
factory _$ProxiesTabStateCopyWith(_ProxiesTabState value, $Res Function(_ProxiesTabState) _then) = __$ProxiesTabStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
List<Group> groups, String? currentGroupName, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns
|
||||
List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns
|
||||
});
|
||||
|
||||
|
||||
@@ -3526,14 +3516,12 @@ class __$ProxiesTabStateCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of ProxiesTabState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? groups = null,Object? currentGroupName = freezed,Object? proxiesSortType = null,Object? proxyCardType = null,Object? sortNum = null,Object? columns = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? groups = null,Object? currentGroupName = freezed,Object? proxyCardType = null,Object? columns = null,}) {
|
||||
return _then(_ProxiesTabState(
|
||||
groups: null == groups ? _self._groups : groups // ignore: cast_nullable_to_non_nullable
|
||||
as List<Group>,currentGroupName: freezed == currentGroupName ? _self.currentGroupName : currentGroupName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,proxiesSortType: null == proxiesSortType ? _self.proxiesSortType : proxiesSortType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxiesSortType,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxyCardType,sortNum: null == sortNum ? _self.sortNum : sortNum // ignore: cast_nullable_to_non_nullable
|
||||
as num,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
|
||||
as String?,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxyCardType,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
@@ -5733,40 +5721,40 @@ as double,
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProxyCardState {
|
||||
mixin _$SelectedProxyState {
|
||||
|
||||
String get proxyName; String? get testUrl;
|
||||
/// Create a copy of ProxyCardState
|
||||
String get proxyName; bool get group; String? get testUrl;
|
||||
/// Create a copy of SelectedProxyState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$ProxyCardStateCopyWith<ProxyCardState> get copyWith => _$ProxyCardStateCopyWithImpl<ProxyCardState>(this as ProxyCardState, _$identity);
|
||||
$SelectedProxyStateCopyWith<SelectedProxyState> get copyWith => _$SelectedProxyStateCopyWithImpl<SelectedProxyState>(this as SelectedProxyState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxyCardState&&(identical(other.proxyName, proxyName) || other.proxyName == proxyName)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SelectedProxyState&&(identical(other.proxyName, proxyName) || other.proxyName == proxyName)&&(identical(other.group, group) || other.group == group)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,proxyName,testUrl);
|
||||
int get hashCode => Object.hash(runtimeType,proxyName,group,testUrl);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProxyCardState(proxyName: $proxyName, testUrl: $testUrl)';
|
||||
return 'SelectedProxyState(proxyName: $proxyName, group: $group, testUrl: $testUrl)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $ProxyCardStateCopyWith<$Res> {
|
||||
factory $ProxyCardStateCopyWith(ProxyCardState value, $Res Function(ProxyCardState) _then) = _$ProxyCardStateCopyWithImpl;
|
||||
abstract mixin class $SelectedProxyStateCopyWith<$Res> {
|
||||
factory $SelectedProxyStateCopyWith(SelectedProxyState value, $Res Function(SelectedProxyState) _then) = _$SelectedProxyStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String proxyName, String? testUrl
|
||||
String proxyName, bool group, String? testUrl
|
||||
});
|
||||
|
||||
|
||||
@@ -5774,19 +5762,20 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$ProxyCardStateCopyWithImpl<$Res>
|
||||
implements $ProxyCardStateCopyWith<$Res> {
|
||||
_$ProxyCardStateCopyWithImpl(this._self, this._then);
|
||||
class _$SelectedProxyStateCopyWithImpl<$Res>
|
||||
implements $SelectedProxyStateCopyWith<$Res> {
|
||||
_$SelectedProxyStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final ProxyCardState _self;
|
||||
final $Res Function(ProxyCardState) _then;
|
||||
final SelectedProxyState _self;
|
||||
final $Res Function(SelectedProxyState) _then;
|
||||
|
||||
/// Create a copy of ProxyCardState
|
||||
/// Create a copy of SelectedProxyState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? proxyName = null,Object? testUrl = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? proxyName = null,Object? group = null,Object? testUrl = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
proxyName: null == proxyName ? _self.proxyName : proxyName // ignore: cast_nullable_to_non_nullable
|
||||
as String,testUrl: freezed == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,group: null == group ? _self.group : group // ignore: cast_nullable_to_non_nullable
|
||||
as bool,testUrl: freezed == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
@@ -5794,8 +5783,8 @@ as String?,
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [ProxyCardState].
|
||||
extension ProxyCardStatePatterns on ProxyCardState {
|
||||
/// Adds pattern-matching-related methods to [SelectedProxyState].
|
||||
extension SelectedProxyStatePatterns on SelectedProxyState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
@@ -5808,10 +5797,10 @@ extension ProxyCardStatePatterns on ProxyCardState {
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ProxyCardState value)? $default,{required TResult orElse(),}){
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SelectedProxyState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ProxyCardState() when $default != null:
|
||||
case _SelectedProxyState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -5830,10 +5819,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ProxyCardState value) $default,){
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SelectedProxyState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ProxyCardState():
|
||||
case _SelectedProxyState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
@@ -5851,10 +5840,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ProxyCardState value)? $default,){
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SelectedProxyState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ProxyCardState() when $default != null:
|
||||
case _SelectedProxyState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
@@ -5872,10 +5861,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String proxyName, String? testUrl)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String proxyName, bool group, String? testUrl)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ProxyCardState() when $default != null:
|
||||
return $default(_that.proxyName,_that.testUrl);case _:
|
||||
case _SelectedProxyState() when $default != null:
|
||||
return $default(_that.proxyName,_that.group,_that.testUrl);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -5893,10 +5882,10 @@ return $default(_that.proxyName,_that.testUrl);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String proxyName, String? testUrl) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String proxyName, bool group, String? testUrl) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ProxyCardState():
|
||||
return $default(_that.proxyName,_that.testUrl);case _:
|
||||
case _SelectedProxyState():
|
||||
return $default(_that.proxyName,_that.group,_that.testUrl);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -5913,10 +5902,10 @@ return $default(_that.proxyName,_that.testUrl);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String proxyName, String? testUrl)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String proxyName, bool group, String? testUrl)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ProxyCardState() when $default != null:
|
||||
return $default(_that.proxyName,_that.testUrl);case _:
|
||||
case _SelectedProxyState() when $default != null:
|
||||
return $default(_that.proxyName,_that.group,_that.testUrl);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -5927,44 +5916,45 @@ return $default(_that.proxyName,_that.testUrl);case _:
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _ProxyCardState implements ProxyCardState {
|
||||
const _ProxyCardState({required this.proxyName, this.testUrl});
|
||||
class _SelectedProxyState implements SelectedProxyState {
|
||||
const _SelectedProxyState({required this.proxyName, this.group = false, this.testUrl});
|
||||
|
||||
|
||||
@override final String proxyName;
|
||||
@override@JsonKey() final bool group;
|
||||
@override final String? testUrl;
|
||||
|
||||
/// Create a copy of ProxyCardState
|
||||
/// Create a copy of SelectedProxyState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$ProxyCardStateCopyWith<_ProxyCardState> get copyWith => __$ProxyCardStateCopyWithImpl<_ProxyCardState>(this, _$identity);
|
||||
_$SelectedProxyStateCopyWith<_SelectedProxyState> get copyWith => __$SelectedProxyStateCopyWithImpl<_SelectedProxyState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxyCardState&&(identical(other.proxyName, proxyName) || other.proxyName == proxyName)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SelectedProxyState&&(identical(other.proxyName, proxyName) || other.proxyName == proxyName)&&(identical(other.group, group) || other.group == group)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,proxyName,testUrl);
|
||||
int get hashCode => Object.hash(runtimeType,proxyName,group,testUrl);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProxyCardState(proxyName: $proxyName, testUrl: $testUrl)';
|
||||
return 'SelectedProxyState(proxyName: $proxyName, group: $group, testUrl: $testUrl)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$ProxyCardStateCopyWith<$Res> implements $ProxyCardStateCopyWith<$Res> {
|
||||
factory _$ProxyCardStateCopyWith(_ProxyCardState value, $Res Function(_ProxyCardState) _then) = __$ProxyCardStateCopyWithImpl;
|
||||
abstract mixin class _$SelectedProxyStateCopyWith<$Res> implements $SelectedProxyStateCopyWith<$Res> {
|
||||
factory _$SelectedProxyStateCopyWith(_SelectedProxyState value, $Res Function(_SelectedProxyState) _then) = __$SelectedProxyStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String proxyName, String? testUrl
|
||||
String proxyName, bool group, String? testUrl
|
||||
});
|
||||
|
||||
|
||||
@@ -5972,19 +5962,20 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$ProxyCardStateCopyWithImpl<$Res>
|
||||
implements _$ProxyCardStateCopyWith<$Res> {
|
||||
__$ProxyCardStateCopyWithImpl(this._self, this._then);
|
||||
class __$SelectedProxyStateCopyWithImpl<$Res>
|
||||
implements _$SelectedProxyStateCopyWith<$Res> {
|
||||
__$SelectedProxyStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _ProxyCardState _self;
|
||||
final $Res Function(_ProxyCardState) _then;
|
||||
final _SelectedProxyState _self;
|
||||
final $Res Function(_SelectedProxyState) _then;
|
||||
|
||||
/// Create a copy of ProxyCardState
|
||||
/// Create a copy of SelectedProxyState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? proxyName = null,Object? testUrl = freezed,}) {
|
||||
return _then(_ProxyCardState(
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? proxyName = null,Object? group = null,Object? testUrl = freezed,}) {
|
||||
return _then(_SelectedProxyState(
|
||||
proxyName: null == proxyName ? _self.proxyName : proxyName // ignore: cast_nullable_to_non_nullable
|
||||
as String,testUrl: freezed == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,group: null == group ? _self.group : group // ignore: cast_nullable_to_non_nullable
|
||||
as bool,testUrl: freezed == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// ignore_for_file: invalid_annotation_target
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
@@ -102,9 +102,7 @@ abstract class ProxiesListState with _$ProxiesListState {
|
||||
const factory ProxiesListState({
|
||||
required List<Group> groups,
|
||||
required Set<String> currentUnfoldSet,
|
||||
required ProxiesSortType proxiesSortType,
|
||||
required ProxyCardType proxyCardType,
|
||||
required num sortNum,
|
||||
required int columns,
|
||||
}) = _ProxiesListState;
|
||||
}
|
||||
@@ -114,9 +112,7 @@ abstract class ProxiesTabState with _$ProxiesTabState {
|
||||
const factory ProxiesTabState({
|
||||
required List<Group> groups,
|
||||
required String? currentGroupName,
|
||||
required ProxiesSortType proxiesSortType,
|
||||
required ProxyCardType proxyCardType,
|
||||
required num sortNum,
|
||||
required int columns,
|
||||
}) = _ProxiesTabState;
|
||||
}
|
||||
@@ -233,9 +229,12 @@ abstract class DashboardState with _$DashboardState {
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class ProxyCardState with _$ProxyCardState {
|
||||
const factory ProxyCardState({required String proxyName, String? testUrl}) =
|
||||
_ProxyCardState;
|
||||
abstract class SelectedProxyState with _$SelectedProxyState {
|
||||
const factory SelectedProxyState({
|
||||
required String proxyName,
|
||||
@Default(false) bool group,
|
||||
String? testUrl,
|
||||
}) = _SelectedProxyState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
||||
@@ -27,19 +27,15 @@ class HomePage extends StatelessWidget {
|
||||
pageBuilder: (_, index) {
|
||||
final navigationItem = state.navigationItems[index];
|
||||
final navigationView = navigationItem.builder(context);
|
||||
final view = isMobile
|
||||
? KeepScope(
|
||||
keep: navigationItem.keep,
|
||||
child: navigationView,
|
||||
)
|
||||
: KeepScope(
|
||||
keep: navigationItem.keep,
|
||||
child: Navigator(
|
||||
onGenerateRoute: (_) {
|
||||
return CommonRoute(builder: (_) => navigationView);
|
||||
},
|
||||
final view = KeepScope(
|
||||
keep: navigationItem.keep,
|
||||
child: isMobile
|
||||
? navigationView
|
||||
: Navigator(
|
||||
pages: [MaterialPage(child: navigationView)],
|
||||
onDidRemovePage: (_) {},
|
||||
),
|
||||
);
|
||||
);
|
||||
return view;
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:fl_clash/common/app_localizations.dart';
|
||||
@@ -38,45 +39,48 @@ class App {
|
||||
}
|
||||
|
||||
Future<List<Package>> getPackages() async {
|
||||
final packagesString =
|
||||
await methodChannel.invokeMethod<String>('getPackages');
|
||||
final packagesString = await methodChannel.invokeMethod<String>(
|
||||
'getPackages',
|
||||
);
|
||||
return Isolate.run<List<Package>>(() {
|
||||
final List<dynamic> packagesRaw =
|
||||
packagesString != null ? json.decode(packagesString) : [];
|
||||
final List<dynamic> packagesRaw = packagesString != null
|
||||
? json.decode(packagesString)
|
||||
: [];
|
||||
return packagesRaw.map((e) => Package.fromJson(e)).toSet().toList();
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<String>> getChinaPackageNames() async {
|
||||
final packageNamesString =
|
||||
await methodChannel.invokeMethod<String>('getChinaPackageNames');
|
||||
final packageNamesString = await methodChannel.invokeMethod<String>(
|
||||
'getChinaPackageNames',
|
||||
);
|
||||
return Isolate.run<List<String>>(() {
|
||||
final List<dynamic> packageNamesRaw =
|
||||
packageNamesString != null ? json.decode(packageNamesString) : [];
|
||||
final List<dynamic> packageNamesRaw = packageNamesString != null
|
||||
? json.decode(packageNamesString)
|
||||
: [];
|
||||
return packageNamesRaw.map((e) => e.toString()).toList();
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool?> requestNotificationsPermission() async {
|
||||
return await methodChannel
|
||||
.invokeMethod<bool>('requestNotificationsPermission');
|
||||
return await methodChannel.invokeMethod<bool>(
|
||||
'requestNotificationsPermission',
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> openFile(String path) async {
|
||||
return await methodChannel.invokeMethod<bool>('openFile', {
|
||||
'path': path,
|
||||
}) ??
|
||||
return await methodChannel.invokeMethod<bool>('openFile', {'path': path}) ??
|
||||
false;
|
||||
}
|
||||
|
||||
Future<ImageProvider?> getPackageIcon(String packageName) async {
|
||||
final base64 = await methodChannel.invokeMethod<String>('getPackageIcon', {
|
||||
final path = await methodChannel.invokeMethod<String>('getPackageIcon', {
|
||||
'packageName': packageName,
|
||||
});
|
||||
if (base64 == null) {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
return MemoryImage(base64Decode(base64));
|
||||
return FileImage(File(path));
|
||||
}
|
||||
|
||||
Future<bool?> tip(String? message) async {
|
||||
|
||||
@@ -76,16 +76,16 @@ class Service {
|
||||
return await methodChannel.invokeMethod<bool>('stop') ?? false;
|
||||
}
|
||||
|
||||
Future<bool> syncAndroidState(AndroidState state) async {
|
||||
return await methodChannel.invokeMethod<bool>(
|
||||
Future<String> syncAndroidState(AndroidState state) async {
|
||||
return await methodChannel.invokeMethod<String>(
|
||||
'syncState',
|
||||
json.encode(state),
|
||||
) ??
|
||||
false;
|
||||
'';
|
||||
}
|
||||
|
||||
Future<bool> init() async {
|
||||
return await methodChannel.invokeMethod<bool>('init') ?? false;
|
||||
Future<String> init() async {
|
||||
return await methodChannel.invokeMethod<String>('init') ?? '';
|
||||
}
|
||||
|
||||
Future<DateTime?> getRunTime() async {
|
||||
|
||||
@@ -55,7 +55,10 @@ class Requests extends _$Requests with AutoDisposeNotifierMixin {
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class Providers extends _$Providers with AutoDisposeNotifierMixin {
|
||||
class Providers extends _$Providers with AnyNotifierMixin {
|
||||
@override
|
||||
List<ExternalProvider> get value => globalState.appState.providers;
|
||||
|
||||
@override
|
||||
List<ExternalProvider> build() {
|
||||
return globalState.appState.providers;
|
||||
@@ -67,10 +70,13 @@ class Providers extends _$Providers with AutoDisposeNotifierMixin {
|
||||
}
|
||||
|
||||
void setProvider(ExternalProvider? provider) {
|
||||
if (!ref.mounted) {
|
||||
return;
|
||||
}
|
||||
if (provider == null) return;
|
||||
final index = state.indexWhere((item) => item.name == provider.name);
|
||||
final index = value.indexWhere((item) => item.name == provider.name);
|
||||
if (index == -1) return;
|
||||
final newState = List<ExternalProvider>.from(state)..[index] = provider;
|
||||
final newState = List<ExternalProvider>.from(value)..[index] = provider;
|
||||
value = newState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ final class LogsProvider extends $NotifierProvider<Logs, FixedList<Log>> {
|
||||
}
|
||||
}
|
||||
|
||||
String _$logsHash() => r'0a32e067292d449d61af59a689cb26691f4afe44';
|
||||
String _$logsHash() => r'a671cf70f13d38cae75dc51841b651fe2d2dad9a';
|
||||
|
||||
abstract class _$Logs extends $Notifier<FixedList<Log>> {
|
||||
FixedList<Log> build();
|
||||
@@ -143,7 +143,7 @@ final class RequestsProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$requestsHash() => r'526f2c1da1347fd2e6e3e23aac0335fcfe0cb28a';
|
||||
String _$requestsHash() => r'8642621b8b5f2e56f3abb04554c058fb30389795';
|
||||
|
||||
abstract class _$Requests extends $Notifier<FixedList<TrackerInfo>> {
|
||||
FixedList<TrackerInfo> build();
|
||||
@@ -197,7 +197,7 @@ final class ProvidersProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$providersHash() => r'4292240629a99470b2e72426dde3b9049b9b57e0';
|
||||
String _$providersHash() => r'9cb491314be6dca0d9ff2d09aa276d19a92895af';
|
||||
|
||||
abstract class _$Providers extends $Notifier<List<ExternalProvider>> {
|
||||
List<ExternalProvider> build();
|
||||
@@ -304,7 +304,7 @@ final class SystemBrightnessProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$systemBrightnessHash() => r'46eb2d23b05405723efc29480e8f258bf2d8138b';
|
||||
String _$systemBrightnessHash() => r'2fb112459d5f505768f8c33b314aa62cf1fb0a0a';
|
||||
|
||||
abstract class _$SystemBrightness extends $Notifier<Brightness> {
|
||||
Brightness build();
|
||||
@@ -357,7 +357,7 @@ final class TrafficsProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$trafficsHash() => r'8b86eb718fed5776de174c51fd5b231957011fe6';
|
||||
String _$trafficsHash() => r'7df7d01f39e9fa1bf629221c9f73273757fa535a';
|
||||
|
||||
abstract class _$Traffics extends $Notifier<FixedList<Traffic>> {
|
||||
FixedList<Traffic> build();
|
||||
@@ -462,7 +462,7 @@ final class LocalIpProvider extends $NotifierProvider<LocalIp, String?> {
|
||||
}
|
||||
}
|
||||
|
||||
String _$localIpHash() => r'2dd4afdb29db4791ebd80d976f9ea31c62959199';
|
||||
String _$localIpHash() => r'25ff07ff9ae316eac7ef39c29d9ae2714b7ba323';
|
||||
|
||||
abstract class _$LocalIp extends $Notifier<String?> {
|
||||
String? build();
|
||||
@@ -1199,7 +1199,7 @@ final class DelayDataSourceProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$delayDataSourceHash() => r'1b94dcfdb9e1eb4c0b7ca69d933f2299d1f94ed5';
|
||||
String _$delayDataSourceHash() => r'0cc7064c6e7e7a1823df1c5b339001ae49ee54f1';
|
||||
|
||||
abstract class _$DelayDataSource extends $Notifier<DelayMap> {
|
||||
DelayMap build();
|
||||
@@ -1308,7 +1308,7 @@ final class ProfileOverrideStateProvider
|
||||
}
|
||||
|
||||
String _$profileOverrideStateHash() =>
|
||||
r'c57bb59c3900b6ee752d6c1cd5c9c0ccc6d1f45e';
|
||||
r'6bcf739e034cc39623dc63bf304189d63fc19404';
|
||||
|
||||
abstract class _$ProfileOverrideState extends $Notifier<ProfileOverrideModel?> {
|
||||
ProfileOverrideModel? build();
|
||||
@@ -1414,7 +1414,7 @@ final class QueryMapProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$queryMapHash() => r'102d489d31d312f082d7117f80a6de8318eaaf75';
|
||||
String _$queryMapHash() => r'f64a1bf5fcd4f85986d8ba3c956e397abc4f2d5d';
|
||||
|
||||
abstract class _$QueryMap extends $Notifier<Map<QueryTag, String>> {
|
||||
Map<QueryTag, String> build();
|
||||
|
||||
@@ -38,7 +38,7 @@ final class AppSettingProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$appSettingHash() => r'13a93334e18b97f5d52eb3e05bbc7b0b8a5c453e';
|
||||
String _$appSettingHash() => r'7ec7fbf146e690dea42cf854fa4452b2652d8a46';
|
||||
|
||||
abstract class _$AppSetting extends $Notifier<AppSettingProps> {
|
||||
AppSettingProps build();
|
||||
@@ -91,7 +91,7 @@ final class WindowSettingProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$windowSettingHash() => r'9bf31c7e08fab84213f31e249270f9d730bdf711';
|
||||
String _$windowSettingHash() => r'fc0e5c4ec95a57a24e0e656fc2fab6f31add31e7';
|
||||
|
||||
abstract class _$WindowSetting extends $Notifier<WindowProps> {
|
||||
WindowProps build();
|
||||
@@ -143,7 +143,7 @@ final class VpnSettingProvider extends $NotifierProvider<VpnSetting, VpnProps> {
|
||||
}
|
||||
}
|
||||
|
||||
String _$vpnSettingHash() => r'3dae8b56504bfb906aca546c5a5389d79d259a5e';
|
||||
String _$vpnSettingHash() => r'1dad4881ae7bcec76678585ac7b84f820b2ca92b';
|
||||
|
||||
abstract class _$VpnSetting extends $Notifier<VpnProps> {
|
||||
VpnProps build();
|
||||
@@ -196,7 +196,7 @@ final class NetworkSettingProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$networkSettingHash() => r'5a30d4cbfaba94cc29ad08dc1771ebb368b4ba14';
|
||||
String _$networkSettingHash() => r'6ac5959ad478247fd60329221743cccc7a7d010b';
|
||||
|
||||
abstract class _$NetworkSetting extends $Notifier<NetworkProps> {
|
||||
NetworkProps build();
|
||||
@@ -249,7 +249,7 @@ final class ThemeSettingProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$themeSettingHash() => r'0b5620b696d73260d94f63cbfb65857acd2000f0';
|
||||
String _$themeSettingHash() => r'0ddad89cb63fc2b2094dd82262c76d972c2def5c';
|
||||
|
||||
abstract class _$ThemeSetting extends $Notifier<ThemeProps> {
|
||||
ThemeProps build();
|
||||
@@ -302,7 +302,7 @@ final class ProfilesProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$profilesHash() => r'3203cc7de88b91fff86b79c75c2cacd8116fffb7';
|
||||
String _$profilesHash() => r'aad57222a4a0bd16f2c70f9eb8ba0053d1a26d0f';
|
||||
|
||||
abstract class _$Profiles extends $Notifier<List<Profile>> {
|
||||
List<Profile> build();
|
||||
@@ -408,7 +408,7 @@ final class AppDAVSettingProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$appDAVSettingHash() => r'4bf293ac0d1fba157f60df920b7ffd5afefaab26';
|
||||
String _$appDAVSettingHash() => r'fa8de5d89d7a11f34f3f8e20b71cf164e5e11888';
|
||||
|
||||
abstract class _$AppDAVSetting extends $Notifier<DAV?> {
|
||||
DAV? build();
|
||||
@@ -567,7 +567,7 @@ final class ProxiesStyleSettingProvider
|
||||
}
|
||||
|
||||
String _$proxiesStyleSettingHash() =>
|
||||
r'54ebf20a8d4455b2d7a65824f375c4c02a5fba28';
|
||||
r'4ff62951ddc8289220191850516b6751ee69d642';
|
||||
|
||||
abstract class _$ProxiesStyleSetting extends $Notifier<ProxiesStyle> {
|
||||
ProxiesStyle build();
|
||||
@@ -620,7 +620,7 @@ final class ScriptStateProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$scriptStateHash() => r'afbb70d1dd7e577b2377ecd8ab35d0905c1d0e87';
|
||||
String _$scriptStateHash() => r'4770c34c3d24451fef95e372450e4a333b419977';
|
||||
|
||||
abstract class _$ScriptState extends $Notifier<ScriptProps> {
|
||||
ScriptProps build();
|
||||
@@ -673,7 +673,7 @@ final class PatchClashConfigProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$patchClashConfigHash() => r'd9acdd0ace673fc1c1460b63d7a27c5787713c14';
|
||||
String _$patchClashConfigHash() => r'b355bd89969d4d119631fdf117df230a71493fa8';
|
||||
|
||||
abstract class _$PatchClashConfig extends $Notifier<ClashConfig> {
|
||||
ClashConfig build();
|
||||
|
||||
@@ -698,7 +698,7 @@ final class ProxiesListStateProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$proxiesListStateHash() => r'1438dbcaf0d45fbd2cb7fdde81cf1dc06bbd992f';
|
||||
String _$proxiesListStateHash() => r'b16ad96516ece78f6cb22f558a0535000b784317';
|
||||
|
||||
@ProviderFor(proxiesTabState)
|
||||
const proxiesTabStateProvider = ProxiesTabStateProvider._();
|
||||
@@ -740,7 +740,7 @@ final class ProxiesTabStateProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$proxiesTabStateHash() => r'591fd8c2fa0df4c6a4832ffa04eb8d62453ba225';
|
||||
String _$proxiesTabStateHash() => r'143b106d74da618327cbac48af15078efd8cabee';
|
||||
|
||||
@ProviderFor(isStart)
|
||||
const isStartProvider = IsStartProvider._();
|
||||
@@ -1248,7 +1248,7 @@ final class GetDelayProvider extends $FunctionalProvider<int?, int?, int?>
|
||||
}
|
||||
}
|
||||
|
||||
String _$getDelayHash() => r'b5920ac7de0aaadb8ff63fac993bd90ff87cd25a';
|
||||
String _$getDelayHash() => r'15df90fb31665501b21ea671a72e35beaf32141b';
|
||||
|
||||
final class GetDelayFamily extends $Family
|
||||
with
|
||||
@@ -1512,55 +1512,62 @@ final class GetProxiesColumnsProvider extends $FunctionalProvider<int, int, int>
|
||||
|
||||
String _$getProxiesColumnsHash() => r'725066b5fc21f590a4c2656a1fd5e14ab7079079';
|
||||
|
||||
@ProviderFor(getProxyCardState)
|
||||
const getProxyCardStateProvider = GetProxyCardStateFamily._();
|
||||
@ProviderFor(realSelectedProxyState)
|
||||
const realSelectedProxyStateProvider = RealSelectedProxyStateFamily._();
|
||||
|
||||
final class GetProxyCardStateProvider
|
||||
extends $FunctionalProvider<ProxyCardState, ProxyCardState, ProxyCardState>
|
||||
with $Provider<ProxyCardState> {
|
||||
const GetProxyCardStateProvider._({
|
||||
required GetProxyCardStateFamily super.from,
|
||||
final class RealSelectedProxyStateProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
SelectedProxyState,
|
||||
SelectedProxyState,
|
||||
SelectedProxyState
|
||||
>
|
||||
with $Provider<SelectedProxyState> {
|
||||
const RealSelectedProxyStateProvider._({
|
||||
required RealSelectedProxyStateFamily super.from,
|
||||
required String super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'getProxyCardStateProvider',
|
||||
name: r'realSelectedProxyStateProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$getProxyCardStateHash();
|
||||
String debugGetCreateSourceHash() => _$realSelectedProxyStateHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'getProxyCardStateProvider'
|
||||
return r'realSelectedProxyStateProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<ProxyCardState> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
$ProviderElement<SelectedProxyState> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
ProxyCardState create(Ref ref) {
|
||||
SelectedProxyState create(Ref ref) {
|
||||
final argument = this.argument as String;
|
||||
return getProxyCardState(ref, argument);
|
||||
return realSelectedProxyState(ref, argument);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(ProxyCardState value) {
|
||||
Override overrideWithValue(SelectedProxyState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<ProxyCardState>(value),
|
||||
providerOverride: $SyncValueProvider<SelectedProxyState>(value),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is GetProxyCardStateProvider && other.argument == argument;
|
||||
return other is RealSelectedProxyStateProvider &&
|
||||
other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1569,24 +1576,25 @@ final class GetProxyCardStateProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$getProxyCardStateHash() => r'0f131148cb5ed60c9c4c4f31fbe32f114ac346bb';
|
||||
String _$realSelectedProxyStateHash() =>
|
||||
r'42fa131419f0a26e30c4f5269bf020893b7f828c';
|
||||
|
||||
final class GetProxyCardStateFamily extends $Family
|
||||
with $FunctionalFamilyOverride<ProxyCardState, String> {
|
||||
const GetProxyCardStateFamily._()
|
||||
final class RealSelectedProxyStateFamily extends $Family
|
||||
with $FunctionalFamilyOverride<SelectedProxyState, String> {
|
||||
const RealSelectedProxyStateFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'getProxyCardStateProvider',
|
||||
name: r'realSelectedProxyStateProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
GetProxyCardStateProvider call(String proxyName) =>
|
||||
GetProxyCardStateProvider._(argument: proxyName, from: this);
|
||||
RealSelectedProxyStateProvider call(String proxyName) =>
|
||||
RealSelectedProxyStateProvider._(argument: proxyName, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'getProxyCardStateProvider';
|
||||
String toString() => r'realSelectedProxyStateProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(getProxyName)
|
||||
@@ -1801,7 +1809,7 @@ final class GetProxyDescProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$getProxyDescHash() => r'c173fe2393d9c4f5d5d17480e69f9126bb76a17d';
|
||||
String _$getProxyDescHash() => r'4579b55bf7e9fbcfdf91b91619bd0320c585f23d';
|
||||
|
||||
final class GetProxyDescFamily extends $Family
|
||||
with $FunctionalFamilyOverride<String, Proxy> {
|
||||
@@ -2205,6 +2213,55 @@ final class AutoSetSystemDnsStateProvider
|
||||
String _$autoSetSystemDnsStateHash() =>
|
||||
r'2e0976e079100325b1ca797285df48a94c2c066c';
|
||||
|
||||
@ProviderFor(needUpdateGroups)
|
||||
const needUpdateGroupsProvider = NeedUpdateGroupsProvider._();
|
||||
|
||||
final class NeedUpdateGroupsProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
VM3<bool, int, ProxiesSortType>,
|
||||
VM3<bool, int, ProxiesSortType>,
|
||||
VM3<bool, int, ProxiesSortType>
|
||||
>
|
||||
with $Provider<VM3<bool, int, ProxiesSortType>> {
|
||||
const NeedUpdateGroupsProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'needUpdateGroupsProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$needUpdateGroupsHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<VM3<bool, int, ProxiesSortType>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
VM3<bool, int, ProxiesSortType> create(Ref ref) {
|
||||
return needUpdateGroups(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(VM3<bool, int, ProxiesSortType> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<VM3<bool, int, ProxiesSortType>>(
|
||||
value,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$needUpdateGroupsHash() => r'1d1fbf135b4b5d2a2ee984e421ccffe7c4bb0a47';
|
||||
|
||||
@ProviderFor(androidState)
|
||||
const androidStateProvider = AndroidStateProvider._();
|
||||
|
||||
|
||||
@@ -268,20 +268,15 @@ ProxiesListState proxiesListState(Ref ref) {
|
||||
final query = ref.watch(queryProvider(QueryTag.proxies));
|
||||
final currentGroups = ref.watch(filterGroupsStateProvider(query));
|
||||
final currentUnfoldSet = ref.watch(unfoldSetProvider);
|
||||
final vm2 = ref.watch(
|
||||
proxiesStyleSettingProvider.select(
|
||||
(state) => VM2(a: state.sortType, b: state.cardType),
|
||||
),
|
||||
final cardType = ref.watch(
|
||||
proxiesStyleSettingProvider.select((state) => state.cardType),
|
||||
);
|
||||
|
||||
final sortNum = ref.watch(sortNumProvider);
|
||||
final columns = ref.watch(getProxiesColumnsProvider);
|
||||
return ProxiesListState(
|
||||
groups: currentGroups.value,
|
||||
currentUnfoldSet: currentUnfoldSet,
|
||||
proxiesSortType: vm2.a,
|
||||
proxyCardType: vm2.b,
|
||||
sortNum: sortNum,
|
||||
proxyCardType: cardType,
|
||||
columns: columns,
|
||||
);
|
||||
}
|
||||
@@ -293,19 +288,14 @@ ProxiesTabState proxiesTabState(Ref ref) {
|
||||
final currentGroupName = ref.watch(
|
||||
currentProfileProvider.select((state) => state?.currentGroupName),
|
||||
);
|
||||
final vm2 = ref.watch(
|
||||
proxiesStyleSettingProvider.select(
|
||||
(state) => VM2(a: state.sortType, b: state.cardType),
|
||||
),
|
||||
final cardType = ref.watch(
|
||||
proxiesStyleSettingProvider.select((state) => state.cardType),
|
||||
);
|
||||
final sortNum = ref.watch(sortNumProvider);
|
||||
final columns = ref.watch(getProxiesColumnsProvider);
|
||||
return ProxiesTabState(
|
||||
groups: currentGroups.value,
|
||||
currentGroupName: currentGroupName,
|
||||
proxiesSortType: vm2.a,
|
||||
proxyCardType: vm2.b,
|
||||
sortNum: sortNum,
|
||||
proxyCardType: cardType,
|
||||
columns: columns,
|
||||
);
|
||||
}
|
||||
@@ -416,14 +406,14 @@ String getRealTestUrl(Ref ref, [String? testUrl]) {
|
||||
@riverpod
|
||||
int? getDelay(Ref ref, {required String proxyName, String? testUrl}) {
|
||||
final currentTestUrl = ref.watch(getRealTestUrlProvider(testUrl));
|
||||
final proxyCardState = ref.watch(getProxyCardStateProvider(proxyName));
|
||||
final proxyState = ref.watch(realSelectedProxyStateProvider(proxyName));
|
||||
final delay = ref.watch(
|
||||
delayDataSourceProvider.select((state) {
|
||||
final delayMap =
|
||||
state[proxyCardState.testUrl.getSafeValue(currentTestUrl)];
|
||||
return delayMap?[proxyCardState.proxyName];
|
||||
final delayMap = state[proxyState.testUrl.getSafeValue(currentTestUrl)];
|
||||
return delayMap?[proxyState.proxyName];
|
||||
}),
|
||||
);
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
@@ -470,41 +460,14 @@ int getProxiesColumns(Ref ref) {
|
||||
return utils.getProxiesColumns(viewWidth, proxiesLayout);
|
||||
}
|
||||
|
||||
ProxyCardState _getProxyCardState(
|
||||
List<Group> groups,
|
||||
SelectedMap selectedMap,
|
||||
ProxyCardState proxyDelayState,
|
||||
) {
|
||||
if (proxyDelayState.proxyName.isEmpty) return proxyDelayState;
|
||||
final index = groups.indexWhere(
|
||||
(element) => element.name == proxyDelayState.proxyName,
|
||||
);
|
||||
if (index == -1) return proxyDelayState;
|
||||
final group = groups[index];
|
||||
final currentSelectedName = group.getCurrentSelectedName(
|
||||
selectedMap[proxyDelayState.proxyName] ?? '',
|
||||
);
|
||||
if (currentSelectedName.isEmpty) {
|
||||
return proxyDelayState;
|
||||
}
|
||||
return _getProxyCardState(
|
||||
groups,
|
||||
selectedMap,
|
||||
proxyDelayState.copyWith(
|
||||
proxyName: currentSelectedName,
|
||||
testUrl: group.testUrl,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
ProxyCardState getProxyCardState(Ref ref, String proxyName) {
|
||||
SelectedProxyState realSelectedProxyState(Ref ref, String proxyName) {
|
||||
final groups = ref.watch(groupsProvider);
|
||||
final selectedMap = ref.watch(selectedMapProvider);
|
||||
return _getProxyCardState(
|
||||
groups,
|
||||
selectedMap,
|
||||
ProxyCardState(proxyName: proxyName),
|
||||
return computeRealSelectedProxyState(
|
||||
proxyName,
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -534,7 +497,7 @@ String getProxyDesc(Ref ref, Proxy proxy) {
|
||||
final groups = ref.watch(groupsProvider);
|
||||
final index = groups.indexWhere((element) => element.name == proxy.name);
|
||||
if (index == -1) return proxy.type;
|
||||
final state = ref.watch(getProxyCardStateProvider(proxy.name));
|
||||
final state = ref.watch(realSelectedProxyStateProvider(proxy.name));
|
||||
return "${proxy.type}(${state.proxyName.isNotEmpty ? state.proxyName : '*'})";
|
||||
}
|
||||
}
|
||||
@@ -638,6 +601,18 @@ VM2<bool, bool> autoSetSystemDnsState(Ref ref) {
|
||||
return VM2(a: isStart ? realTunEnable : false, b: autoSetSystemDns);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
VM3<bool, int, ProxiesSortType> needUpdateGroups(Ref ref) {
|
||||
final isProxies = ref.watch(
|
||||
currentPageLabelProvider.select((state) => state == PageLabel.proxies),
|
||||
);
|
||||
final sortNum = ref.watch(sortNumProvider);
|
||||
final sortType = ref.watch(
|
||||
proxiesStyleSettingProvider.select((state) => state.sortType),
|
||||
);
|
||||
return VM3(a: isProxies, b: sortNum, c: sortType);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
AndroidState androidState(Ref ref) {
|
||||
final currentProfileName = ref.watch(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi' show Pointer;
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
@@ -16,6 +18,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_js/flutter_js.dart';
|
||||
import 'package:material_color_utilities/palettes/core_palette.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'common/common.dart';
|
||||
@@ -76,8 +79,39 @@ class GlobalState {
|
||||
);
|
||||
await _initDynamicColor();
|
||||
await init();
|
||||
appState = appState.copyWith(coreStatus: CoreStatus.connected);
|
||||
await window?.init(version);
|
||||
_shakingStore();
|
||||
}
|
||||
|
||||
Future<void> _shakingStore() async {
|
||||
final profileIds = config.profiles.map((item) => item.id);
|
||||
final providersRootPath = await appPath.getProvidersRootPath();
|
||||
final profilesRootPath = await appPath.profilesPath;
|
||||
Isolate.run(() async {
|
||||
final profilesDir = Directory(profilesRootPath);
|
||||
final providersDir = Directory(providersRootPath);
|
||||
final List<FileSystemEntity> entities = [];
|
||||
if (await profilesDir.exists()) {
|
||||
entities.addAll(
|
||||
profilesDir.listSync().where(
|
||||
(item) => !item.path.contains('providers'),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (await providersDir.exists()) {
|
||||
entities.addAll(providersDir.listSync());
|
||||
}
|
||||
final deleteFutures = entities.map((entity) async {
|
||||
if (!profileIds.contains(basenameWithoutExtension(entity.path))) {
|
||||
final res = await coreController.deleteFile(entity.path);
|
||||
if (res.isNotEmpty) {
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
await Future.wait(deleteFutures);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _initDynamicColor() async {
|
||||
@@ -98,8 +132,6 @@ class GlobalState {
|
||||
utils.getLocaleForString(config.appSetting.locale) ??
|
||||
WidgetsBinding.instance.platformDispatcher.locale,
|
||||
);
|
||||
await coreController.preload();
|
||||
await service?.syncAndroidState(globalState.getAndroidState());
|
||||
}
|
||||
|
||||
String get ua => config.patchClashConfig.globalUa ?? packageInfo.ua;
|
||||
@@ -199,6 +231,7 @@ class GlobalState {
|
||||
final networkProps = config.networkProps;
|
||||
final port = config.patchClashConfig.mixedPort;
|
||||
return VpnOptions(
|
||||
stack: config.patchClashConfig.tun.stack.name,
|
||||
enable: vpnProps.enable,
|
||||
systemProxy: networkProps.systemProxy,
|
||||
port: port,
|
||||
@@ -253,16 +286,35 @@ class GlobalState {
|
||||
}
|
||||
}
|
||||
|
||||
Future<SetupParams> getSetupParams({required ClashConfig pathConfig}) async {
|
||||
final clashConfig = await patchRawConfig(patchConfig: pathConfig);
|
||||
Future<SetupParams> getSetupParams() async {
|
||||
final params = SetupParams(
|
||||
config: clashConfig,
|
||||
selectedMap: config.currentProfile?.selectedMap ?? {},
|
||||
testUrl: config.appSetting.testUrl,
|
||||
);
|
||||
return params;
|
||||
}
|
||||
|
||||
Future<void> genConfigFile(ClashConfig pathConfig) async {
|
||||
final configFilePath = await appPath.configFilePath;
|
||||
final config = await patchRawConfig(patchConfig: pathConfig);
|
||||
final res = await Isolate.run<String>(() async {
|
||||
try {
|
||||
final res = json.encode(config);
|
||||
final file = File(configFilePath);
|
||||
if (!await file.exists()) {
|
||||
await file.create(recursive: true);
|
||||
}
|
||||
await file.writeAsString(res);
|
||||
return '';
|
||||
} catch (e) {
|
||||
return e.toString();
|
||||
}
|
||||
});
|
||||
if (res.isNotEmpty) {
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
|
||||
AndroidState getAndroidState() {
|
||||
return AndroidState(
|
||||
currentProfileName: config.currentProfile?.label ?? '',
|
||||
@@ -386,7 +438,7 @@ class GlobalState {
|
||||
entry.value.splitByMultipleSeparators;
|
||||
}
|
||||
}
|
||||
var rules = [];
|
||||
List rules = [];
|
||||
if (rawConfig['rules'] != null) {
|
||||
rules = rawConfig['rules'];
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class OutboundModeV2 extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final height = getWidgetHeight(0.72);
|
||||
final height = getWidgetHeight(1);
|
||||
return SizedBox(
|
||||
height: height,
|
||||
child: CommonCard(
|
||||
@@ -110,39 +110,53 @@ class OutboundModeV2 extends StatelessWidget {
|
||||
};
|
||||
return Container(
|
||||
constraints: BoxConstraints.expand(),
|
||||
child: CommonTabBar<Mode>(
|
||||
children: Map.fromEntries(
|
||||
Mode.values.map(
|
||||
(item) => MapEntry(
|
||||
item,
|
||||
Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(),
|
||||
height: height - 16,
|
||||
child: Text(
|
||||
Intl.message(item.name),
|
||||
style: Theme.of(context).textTheme.titleSmall
|
||||
?.adjustSize(1)
|
||||
.copyWith(
|
||||
color: item == mode
|
||||
? _getTextColor(context, item)
|
||||
: null,
|
||||
padding: EdgeInsets.all(4.ap),
|
||||
color: thumbColor.opacity38,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: context.colorScheme.surfaceContainer,
|
||||
),
|
||||
padding: EdgeInsets.all(12.ap),
|
||||
child: LayoutBuilder(
|
||||
builder: (_, constraints) {
|
||||
return CommonTabBar<Mode>(
|
||||
children: Map.fromEntries(
|
||||
Mode.values.map(
|
||||
(item) => MapEntry(
|
||||
item,
|
||||
Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(),
|
||||
height: constraints.maxHeight,
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Text(
|
||||
Intl.message(item.name),
|
||||
style: Theme.of(context).textTheme.titleSmall
|
||||
?.adjustSize(1)
|
||||
.copyWith(
|
||||
color: item == mode
|
||||
? _getTextColor(context, item)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 0),
|
||||
groupValue: mode,
|
||||
onValueChanged: (value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
globalState.appController.changeMode(value);
|
||||
},
|
||||
thumbColor: thumbColor,
|
||||
);
|
||||
},
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
groupValue: mode,
|
||||
onValueChanged: (value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
globalState.appController.changeMode(value);
|
||||
},
|
||||
thumbColor: thumbColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -58,7 +58,7 @@ class _StartButtonState extends ConsumerState<StartButton>
|
||||
|
||||
void updateController() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (isStart) {
|
||||
if (isStart && mounted) {
|
||||
_controller.forward();
|
||||
} else {
|
||||
_controller.reverse();
|
||||
|
||||
@@ -39,6 +39,9 @@ class ProxyCard extends StatelessWidget {
|
||||
getDelayProvider(proxyName: proxy.name, testUrl: testUrl),
|
||||
);
|
||||
return FadeThroughBox(
|
||||
alignment: type == ProxyCardType.expand
|
||||
? Alignment.centerLeft
|
||||
: Alignment.centerRight,
|
||||
child: delay == 0 || delay == null
|
||||
? SizedBox(
|
||||
height: measure.labelSmallHeight,
|
||||
|
||||
@@ -22,13 +22,25 @@ double getItemHeight(ProxyCardType proxyCardType) {
|
||||
|
||||
Future<void> proxyDelayTest(Proxy proxy, [String? testUrl]) async {
|
||||
final appController = globalState.appController;
|
||||
final state = appController.getProxyCardState(proxy.name);
|
||||
final url = state.testUrl.getSafeValue(appController.getRealTestUrl(testUrl));
|
||||
final groups = globalState.appState.groups;
|
||||
final selectedMap = globalState.config.currentProfile?.selectedMap ?? {};
|
||||
final state = computeRealSelectedProxyState(
|
||||
proxy.name,
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
);
|
||||
final currentTestUrl = state.testUrl.getSafeValue(
|
||||
appController.getRealTestUrl(testUrl),
|
||||
);
|
||||
if (state.proxyName.isEmpty) {
|
||||
return;
|
||||
}
|
||||
appController.setDelay(Delay(url: url, name: state.proxyName, value: 0));
|
||||
appController.setDelay(await coreController.getDelay(url, state.proxyName));
|
||||
appController.setDelay(
|
||||
Delay(url: currentTestUrl, name: state.proxyName, value: 0),
|
||||
);
|
||||
appController.setDelay(
|
||||
await coreController.getDelay(currentTestUrl, state.proxyName),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> delayTest(List<Proxy> proxies, [String? testUrl]) async {
|
||||
@@ -36,7 +48,13 @@ Future<void> delayTest(List<Proxy> proxies, [String? testUrl]) async {
|
||||
final proxyNames = proxies.map((proxy) => proxy.name).toSet().toList();
|
||||
|
||||
final delayProxies = proxyNames.map<Future>((proxyName) async {
|
||||
final state = appController.getProxyCardState(proxyName);
|
||||
final groups = globalState.appState.groups;
|
||||
final selectedMap = globalState.config.currentProfile?.selectedMap ?? {};
|
||||
final state = computeRealSelectedProxyState(
|
||||
proxyName,
|
||||
groups: groups,
|
||||
selectedMap: selectedMap,
|
||||
);
|
||||
final url = state.testUrl.getSafeValue(
|
||||
appController.getRealTestUrl(testUrl),
|
||||
);
|
||||
|
||||
@@ -28,7 +28,6 @@ class _ProxiesListViewState extends State<ProxiesListView> {
|
||||
null,
|
||||
);
|
||||
List<double> _headerOffset = [];
|
||||
GroupNameProxiesMap _lastGroupNameProxiesMap = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -84,6 +83,7 @@ class _ProxiesListViewState extends State<ProxiesListView> {
|
||||
final tempUnfoldSet = Set<String>.from(currentUnfoldSet);
|
||||
if (tempUnfoldSet.contains(groupName)) {
|
||||
tempUnfoldSet.remove(groupName);
|
||||
_autoScrollToGroup(groupName);
|
||||
} else {
|
||||
tempUnfoldSet.add(groupName);
|
||||
}
|
||||
@@ -118,10 +118,8 @@ class _ProxiesListViewState extends State<ProxiesListView> {
|
||||
required int columns,
|
||||
required Set<String> currentUnfoldSet,
|
||||
required ProxyCardType cardType,
|
||||
required ProxiesSortType sortType,
|
||||
}) {
|
||||
final items = <Widget>[];
|
||||
final GroupNameProxiesMap groupNameProxiesMap = {};
|
||||
for (final group in groups) {
|
||||
final groupName = group.name;
|
||||
final isExpand = currentUnfoldSet.contains(groupName);
|
||||
@@ -137,25 +135,23 @@ class _ProxiesListViewState extends State<ProxiesListView> {
|
||||
const SizedBox(height: 8),
|
||||
]);
|
||||
if (isExpand) {
|
||||
final sortedProxies = globalState.appController.getSortProxies(
|
||||
proxies: group.all,
|
||||
sortType: sortType,
|
||||
testUrl: group.testUrl,
|
||||
);
|
||||
groupNameProxiesMap[groupName] = sortedProxies;
|
||||
final chunks = sortedProxies.chunks(columns);
|
||||
final proxies = group.all;
|
||||
final chunks = proxies.chunks(columns);
|
||||
final rows = chunks
|
||||
.map<Widget>((proxies) {
|
||||
final children = proxies
|
||||
.map<Widget>(
|
||||
(proxy) => Flexible(
|
||||
child: ProxyCard(
|
||||
testUrl: group.testUrl,
|
||||
type: cardType,
|
||||
groupType: group.type,
|
||||
key: ValueKey('$groupName.${proxy.name}'),
|
||||
proxy: proxy,
|
||||
groupName: groupName,
|
||||
child: SizedBox(
|
||||
height: getItemHeight(cardType),
|
||||
child: ProxyCard(
|
||||
testUrl: group.testUrl,
|
||||
type: cardType,
|
||||
groupType: group.type,
|
||||
key: ValueKey('$groupName.${proxy.name}'),
|
||||
proxy: proxy,
|
||||
groupName: groupName,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -171,7 +167,6 @@ class _ProxiesListViewState extends State<ProxiesListView> {
|
||||
items.addAll([...rows, const SizedBox(height: 8)]);
|
||||
}
|
||||
}
|
||||
_lastGroupNameProxiesMap = groupNameProxiesMap;
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -197,30 +192,44 @@ class _ProxiesListViewState extends State<ProxiesListView> {
|
||||
);
|
||||
}
|
||||
|
||||
void _scrollToGroupSelected(String groupName) {
|
||||
double _getGroupOffset(String groupName) {
|
||||
if (_controller.position.maxScrollExtent == 0) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
final appController = globalState.appController;
|
||||
final currentGroups = appController.getCurrentGroups();
|
||||
final groupNames = currentGroups.map((e) => e.name).toList();
|
||||
final findIndex = groupNames.indexWhere((item) => item == groupName);
|
||||
final index = findIndex != -1 ? findIndex : 0;
|
||||
final currentInitOffset = _headerOffset[index];
|
||||
final proxies = _lastGroupNameProxiesMap[groupName];
|
||||
_controller.animateTo(
|
||||
min(
|
||||
currentInitOffset +
|
||||
8 +
|
||||
getScrollToSelectedOffset(
|
||||
groupName: groupName,
|
||||
proxies: proxies ?? [],
|
||||
),
|
||||
_controller.position.maxScrollExtent,
|
||||
),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeIn,
|
||||
final currentGroups = globalState.appController.getCurrentGroups();
|
||||
final findIndex = currentGroups.indexWhere(
|
||||
(item) => item.name == groupName,
|
||||
);
|
||||
final index = findIndex != -1 ? findIndex : 0;
|
||||
return _headerOffset[index];
|
||||
}
|
||||
|
||||
void _autoScrollToGroup(String groupName) {
|
||||
_controller.jumpTo(_getGroupOffset(groupName));
|
||||
}
|
||||
|
||||
void _scrollToGroupSelected(String groupName) {
|
||||
final currentInitOffset = _getGroupOffset(groupName);
|
||||
final currentGroups = globalState.appController.getCurrentGroups();
|
||||
final proxies = currentGroups.getGroup(groupName)?.all;
|
||||
_jumpTo(
|
||||
currentInitOffset +
|
||||
8 +
|
||||
getScrollToSelectedOffset(
|
||||
groupName: groupName,
|
||||
proxies: proxies ?? [],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _jumpTo(double offset) {
|
||||
if (mounted && _controller.hasClients) {
|
||||
_controller.animateTo(
|
||||
min(offset, _controller.position.maxScrollExtent),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -240,7 +249,6 @@ class _ProxiesListViewState extends State<ProxiesListView> {
|
||||
currentUnfoldSet: state.currentUnfoldSet,
|
||||
columns: state.columns,
|
||||
cardType: state.proxyCardType,
|
||||
sortType: state.proxiesSortType,
|
||||
);
|
||||
final itemsOffset = _getItemHeightList(items, state.proxyCardType);
|
||||
return CommonScrollBar(
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/common.dart';
|
||||
import 'package:fl_clash/models/widget.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/views/proxies/list.dart';
|
||||
import 'package:fl_clash/views/proxies/providers.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
@@ -138,11 +137,6 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> {
|
||||
},
|
||||
fireImmediately: true,
|
||||
);
|
||||
ref.listenManual(currentPageLabelProvider, (prev, next) {
|
||||
if (prev != next) {
|
||||
globalState.appController.updateGroupsDebounce();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -2,15 +2,14 @@ import 'dart:math';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/common.dart';
|
||||
import 'package:fl_clash/models/config.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:riverpod_annotation/experimental/scope.dart';
|
||||
|
||||
import '../../models/common.dart';
|
||||
import 'card.dart';
|
||||
import 'common.dart';
|
||||
|
||||
@@ -61,7 +60,7 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
|
||||
Future<void> delayTestCurrentGroup() async {
|
||||
final currentGroupName = globalState.appController.getCurrentGroupName();
|
||||
final currentState = _keyMap[currentGroupName]?.currentState;
|
||||
await delayTest(currentState?.proxies ?? [], currentState?.testUrl);
|
||||
await delayTest(currentState?.currentProxies ?? [], currentState?.testUrl);
|
||||
}
|
||||
|
||||
Widget _buildMoreButton() {
|
||||
@@ -182,15 +181,11 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
|
||||
final children = groups.map((group) {
|
||||
final key = GlobalObjectKey<_ProxyGroupViewState>(group.name);
|
||||
keyMap[group.name] = key;
|
||||
return KeepScope(
|
||||
keep: true,
|
||||
child: ProxyGroupView(
|
||||
key: key,
|
||||
group: group,
|
||||
columns: state.columns,
|
||||
cardType: state.proxyCardType,
|
||||
sortType: state.proxiesSortType,
|
||||
),
|
||||
return ProxyGroupView(
|
||||
key: key,
|
||||
group: group,
|
||||
columns: state.columns,
|
||||
cardType: state.proxyCardType,
|
||||
);
|
||||
}).toList();
|
||||
_keyMap = keyMap;
|
||||
@@ -252,19 +247,16 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
|
||||
}
|
||||
}
|
||||
|
||||
@Dependencies([proxiesTabState, proxiesTabControllerState])
|
||||
class ProxyGroupView extends ConsumerStatefulWidget {
|
||||
final Group group;
|
||||
final int columns;
|
||||
final ProxyCardType cardType;
|
||||
final ProxiesSortType sortType;
|
||||
|
||||
const ProxyGroupView({
|
||||
super.key,
|
||||
required this.group,
|
||||
required this.columns,
|
||||
required this.cardType,
|
||||
required this.sortType,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -274,7 +266,7 @@ class ProxyGroupView extends ConsumerStatefulWidget {
|
||||
class _ProxyGroupViewState extends ConsumerState<ProxyGroupView> {
|
||||
late final ScrollController _controller;
|
||||
|
||||
List<Proxy> proxies = [];
|
||||
List<Proxy> currentProxies = [];
|
||||
String? testUrl;
|
||||
|
||||
@override
|
||||
@@ -308,7 +300,7 @@ class _ProxyGroupViewState extends ConsumerState<ProxyGroupView> {
|
||||
16 +
|
||||
getScrollToSelectedOffset(
|
||||
groupName: widget.group.name,
|
||||
proxies: proxies,
|
||||
proxies: currentProxies,
|
||||
),
|
||||
_controller.position.maxScrollExtent,
|
||||
),
|
||||
@@ -321,45 +313,36 @@ class _ProxyGroupViewState extends ConsumerState<ProxyGroupView> {
|
||||
Widget build(BuildContext context) {
|
||||
final group = widget.group;
|
||||
final proxies = group.all;
|
||||
final sortedProxies = globalState.appController.getSortProxies(
|
||||
proxies: proxies,
|
||||
sortType: widget.sortType,
|
||||
testUrl: group.testUrl,
|
||||
);
|
||||
this.proxies = sortedProxies;
|
||||
testUrl = group.testUrl;
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: CommonScrollBar(
|
||||
currentProxies = proxies;
|
||||
return CommonScrollBar(
|
||||
controller: _controller,
|
||||
child: GridView.builder(
|
||||
key: _getPageStorageKey(),
|
||||
controller: _controller,
|
||||
child: GridView.builder(
|
||||
key: _getPageStorageKey(),
|
||||
controller: _controller,
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 96,
|
||||
),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: widget.columns,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisExtent: getItemHeight(widget.cardType),
|
||||
),
|
||||
itemCount: sortedProxies.length,
|
||||
itemBuilder: (_, index) {
|
||||
final proxy = sortedProxies[index];
|
||||
return ProxyCard(
|
||||
testUrl: group.testUrl,
|
||||
groupType: group.type,
|
||||
type: widget.cardType,
|
||||
proxy: proxy,
|
||||
groupName: group.name,
|
||||
);
|
||||
},
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 96,
|
||||
),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: widget.columns,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisExtent: getItemHeight(widget.cardType),
|
||||
),
|
||||
itemCount: currentProxies.length,
|
||||
itemBuilder: (_, index) {
|
||||
final proxy = currentProxies[index];
|
||||
return ProxyCard(
|
||||
testUrl: group.testUrl,
|
||||
groupType: group.type,
|
||||
type: widget.cardType,
|
||||
proxy: proxy,
|
||||
groupName: group.name,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class CommonTargetIcon extends StatelessWidget {
|
||||
@@ -17,6 +18,7 @@ class CommonTargetIcon extends StatelessWidget {
|
||||
if (src.isEmpty) {
|
||||
return _defaultIcon();
|
||||
}
|
||||
|
||||
final base64 = src.getBase64;
|
||||
if (base64 != null) {
|
||||
return Image.memory(
|
||||
@@ -27,18 +29,8 @@ class CommonTargetIcon extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}
|
||||
return FutureBuilder(
|
||||
future: DefaultCacheManager().getSingleFile(src),
|
||||
builder: (_, snapshot) {
|
||||
final data = snapshot.data;
|
||||
if (data == null) {
|
||||
return SizedBox();
|
||||
}
|
||||
return src.isSvg
|
||||
? SvgPicture.file(data, errorBuilder: (_, _, _) => _defaultIcon())
|
||||
: Image.file(data, errorBuilder: (_, _, _) => _defaultIcon());
|
||||
},
|
||||
);
|
||||
|
||||
return ImageCacheWidget(src: src, defaultWidget: _defaultIcon());
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -46,3 +38,46 @@ class CommonTargetIcon extends StatelessWidget {
|
||||
return SizedBox(width: size, height: size, child: _buildIcon());
|
||||
}
|
||||
}
|
||||
|
||||
class ImageCacheWidget extends StatefulWidget {
|
||||
final String src;
|
||||
final Widget defaultWidget;
|
||||
|
||||
const ImageCacheWidget({
|
||||
super.key,
|
||||
required this.src,
|
||||
required this.defaultWidget,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ImageCacheWidget> createState() => _ImageCacheWidgetState();
|
||||
}
|
||||
|
||||
class _ImageCacheWidgetState extends State<ImageCacheWidget> {
|
||||
late Future<File> _imageFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_imageFuture = LocalImageCacheManager().getSingleFile(widget.src);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<File>(
|
||||
future: _imageFuture,
|
||||
builder: (context, snapshot) {
|
||||
final data = snapshot.data;
|
||||
if (data == null) {
|
||||
return SizedBox();
|
||||
}
|
||||
return widget.src.isSvg
|
||||
? SvgPicture.file(
|
||||
data,
|
||||
errorBuilder: (_, _, _) => widget.defaultWidget,
|
||||
)
|
||||
: Image.file(data, errorBuilder: (_, _, _) => widget.defaultWidget);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ packages:
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
custom_lint:
|
||||
dependency: "direct dev"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint
|
||||
sha256: "78085fbe842de7c5bef92de811ca81536968dbcbbcdac5c316711add2d15e796"
|
||||
@@ -770,10 +770,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: json_serializable
|
||||
sha256: ce2cf974ccdee13be2a510832d7fba0b94b364e0b0395dee42abaa51b855be27
|
||||
sha256: "3f2913b7c2430afe8ac5afe6fb15c1de4a60af4f630625e6e238f80ba4b80cbd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.10.0"
|
||||
version: "6.11.0"
|
||||
launch_at_startup:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: fl_clash
|
||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||
publish_to: 'none'
|
||||
version: 0.8.88+2025082601
|
||||
version: 0.8.88+2025083102
|
||||
environment:
|
||||
sdk: '>=3.8.0 <4.0.0'
|
||||
|
||||
@@ -73,7 +73,6 @@ dev_dependencies:
|
||||
freezed: ^3.2.0
|
||||
riverpod_generator: ^3.0.0-dev.17
|
||||
riverpod_lint: ^3.0.0-dev.17
|
||||
custom_lint: ^0.8.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
Reference in New Issue
Block a user