Compare commits
1 Commits
v0.8.85-pr
...
v0.8.85-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fbc4170b9 |
1
.github/workflows/build.yaml
vendored
1
.github/workflows/build.yaml
vendored
@@ -68,7 +68,6 @@ jobs:
|
||||
with:
|
||||
channel: ${{ (startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')) && 'master' || 'stable' }}
|
||||
cache: true
|
||||
flutter-version: 3.29.3
|
||||
|
||||
- name: Get Flutter Dependency
|
||||
run: flutter pub get
|
||||
|
||||
98
android/app/build.gradle
Normal file
98
android/app/build.gradle
Normal file
@@ -0,0 +1,98 @@
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
def defStoreFile = file("keystore.jks")
|
||||
def defStorePassword = localProperties.getProperty('storePassword')
|
||||
def defKeyAlias = localProperties.getProperty('keyAlias')
|
||||
def defKeyPassword = localProperties.getProperty('keyPassword')
|
||||
def isRelease = defStoreFile.exists() && defStorePassword != null && defKeyAlias != null && defKeyPassword != null
|
||||
|
||||
android {
|
||||
namespace "com.follow.clash"
|
||||
compileSdk 35
|
||||
ndkVersion = "28.0.13004108"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
if (isRelease) {
|
||||
release {
|
||||
storeFile defStoreFile
|
||||
storePassword defStorePassword
|
||||
keyAlias defKeyAlias
|
||||
keyPassword defKeyPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.follow.clash"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 35
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
applicationIdSuffix '.debug'
|
||||
}
|
||||
release {
|
||||
debuggable false
|
||||
if (isRelease) {
|
||||
signingConfig signingConfigs.release
|
||||
} else {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":core")
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation("com.android.tools.smali:smali-dexlib2:3.0.9") {
|
||||
exclude group: "com.google.guava", module: "guava"
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
val localPropertiesFile = rootProject.file("local.properties")
|
||||
val localProperties = Properties().apply {
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.inputStream().use { load(it) }
|
||||
}
|
||||
}
|
||||
|
||||
val mStoreFile: File = file("keystore.jks")
|
||||
val mStorePassword: String? = localProperties.getProperty("storePassword")
|
||||
val mKeyAlias: String? = localProperties.getProperty("keyAlias")
|
||||
val mKeyPassword: String? = localProperties.getProperty("keyPassword")
|
||||
val isRelease = mStoreFile.exists()
|
||||
&& mStorePassword != null
|
||||
&& mKeyAlias != null
|
||||
&& mKeyPassword != null
|
||||
|
||||
android {
|
||||
namespace = "com.follow.clash"
|
||||
compileSdk = 35
|
||||
ndkVersion = "28.0.13004108"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.follow.clash"
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
if (isRelease) {
|
||||
create("release") {
|
||||
storeFile = mStoreFile
|
||||
storePassword = mStorePassword
|
||||
keyAlias = mKeyAlias
|
||||
keyPassword = mKeyPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
isMinifyEnabled = false
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isDebuggable = false
|
||||
|
||||
signingConfig = if (isRelease) {
|
||||
signingConfigs.getByName("release")
|
||||
} else {
|
||||
signingConfigs.getByName("debug")
|
||||
}
|
||||
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core"))
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
implementation("com.android.tools.smali:smali-dexlib2:3.0.9") {
|
||||
exclude(group = "com.google.guava", module = "guava")
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,6 @@ import com.follow.clash.plugins.VpnPlugin
|
||||
import io.flutter.FlutterInjector
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.embedding.engine.dart.DartExecutor
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
@@ -37,15 +33,6 @@ object GlobalState {
|
||||
return currentEngine?.plugins?.get(AppPlugin::class.java) as AppPlugin?
|
||||
}
|
||||
|
||||
fun syncStatus() {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val status = getCurrentVPNPlugin()?.getStatus() ?: false
|
||||
withContext(Dispatchers.Main){
|
||||
runState.value = if (status) RunState.START else RunState.STOP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getText(text: String): String {
|
||||
return getCurrentAppPlugin()?.getText(text) ?: ""
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ class MainActivity : FlutterActivity() {
|
||||
|
||||
override fun onDestroy() {
|
||||
GlobalState.flutterEngine = null
|
||||
GlobalState.runState.value = RunState.STOP
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,6 @@ data object ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun handleDestroy() {
|
||||
GlobalState.destroyServiceEngine()
|
||||
}
|
||||
|
||||
@@ -200,13 +200,6 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||
timerJob = null
|
||||
}
|
||||
|
||||
|
||||
suspend fun getStatus(): Boolean? {
|
||||
return withContext(Dispatchers.Default) {
|
||||
flutterMethodChannel.awaitResult<Boolean>("status", null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleStartService() {
|
||||
if (flClashService == null) {
|
||||
bindService()
|
||||
@@ -255,9 +248,9 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||
GlobalState.runLock.withLock {
|
||||
if (GlobalState.runState.value == RunState.STOP) return
|
||||
GlobalState.runState.value = RunState.STOP
|
||||
flClashService?.stop()
|
||||
stopForegroundJob()
|
||||
Core.stopTun()
|
||||
flClashService?.stop()
|
||||
GlobalState.handleTryDestroy()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ class FlClashTileService : TileService() {
|
||||
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
GlobalState.syncStatus()
|
||||
GlobalState.runState.value?.let { updateTile(it) }
|
||||
GlobalState.runState.observeForever(observer)
|
||||
}
|
||||
|
||||
33
android/build.gradle
Normal file
33
android/build.gradle
Normal file
@@ -0,0 +1,33 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = "${kotlin_version}"
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$agp_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
project.evaluationDependsOn(':core')
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
@@ -12,6 +12,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
0
android/core/consumer-rules.pro
Normal file
0
android/core/consumer-rules.pro
Normal file
27
android/settings.gradle
Normal file
27
android/settings.gradle
Normal file
@@ -0,0 +1,27 @@
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}
|
||||
settings.ext.flutterSdkPath = flutterSdkPath()
|
||||
|
||||
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "$agp_version" apply false
|
||||
id "org.jetbrains.kotlin.android" version "$kotlin_version" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
include ':core'
|
||||
@@ -1,29 +0,0 @@
|
||||
pluginManagement {
|
||||
val flutterSdkPath = run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.9.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
|
||||
}
|
||||
|
||||
|
||||
include(":app")
|
||||
include(":core")
|
||||
@@ -13,6 +13,7 @@
|
||||
"resourcesDesc": "External resource related info",
|
||||
"trafficUsage": "Traffic usage",
|
||||
"coreInfo": "Core info",
|
||||
"nullCoreInfoDesc": "Unable to obtain core info",
|
||||
"networkSpeed": "Network speed",
|
||||
"outboundMode": "Outbound mode",
|
||||
"networkDetection": "Network detection",
|
||||
@@ -21,6 +22,7 @@
|
||||
"noProxy": "No proxy",
|
||||
"noProxyDesc": "Please create a profile or add a valid profile",
|
||||
"nullProfileDesc": "No profile, Please add a profile",
|
||||
"nullLogsDesc": "No logs",
|
||||
"settings": "Settings",
|
||||
"language": "Language",
|
||||
"defaultText": "Default",
|
||||
@@ -147,6 +149,8 @@
|
||||
"addressHelp": "WebDAV server address",
|
||||
"addressTip": "Please enter a valid WebDAV address",
|
||||
"password": "Password",
|
||||
"passwordTip": "Password cannot be empty",
|
||||
"accountTip": "Account cannot be empty",
|
||||
"checkUpdate": "Check for updates",
|
||||
"discoverNewVersion": "Discover the new version",
|
||||
"checkUpdateError": "The current application is already the latest version",
|
||||
@@ -181,6 +185,8 @@
|
||||
"expirationTime": "Expiration time",
|
||||
"connections": "Connections",
|
||||
"connectionsDesc": "View current connections data",
|
||||
"nullRequestsDesc": "No requests",
|
||||
"nullConnectionsDesc": "No connections",
|
||||
"intranetIP": "Intranet IP",
|
||||
"view": "View",
|
||||
"cut": "Cut",
|
||||
@@ -213,6 +219,7 @@
|
||||
"autoCloseConnectionsDesc": "Auto close connections after change node",
|
||||
"onlyStatisticsProxy": "Only statistics proxy",
|
||||
"onlyStatisticsProxyDesc": "When turned on, only statistics proxy traffic",
|
||||
"deleteProfileTip": "Sure you want to delete the current profile?",
|
||||
"pureBlackMode": "Pure black mode",
|
||||
"keepAliveIntervalDesc": "Tcp keep alive interval",
|
||||
"entries": " entries",
|
||||
@@ -243,6 +250,7 @@
|
||||
"dnsDesc": "Update DNS related settings",
|
||||
"key": "Key",
|
||||
"value": "Value",
|
||||
"notEmpty": "Cannot be empty",
|
||||
"hostsDesc": "Add Hosts",
|
||||
"vpnTip": "Changes take effect after restarting the VPN",
|
||||
"vpnEnableDesc": "Auto routes all system traffic through VpnService",
|
||||
@@ -329,12 +337,15 @@
|
||||
"fileIsUpdate": "The file has been modified. Do you want to save the changes?",
|
||||
"profileHasUpdate": "The profile has been modified. Do you want to disable auto update?",
|
||||
"hasCacheChange": "Do you want to cache the changes?",
|
||||
"nullProxies": "No proxies",
|
||||
"copySuccess": "Copy success",
|
||||
"copyLink": "Copy link",
|
||||
"exportFile": "Export file",
|
||||
"cacheCorrupt": "The cache is corrupt. Do you want to clear it?",
|
||||
"detectionTip": "Relying on third-party api is for reference only",
|
||||
"listen": "Listen",
|
||||
"keyExists": "The current key already exists",
|
||||
"valueExists": "The current value already exists",
|
||||
"undo": "undo",
|
||||
"redo": "redo",
|
||||
"none": "none",
|
||||
@@ -342,21 +353,28 @@
|
||||
"basicConfigDesc": "Modify the basic configuration globally",
|
||||
"selectedCountTitle": "{count} items have been selected",
|
||||
"addRule": "Add rule",
|
||||
"ruleProviderEmptyTip": "Rule provider cannot be empty",
|
||||
"ruleName": "Rule name",
|
||||
"content": "Content",
|
||||
"contentEmptyTip": "Content cannot be empty",
|
||||
"subRule": "Sub rule",
|
||||
"subRuleEmptyTip": "Sub rule content cannot be empty",
|
||||
"ruleTarget": "Rule target",
|
||||
"ruleTargetEmptyTip": "Rule target cannot be empty",
|
||||
"sourceIp": "Source IP",
|
||||
"noResolve": "No resolve IP",
|
||||
"getOriginRules": "Get original rules",
|
||||
"overrideOriginRules": "Override the original rule",
|
||||
"addedOriginRules": "Attach on the original rules",
|
||||
"enableOverride": "Enable override",
|
||||
"deleteRuleTip": "Are you sure you want to delete the selected rule?",
|
||||
"saveChanges": "Do you want to save the changes?",
|
||||
"generalDesc": "Modify general settings",
|
||||
"findProcessModeDesc": "There is a certain performance loss after opening",
|
||||
"tabAnimationDesc": "Effective only in mobile view",
|
||||
"saveTip": "Are you sure you want to save?",
|
||||
"deleteColorTip": "Are you sure you want to delete the current color?",
|
||||
"colorExists": "Current color already exists",
|
||||
"colorSchemes": "Color schemes",
|
||||
"palette": "Palette",
|
||||
"tonalSpotScheme": "TonalSpot",
|
||||
@@ -382,25 +400,5 @@
|
||||
"recoveryStrategy": "Recovery strategy",
|
||||
"recoveryStrategy_override": "Override",
|
||||
"recoveryStrategy_compatible": "Compatible",
|
||||
"logsTest": "Logs test",
|
||||
"emptyTip": "{label} cannot be empty",
|
||||
"urlTip": "{label} must be a url",
|
||||
"numberTip": "{label} must be a number",
|
||||
"interval": "Interval",
|
||||
"existsTip": "Current {label} already exists",
|
||||
"deleteTip": "Are you sure you want to delete the current {label}?",
|
||||
"deleteMultipTip": "Are you sure you want to delete the selected {label}?",
|
||||
"nullTip": "No {label} at the moment",
|
||||
"script": "Script",
|
||||
"color": "Color",
|
||||
"rename": "Rename",
|
||||
"unnamed": "Unnamed",
|
||||
"pleaseEnterScriptName": "Please enter a script name",
|
||||
"overrideInvalidTip": "Does not take effect in script mode",
|
||||
"mixedPort": "Mixed Port",
|
||||
"socksPort": "Socks Port",
|
||||
"redirPort": "Redir Port",
|
||||
"tproxyPort": "Tproxy Port",
|
||||
"portTip": "{label} must be between 1024 and 49151",
|
||||
"portConflictTip": "Please enter a different port"
|
||||
"logsTest": "Logs test"
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"resourcesDesc": "外部リソース関連情報",
|
||||
"trafficUsage": "トラフィック使用量",
|
||||
"coreInfo": "コア情報",
|
||||
"nullCoreInfoDesc": "コア情報を取得できません",
|
||||
"networkSpeed": "ネットワーク速度",
|
||||
"outboundMode": "アウトバウンドモード",
|
||||
"networkDetection": "ネットワーク検出",
|
||||
@@ -21,6 +22,7 @@
|
||||
"noProxy": "プロキシなし",
|
||||
"noProxyDesc": "プロファイルを作成するか、有効なプロファイルを追加してください",
|
||||
"nullProfileDesc": "プロファイルがありません。追加してください",
|
||||
"nullLogsDesc": "ログがありません",
|
||||
"settings": "設定",
|
||||
"language": "言語",
|
||||
"defaultText": "デフォルト",
|
||||
@@ -147,6 +149,8 @@
|
||||
"addressHelp": "WebDAVサーバーアドレス",
|
||||
"addressTip": "有効なWebDAVアドレスを入力",
|
||||
"password": "パスワード",
|
||||
"passwordTip": "パスワードは必須です",
|
||||
"accountTip": "アカウントは必須です",
|
||||
"checkUpdate": "更新を確認",
|
||||
"discoverNewVersion": "新バージョンを発見",
|
||||
"checkUpdateError": "アプリは最新版です",
|
||||
@@ -181,6 +185,8 @@
|
||||
"expirationTime": "有効期限",
|
||||
"connections": "接続",
|
||||
"connectionsDesc": "現在の接続データを表示",
|
||||
"nullRequestsDesc": "リクエストなし",
|
||||
"nullConnectionsDesc": "接続なし",
|
||||
"intranetIP": "イントラネットIP",
|
||||
"view": "表示",
|
||||
"cut": "切り取り",
|
||||
@@ -213,6 +219,7 @@
|
||||
"autoCloseConnectionsDesc": "ノード変更後に接続を自動閉じる",
|
||||
"onlyStatisticsProxy": "プロキシのみ統計",
|
||||
"onlyStatisticsProxyDesc": "有効化するとプロキシトラフィックのみ統計",
|
||||
"deleteProfileTip": "現在のプロファイルを削除しますか?",
|
||||
"pureBlackMode": "純黒モード",
|
||||
"keepAliveIntervalDesc": "TCPキープアライブ間隔",
|
||||
"entries": " エントリ",
|
||||
@@ -243,6 +250,7 @@
|
||||
"dnsDesc": "DNS関連設定の更新",
|
||||
"key": "キー",
|
||||
"value": "値",
|
||||
"notEmpty": "空欄不可",
|
||||
"hostsDesc": "ホストを追加",
|
||||
"vpnTip": "変更はVPN再起動後に有効",
|
||||
"vpnEnableDesc": "VpnService経由で全システムトラフィックをルーティング",
|
||||
@@ -329,12 +337,15 @@
|
||||
"fileIsUpdate": "ファイルが変更されました。保存しますか?",
|
||||
"profileHasUpdate": "プロファイルが変更されました。自動更新を無効化しますか?",
|
||||
"hasCacheChange": "変更をキャッシュしますか?",
|
||||
"nullProxies": "プロキシなし",
|
||||
"copySuccess": "コピー成功",
|
||||
"copyLink": "リンクをコピー",
|
||||
"exportFile": "ファイルをエクスポート",
|
||||
"cacheCorrupt": "キャッシュが破損しています。クリアしますか?",
|
||||
"detectionTip": "サードパーティAPIに依存(参考値)",
|
||||
"listen": "リスン",
|
||||
"keyExists": "現在のキーは既に存在します",
|
||||
"valueExists": "現在の値は既に存在します",
|
||||
"undo": "元に戻す",
|
||||
"redo": "やり直す",
|
||||
"none": "なし",
|
||||
@@ -342,21 +353,28 @@
|
||||
"basicConfigDesc": "基本設定をグローバルに変更",
|
||||
"selectedCountTitle": "{count} 項目が選択されています",
|
||||
"addRule": "ルールを追加",
|
||||
"ruleProviderEmptyTip": "ルールプロバイダーは必須です",
|
||||
"ruleName": "ルール名",
|
||||
"content": "内容",
|
||||
"contentEmptyTip": "内容は必須です",
|
||||
"subRule": "サブルール",
|
||||
"subRuleEmptyTip": "サブルールの内容は必須です",
|
||||
"ruleTarget": "ルール対象",
|
||||
"ruleTargetEmptyTip": "ルール対象は必須です",
|
||||
"sourceIp": "送信元IP",
|
||||
"noResolve": "IPを解決しない",
|
||||
"getOriginRules": "元のルールを取得",
|
||||
"overrideOriginRules": "元のルールを上書き",
|
||||
"addedOriginRules": "元のルールに追加",
|
||||
"enableOverride": "上書きを有効化",
|
||||
"deleteRuleTip": "選択したルールを削除しますか?",
|
||||
"saveChanges": "変更を保存しますか?",
|
||||
"generalDesc": "一般設定を変更",
|
||||
"findProcessModeDesc": "有効化するとパフォーマンスが若干低下します",
|
||||
"tabAnimationDesc": "モバイル表示でのみ有効",
|
||||
"saveTip": "保存してもよろしいですか?",
|
||||
"deleteColorTip": "現在の色を削除しますか?",
|
||||
"colorExists": "この色は既に存在します",
|
||||
"colorSchemes": "カラースキーム",
|
||||
"palette": "パレット",
|
||||
"tonalSpotScheme": "トーンスポット",
|
||||
@@ -383,25 +401,5 @@
|
||||
"recoveryStrategy": "リカバリー戦略",
|
||||
"recoveryStrategy_override": "オーバーライド",
|
||||
"recoveryStrategy_compatible": "互換性",
|
||||
"logsTest": "ログテスト",
|
||||
"emptyTip": "{label}は空欄にできません",
|
||||
"urlTip": "{label}はURLである必要があります",
|
||||
"numberTip": "{label}は数字でなければなりません",
|
||||
"interval": "インターバル",
|
||||
"existsTip": "現在の{label}は既に存在しています",
|
||||
"deleteTip": "現在の{label}を削除してもよろしいですか?",
|
||||
"deleteMultipTip": "選択された{label}を削除してもよろしいですか?",
|
||||
"nullTip": "現在{label}はありません",
|
||||
"script": "スクリプト",
|
||||
"color": "カラー",
|
||||
"rename": "リネーム",
|
||||
"unnamed": "無題",
|
||||
"pleaseEnterScriptName": "スクリプト名を入力してください",
|
||||
"overrideInvalidTip": "スクリプトモードでは有効になりません",
|
||||
"mixedPort": "混合ポート",
|
||||
"socksPort": "Socksポート",
|
||||
"redirPort": "Redirポート",
|
||||
"tproxyPort": "Tproxyポート",
|
||||
"portTip": "{label} は 1024 から 49151 の間でなければなりません",
|
||||
"portConflictTip": "別のポートを入力してください"
|
||||
"logsTest": "ログテスト"
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"resourcesDesc": "Информация, связанная с внешними ресурсами",
|
||||
"trafficUsage": "Использование трафика",
|
||||
"coreInfo": "Информация о ядре",
|
||||
"nullCoreInfoDesc": "Не удалось получить информацию о ядре",
|
||||
"networkSpeed": "Скорость сети",
|
||||
"outboundMode": "Режим исходящего трафика",
|
||||
"networkDetection": "Обнаружение сети",
|
||||
@@ -21,6 +22,7 @@
|
||||
"noProxy": "Нет прокси",
|
||||
"noProxyDesc": "Пожалуйста, создайте профиль или добавьте действительный профиль",
|
||||
"nullProfileDesc": "Нет профиля, пожалуйста, добавьте профиль",
|
||||
"nullLogsDesc": "Нет логов",
|
||||
"settings": "Настройки",
|
||||
"language": "Язык",
|
||||
"defaultText": "По умолчанию",
|
||||
@@ -147,6 +149,8 @@
|
||||
"addressHelp": "Адрес сервера WebDAV",
|
||||
"addressTip": "Пожалуйста, введите действительный адрес WebDAV",
|
||||
"password": "Пароль",
|
||||
"passwordTip": "Пароль не может быть пустым",
|
||||
"accountTip": "Аккаунт не может быть пустым",
|
||||
"checkUpdate": "Проверить обновления",
|
||||
"discoverNewVersion": "Обнаружена новая версия",
|
||||
"checkUpdateError": "Текущее приложение уже является последней версией",
|
||||
@@ -181,6 +185,8 @@
|
||||
"expirationTime": "Время истечения",
|
||||
"connections": "Соединения",
|
||||
"connectionsDesc": "Просмотр текущих данных о соединениях",
|
||||
"nullRequestsDesc": "Нет запросов",
|
||||
"nullConnectionsDesc": "Нет соединений",
|
||||
"intranetIP": "Внутренний IP",
|
||||
"view": "Просмотр",
|
||||
"cut": "Вырезать",
|
||||
@@ -213,6 +219,7 @@
|
||||
"autoCloseConnectionsDesc": "Автоматически закрывать соединения после смены узла",
|
||||
"onlyStatisticsProxy": "Только статистика прокси",
|
||||
"onlyStatisticsProxyDesc": "При включении будет учитываться только трафик прокси",
|
||||
"deleteProfileTip": "Вы уверены, что хотите удалить текущий профиль?",
|
||||
"pureBlackMode": "Чисто черный режим",
|
||||
"keepAliveIntervalDesc": "Интервал поддержания TCP-соединения",
|
||||
"entries": " записей",
|
||||
@@ -243,6 +250,7 @@
|
||||
"dnsDesc": "Обновление настроек, связанных с DNS",
|
||||
"key": "Ключ",
|
||||
"value": "Значение",
|
||||
"notEmpty": "Не может быть пустым",
|
||||
"hostsDesc": "Добавить Hosts",
|
||||
"vpnTip": "Изменения вступят в силу после перезапуска VPN",
|
||||
"vpnEnableDesc": "Автоматически направляет весь системный трафик через VpnService",
|
||||
@@ -329,12 +337,15 @@
|
||||
"fileIsUpdate": "Файл был изменен. Хотите сохранить изменения?",
|
||||
"profileHasUpdate": "Профиль был изменен. Хотите отключить автообновление?",
|
||||
"hasCacheChange": "Хотите сохранить изменения в кэше?",
|
||||
"nullProxies": "Нет прокси",
|
||||
"copySuccess": "Копирование успешно",
|
||||
"copyLink": "Копировать ссылку",
|
||||
"exportFile": "Экспорт файла",
|
||||
"cacheCorrupt": "Кэш поврежден. Хотите очистить его?",
|
||||
"detectionTip": "Опирается на сторонний API, только для справки",
|
||||
"listen": "Слушать",
|
||||
"keyExists": "Текущий ключ уже существует",
|
||||
"valueExists": "Текущее значение уже существует",
|
||||
"undo": "Отменить",
|
||||
"redo": "Повторить",
|
||||
"none": "Нет",
|
||||
@@ -342,21 +353,28 @@
|
||||
"basicConfigDesc": "Глобальное изменение базовых настроек",
|
||||
"selectedCountTitle": "Выбрано {count} элементов",
|
||||
"addRule": "Добавить правило",
|
||||
"ruleProviderEmptyTip": "Поставщик правил не может быть пустым",
|
||||
"ruleName": "Название правила",
|
||||
"content": "Содержание",
|
||||
"contentEmptyTip": "Содержание не может быть пустым",
|
||||
"subRule": "Подправило",
|
||||
"subRuleEmptyTip": "Содержание подправила не может быть пустым",
|
||||
"ruleTarget": "Цель правила",
|
||||
"ruleTargetEmptyTip": "Цель правила не может быть пустой",
|
||||
"sourceIp": "Исходный IP",
|
||||
"noResolve": "Не разрешать IP",
|
||||
"getOriginRules": "Получить оригинальные правила",
|
||||
"overrideOriginRules": "Переопределить оригинальное правило",
|
||||
"addedOriginRules": "Добавить к оригинальным правилам",
|
||||
"enableOverride": "Включить переопределение",
|
||||
"deleteRuleTip": "Вы уверены, что хотите удалить выбранное правило?",
|
||||
"saveChanges": "Сохранить изменения?",
|
||||
"generalDesc": "Изменение общих настроек",
|
||||
"findProcessModeDesc": "При включении возможны небольшие потери производительности",
|
||||
"tabAnimationDesc": "Действительно только в мобильном виде",
|
||||
"saveTip": "Вы уверены, что хотите сохранить?",
|
||||
"deleteColorTip": "Удалить текущий цвет?",
|
||||
"colorExists": "Этот цвет уже существует",
|
||||
"colorSchemes": "Цветовые схемы",
|
||||
"palette": "Палитра",
|
||||
"tonalSpotScheme": "Тональный акцент",
|
||||
@@ -383,25 +401,5 @@
|
||||
"recoveryStrategy": "Стратегия восстановления",
|
||||
"recoveryStrategy_override": "Переопределение",
|
||||
"recoveryStrategy_compatible": "Совместимый",
|
||||
"logsTest": "Тест журналов",
|
||||
"emptyTip": "{label} не может быть пустым",
|
||||
"urlTip": "{label} должен быть URL",
|
||||
"numberTip": "{label} должно быть числом",
|
||||
"interval": "Интервал",
|
||||
"existsTip": "Текущий {label} уже существует",
|
||||
"deleteTip": "Вы уверены, что хотите удалить текущий {label}?",
|
||||
"deleteMultipTip": "Вы уверены, что хотите удалить выбранные {label}?",
|
||||
"nullTip": "Сейчас {label} нет",
|
||||
"script": "Скрипт",
|
||||
"color": "Цвет",
|
||||
"rename": "Переименовать",
|
||||
"unnamed": "Без имени",
|
||||
"pleaseEnterScriptName": "Пожалуйста, введите название скрипта",
|
||||
"overrideInvalidTip": "В скриптовом режиме не действует",
|
||||
"mixedPort": "Смешанный порт",
|
||||
"socksPort": "Socks-порт",
|
||||
"redirPort": "Redir-порт",
|
||||
"tproxyPort": "Tproxy-порт",
|
||||
"portTip": "{label} должен быть числом от 1024 до 49151",
|
||||
"portConflictTip": "Введите другой порт"
|
||||
"logsTest": "Тест журналов"
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"resourcesDesc": "外部资源相关信息",
|
||||
"trafficUsage": "流量统计",
|
||||
"coreInfo": "内核信息",
|
||||
"nullCoreInfoDesc": "无法获取内核信息",
|
||||
"networkSpeed": "网络速度",
|
||||
"outboundMode": "出站模式",
|
||||
"networkDetection": "网络检测",
|
||||
@@ -21,6 +22,7 @@
|
||||
"noProxy": "暂无代理",
|
||||
"noProxyDesc": "请创建配置文件或者添加有效配置文件",
|
||||
"nullProfileDesc": "没有配置文件,请先添加配置文件",
|
||||
"nullLogsDesc": "暂无日志",
|
||||
"settings": "设置",
|
||||
"language": "语言",
|
||||
"defaultText": "默认",
|
||||
@@ -147,6 +149,8 @@
|
||||
"addressHelp": "WebDAV服务器地址",
|
||||
"addressTip": "请输入有效的WebDAV地址",
|
||||
"password": "密码",
|
||||
"passwordTip": "密码不能为空",
|
||||
"accountTip": "账号不能为空",
|
||||
"checkUpdate": "检查更新",
|
||||
"discoverNewVersion": "发现新版本",
|
||||
"checkUpdateError": "当前应用已经是最新版了",
|
||||
@@ -181,6 +185,8 @@
|
||||
"expirationTime": "到期时间",
|
||||
"connections": "连接",
|
||||
"connectionsDesc": "查看当前连接数据",
|
||||
"nullRequestsDesc": "暂无请求",
|
||||
"nullConnectionsDesc": "暂无连接",
|
||||
"intranetIP": "内网 IP",
|
||||
"view": "查看",
|
||||
"cut": "剪切",
|
||||
@@ -213,6 +219,7 @@
|
||||
"autoCloseConnectionsDesc": "切换节点后自动关闭连接",
|
||||
"onlyStatisticsProxy": "仅统计代理",
|
||||
"onlyStatisticsProxyDesc": "开启后,将只统计代理流量",
|
||||
"deleteProfileTip": "确定要删除当前配置吗?",
|
||||
"pureBlackMode": "纯黑模式",
|
||||
"keepAliveIntervalDesc": "TCP保持活动间隔",
|
||||
"entries": "个条目",
|
||||
@@ -243,6 +250,7 @@
|
||||
"dnsDesc": "更新DNS相关设置",
|
||||
"key": "键",
|
||||
"value": "值",
|
||||
"notEmpty": "不能为空",
|
||||
"hostsDesc": "追加Hosts",
|
||||
"vpnTip": "重启VPN后改变生效",
|
||||
"vpnEnableDesc": "通过VpnService自动路由系统所有流量",
|
||||
@@ -329,12 +337,15 @@
|
||||
"fileIsUpdate": "文件有修改,是否保存修改",
|
||||
"profileHasUpdate": "配置文件已经修改,是否关闭自动更新 ",
|
||||
"hasCacheChange": "是否缓存修改",
|
||||
"nullProxies": "暂无代理",
|
||||
"copySuccess": "复制成功",
|
||||
"copyLink": "复制链接",
|
||||
"exportFile": "导出文件",
|
||||
"cacheCorrupt": "缓存已损坏,是否清空?",
|
||||
"detectionTip": "依赖第三方api,仅供参考",
|
||||
"listen": "监听",
|
||||
"keyExists": "当前键已存在",
|
||||
"valueExists": "当前值已存在",
|
||||
"undo": "撤销",
|
||||
"redo": "重做",
|
||||
"none": "无",
|
||||
@@ -342,21 +353,28 @@
|
||||
"basicConfigDesc": "全局修改基本配置",
|
||||
"selectedCountTitle": "已选择 {count} 项",
|
||||
"addRule": "添加规则",
|
||||
"ruleProviderEmptyTip": "规则提供者不能为空",
|
||||
"ruleName": "规则名称",
|
||||
"content": "内容",
|
||||
"contentEmptyTip": "内容不能为空",
|
||||
"subRule": "子规则",
|
||||
"subRuleEmptyTip": "子规则内容不能为空",
|
||||
"ruleTarget": "规则目标",
|
||||
"ruleTargetEmptyTip": "规则目标不能为空",
|
||||
"sourceIp": "源IP",
|
||||
"noResolve": "不解析IP",
|
||||
"getOriginRules": "获取原始规则",
|
||||
"overrideOriginRules": "覆盖原始规则",
|
||||
"addedOriginRules": "附加到原始规则",
|
||||
"enableOverride": "启用覆写",
|
||||
"deleteRuleTip": "确定要删除选中的规则吗?",
|
||||
"saveChanges": "是否保存更改?",
|
||||
"generalDesc": "修改通用设置",
|
||||
"findProcessModeDesc": "开启后会有一定性能损耗",
|
||||
"tabAnimationDesc": "仅在移动视图中有效",
|
||||
"saveTip": "确定要保存吗?",
|
||||
"deleteColorTip": "确定删除当前颜色吗?",
|
||||
"colorExists": "该颜色已存在",
|
||||
"colorSchemes": "配色方案",
|
||||
"palette": "调色板",
|
||||
"tonalSpotScheme": "调性点缀",
|
||||
@@ -383,25 +401,5 @@
|
||||
"recoveryStrategy": "恢复策略",
|
||||
"recoveryStrategy_override": "覆盖",
|
||||
"recoveryStrategy_compatible": "兼容",
|
||||
"logsTest": "日志测试",
|
||||
"emptyTip": "{label}不能为空",
|
||||
"urlTip": "{label}必须为URL",
|
||||
"numberTip": "{label}必须为数字",
|
||||
"interval": "间隔",
|
||||
"existsTip": "{label}当前已存在",
|
||||
"deleteTip": "确定删除当前{label}吗?",
|
||||
"deleteMultipTip": "确定删除选中的{label}吗?",
|
||||
"nullTip": "暂无{label}",
|
||||
"script": "脚本",
|
||||
"color": "颜色",
|
||||
"rename": "重命名",
|
||||
"unnamed": "未命名",
|
||||
"pleaseEnterScriptName": "请输入脚本名称",
|
||||
"overrideInvalidTip": "在脚本模式下不生效",
|
||||
"mixedPort": "混合端口",
|
||||
"socksPort": "Socks端口",
|
||||
"redirPort": "Redir端口",
|
||||
"tproxyPort": "Tproxy端口",
|
||||
"portTip": "{label} 必须在 1024 到 49151 之间",
|
||||
"portConflictTip": "请输入不同的端口"
|
||||
"logsTest": "日志测试"
|
||||
}
|
||||
|
||||
Submodule core/Clash.Meta updated: a77851dd9a...6eac0e5d3d
118
core/action.go
118
core/action.go
@@ -5,17 +5,16 @@ import (
|
||||
)
|
||||
|
||||
type Action struct {
|
||||
Id string `json:"id"`
|
||||
Method Method `json:"method"`
|
||||
Data interface{} `json:"data"`
|
||||
Id string `json:"id"`
|
||||
Method Method `json:"method"`
|
||||
Data interface{} `json:"data"`
|
||||
DefaultValue interface{} `json:"default-value"`
|
||||
}
|
||||
|
||||
type ActionResult struct {
|
||||
Id string `json:"id"`
|
||||
Method Method `json:"method"`
|
||||
Data interface{} `json:"data"`
|
||||
Code int `json:"code"`
|
||||
Port int64
|
||||
}
|
||||
|
||||
func (result ActionResult) Json() ([]byte, error) {
|
||||
@@ -23,117 +22,99 @@ func (result ActionResult) Json() ([]byte, error) {
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (result ActionResult) success(data interface{}) {
|
||||
result.Code = 0
|
||||
result.Data = data
|
||||
result.send()
|
||||
func (action Action) getResult(data interface{}) []byte {
|
||||
resultAction := ActionResult{
|
||||
Id: action.Id,
|
||||
Method: action.Method,
|
||||
Data: data,
|
||||
}
|
||||
res, _ := resultAction.Json()
|
||||
return res
|
||||
}
|
||||
|
||||
func (result ActionResult) error(data interface{}) {
|
||||
result.Code = -1
|
||||
result.Data = data
|
||||
result.send()
|
||||
}
|
||||
|
||||
func handleAction(action *Action, result ActionResult) {
|
||||
func handleAction(action *Action, result func(data interface{})) {
|
||||
switch action.Method {
|
||||
case initClashMethod:
|
||||
paramsString := action.Data.(string)
|
||||
result.success(handleInitClash(paramsString))
|
||||
result(handleInitClash(paramsString))
|
||||
return
|
||||
case getIsInitMethod:
|
||||
result.success(handleGetIsInit())
|
||||
result(handleGetIsInit())
|
||||
return
|
||||
case forceGcMethod:
|
||||
handleForceGc()
|
||||
result.success(true)
|
||||
result(true)
|
||||
return
|
||||
case shutdownMethod:
|
||||
result.success(handleShutdown())
|
||||
result(handleShutdown())
|
||||
return
|
||||
case validateConfigMethod:
|
||||
data := []byte(action.Data.(string))
|
||||
result.success(handleValidateConfig(data))
|
||||
result(handleValidateConfig(data))
|
||||
return
|
||||
case updateConfigMethod:
|
||||
data := []byte(action.Data.(string))
|
||||
result.success(handleUpdateConfig(data))
|
||||
return
|
||||
case setupConfigMethod:
|
||||
data := []byte(action.Data.(string))
|
||||
result.success(handleSetupConfig(data))
|
||||
result(handleUpdateConfig(data))
|
||||
return
|
||||
case getProxiesMethod:
|
||||
result.success(handleGetProxies())
|
||||
result(handleGetProxies())
|
||||
return
|
||||
case changeProxyMethod:
|
||||
data := action.Data.(string)
|
||||
handleChangeProxy(data, func(value string) {
|
||||
result.success(value)
|
||||
result(value)
|
||||
})
|
||||
return
|
||||
case getTrafficMethod:
|
||||
result.success(handleGetTraffic())
|
||||
result(handleGetTraffic())
|
||||
return
|
||||
case getTotalTrafficMethod:
|
||||
result.success(handleGetTotalTraffic())
|
||||
result(handleGetTotalTraffic())
|
||||
return
|
||||
case resetTrafficMethod:
|
||||
handleResetTraffic()
|
||||
result.success(true)
|
||||
result(true)
|
||||
return
|
||||
case asyncTestDelayMethod:
|
||||
data := action.Data.(string)
|
||||
handleAsyncTestDelay(data, func(value string) {
|
||||
result.success(value)
|
||||
result(value)
|
||||
})
|
||||
return
|
||||
case getConnectionsMethod:
|
||||
result.success(handleGetConnections())
|
||||
result(handleGetConnections())
|
||||
return
|
||||
case closeConnectionsMethod:
|
||||
result.success(handleCloseConnections())
|
||||
return
|
||||
case resetConnectionsMethod:
|
||||
result.success(handleResetConnections())
|
||||
return
|
||||
case getConfigMethod:
|
||||
path := action.Data.(string)
|
||||
config, err := handleGetConfig(path)
|
||||
if err != nil {
|
||||
result.error(err)
|
||||
return
|
||||
}
|
||||
result.success(config)
|
||||
result(handleCloseConnections())
|
||||
return
|
||||
case closeConnectionMethod:
|
||||
id := action.Data.(string)
|
||||
result.success(handleCloseConnection(id))
|
||||
result(handleCloseConnection(id))
|
||||
return
|
||||
case getExternalProvidersMethod:
|
||||
result.success(handleGetExternalProviders())
|
||||
result(handleGetExternalProviders())
|
||||
return
|
||||
case getExternalProviderMethod:
|
||||
externalProviderName := action.Data.(string)
|
||||
result.success(handleGetExternalProvider(externalProviderName))
|
||||
result(handleGetExternalProvider(externalProviderName))
|
||||
case updateGeoDataMethod:
|
||||
paramsString := action.Data.(string)
|
||||
var params = map[string]string{}
|
||||
err := json.Unmarshal([]byte(paramsString), ¶ms)
|
||||
if err != nil {
|
||||
result.success(err.Error())
|
||||
result(err.Error())
|
||||
return
|
||||
}
|
||||
geoType := params["geo-type"]
|
||||
geoName := params["geo-name"]
|
||||
handleUpdateGeoData(geoType, geoName, func(value string) {
|
||||
result.success(value)
|
||||
result(value)
|
||||
})
|
||||
return
|
||||
case updateExternalProviderMethod:
|
||||
providerName := action.Data.(string)
|
||||
handleUpdateExternalProvider(providerName, func(value string) {
|
||||
result.success(value)
|
||||
result(value)
|
||||
})
|
||||
return
|
||||
case sideLoadExternalProviderMethod:
|
||||
@@ -141,48 +122,59 @@ func handleAction(action *Action, result ActionResult) {
|
||||
var params = map[string]string{}
|
||||
err := json.Unmarshal([]byte(paramsString), ¶ms)
|
||||
if err != nil {
|
||||
result.success(err.Error())
|
||||
result(err.Error())
|
||||
return
|
||||
}
|
||||
providerName := params["providerName"]
|
||||
data := params["data"]
|
||||
handleSideLoadExternalProvider(providerName, []byte(data), func(value string) {
|
||||
result.success(value)
|
||||
result(value)
|
||||
})
|
||||
return
|
||||
case startLogMethod:
|
||||
handleStartLog()
|
||||
result.success(true)
|
||||
result(true)
|
||||
return
|
||||
case stopLogMethod:
|
||||
handleStopLog()
|
||||
result.success(true)
|
||||
result(true)
|
||||
return
|
||||
case startListenerMethod:
|
||||
result.success(handleStartListener())
|
||||
result(handleStartListener())
|
||||
return
|
||||
case stopListenerMethod:
|
||||
result.success(handleStopListener())
|
||||
result(handleStopListener())
|
||||
return
|
||||
case getCountryCodeMethod:
|
||||
ip := action.Data.(string)
|
||||
handleGetCountryCode(ip, func(value string) {
|
||||
result.success(value)
|
||||
result(value)
|
||||
})
|
||||
return
|
||||
case getMemoryMethod:
|
||||
handleGetMemory(func(value string) {
|
||||
result.success(value)
|
||||
result(value)
|
||||
})
|
||||
return
|
||||
case getProfileMethod:
|
||||
profileId := action.Data.(string)
|
||||
handleGetMemory(func(value string) {
|
||||
result(handleGetProfile(profileId))
|
||||
})
|
||||
return
|
||||
case setStateMethod:
|
||||
data := action.Data.(string)
|
||||
handleSetState(data)
|
||||
result.success(true)
|
||||
result(true)
|
||||
case crashMethod:
|
||||
result.success(true)
|
||||
result(true)
|
||||
handleCrash()
|
||||
default:
|
||||
nextHandle(action, result)
|
||||
handle := nextHandle(action, result)
|
||||
if handle {
|
||||
return
|
||||
} else {
|
||||
result(action.DefaultValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,13 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func Protect(callback unsafe.Pointer, fd int) {
|
||||
func protect(callback unsafe.Pointer, fd int) {
|
||||
if globalCallbacks.protectFunc != nil {
|
||||
C.protect(globalCallbacks.protectFunc, callback, C.int(fd))
|
||||
}
|
||||
}
|
||||
|
||||
func ResolveProcess(callback unsafe.Pointer, protocol int, source, target string, uid int) string {
|
||||
func resolveProcess(callback unsafe.Pointer, protocol int, source, target string, uid int) string {
|
||||
if globalCallbacks.resolveProcessFunc == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
320
core/common.go
320
core/common.go
@@ -1,10 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
b "bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
@@ -22,16 +21,31 @@ import (
|
||||
"github.com/metacubex/mihomo/log"
|
||||
rp "github.com/metacubex/mihomo/rules/provider"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"github.com/samber/lo"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func splitByMultipleSeparators(s string) interface{} {
|
||||
isSeparator := func(r rune) bool {
|
||||
return r == ',' || r == ' ' || r == ';'
|
||||
}
|
||||
|
||||
parts := strings.FieldsFunc(s, isSeparator)
|
||||
if len(parts) > 1 {
|
||||
return parts
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var (
|
||||
currentConfig *config.Config
|
||||
version = 0
|
||||
isRunning = false
|
||||
runLock sync.Mutex
|
||||
mBatch, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
||||
version = 0
|
||||
isRunning = false
|
||||
runLock sync.Mutex
|
||||
ips = []string{"ipwho.is", "api.ip.sb", "ipapi.co", "ipinfo.io"}
|
||||
b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
||||
)
|
||||
|
||||
type ExternalProviders []ExternalProvider
|
||||
@@ -40,6 +54,54 @@ func (a ExternalProviders) Len() int { return len(a) }
|
||||
func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
||||
func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
func readFile(path string) ([]byte, error) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
func getProfilePath(id string) string {
|
||||
return filepath.Join(constant.Path.HomeDir(), "profiles", id+".yaml")
|
||||
}
|
||||
|
||||
func getProfileProvidersPath(id string) string {
|
||||
return filepath.Join(constant.Path.HomeDir(), "providers", id)
|
||||
}
|
||||
|
||||
func getRawConfigWithId(id string) *config.RawConfig {
|
||||
path := getProfilePath(id)
|
||||
bytes, err := readFile(path)
|
||||
if err != nil {
|
||||
return config.DefaultRawConfig()
|
||||
}
|
||||
prof, err := config.UnmarshalRawConfig(bytes)
|
||||
if err != nil {
|
||||
log.Errorln("unmarshalRawConfig error %v", err)
|
||||
return config.DefaultRawConfig()
|
||||
}
|
||||
for _, mapping := range prof.ProxyProvider {
|
||||
value, exist := mapping["path"].(string)
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
|
||||
}
|
||||
for _, mapping := range prof.RuleProvider {
|
||||
value, exist := mapping["path"].(string)
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
|
||||
}
|
||||
return prof
|
||||
}
|
||||
|
||||
func getExternalProvidersRaw() map[string]cp.Provider {
|
||||
eps := make(map[string]cp.Provider)
|
||||
for n, p := range tunnel.Providers() {
|
||||
@@ -104,15 +166,143 @@ func sideUpdateExternalProvider(p cp.Provider, bytes []byte) error {
|
||||
}
|
||||
}
|
||||
|
||||
func updateListeners() {
|
||||
func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig {
|
||||
prof := getRawConfigWithId(profileId)
|
||||
overwriteConfig(prof, cfg)
|
||||
return prof
|
||||
}
|
||||
|
||||
func attachHosts(hosts, patchHosts map[string]any) {
|
||||
for k, v := range patchHosts {
|
||||
if str, ok := v.(string); ok {
|
||||
hosts[k] = splitByMultipleSeparators(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updatePatchDns(dns config.RawDNS) {
|
||||
for pair := dns.NameServerPolicy.Oldest(); pair != nil; pair = pair.Next() {
|
||||
if str, ok := pair.Value.(string); ok {
|
||||
dns.NameServerPolicy.Set(pair.Key, splitByMultipleSeparators(str))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func trimArr(arr []string) (r []string) {
|
||||
for _, e := range arr {
|
||||
r = append(r, strings.Trim(e, " "))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func overrideRules(rules, patchRules []string) []string {
|
||||
target := ""
|
||||
for _, line := range rules {
|
||||
rule := trimArr(strings.Split(line, ","))
|
||||
if len(rule) != 2 {
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(rule[0], "MATCH") {
|
||||
target = rule[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if target == "" {
|
||||
return rules
|
||||
}
|
||||
rulesExt := lo.Map(ips, func(ip string, _ int) string {
|
||||
return fmt.Sprintf("DOMAIN,%s,%s", ip, target)
|
||||
})
|
||||
return append(append(rulesExt, patchRules...), rules...)
|
||||
}
|
||||
|
||||
func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) {
|
||||
targetConfig.ExternalController = patchConfig.ExternalController
|
||||
targetConfig.ExternalUI = ""
|
||||
targetConfig.Interface = ""
|
||||
targetConfig.ExternalUIURL = ""
|
||||
targetConfig.TCPConcurrent = patchConfig.TCPConcurrent
|
||||
targetConfig.UnifiedDelay = patchConfig.UnifiedDelay
|
||||
targetConfig.IPv6 = patchConfig.IPv6
|
||||
targetConfig.LogLevel = patchConfig.LogLevel
|
||||
targetConfig.Port = 0
|
||||
targetConfig.SocksPort = 0
|
||||
targetConfig.KeepAliveInterval = patchConfig.KeepAliveInterval
|
||||
targetConfig.MixedPort = patchConfig.MixedPort
|
||||
targetConfig.FindProcessMode = patchConfig.FindProcessMode
|
||||
targetConfig.AllowLan = patchConfig.AllowLan
|
||||
targetConfig.Mode = patchConfig.Mode
|
||||
targetConfig.Tun.Enable = patchConfig.Tun.Enable
|
||||
targetConfig.Tun.Device = patchConfig.Tun.Device
|
||||
targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
|
||||
targetConfig.Tun.Stack = patchConfig.Tun.Stack
|
||||
targetConfig.Tun.RouteAddress = patchConfig.Tun.RouteAddress
|
||||
targetConfig.GeodataLoader = patchConfig.GeodataLoader
|
||||
targetConfig.Profile.StoreSelected = false
|
||||
targetConfig.GeoXUrl = patchConfig.GeoXUrl
|
||||
targetConfig.GlobalUA = patchConfig.GlobalUA
|
||||
if configParams.TestURL != nil {
|
||||
constant.DefaultTestURL = *configParams.TestURL
|
||||
}
|
||||
for idx := range targetConfig.ProxyGroup {
|
||||
targetConfig.ProxyGroup[idx]["url"] = ""
|
||||
}
|
||||
attachHosts(targetConfig.Hosts, patchConfig.Hosts)
|
||||
if configParams.OverrideDns {
|
||||
updatePatchDns(patchConfig.DNS)
|
||||
targetConfig.DNS = patchConfig.DNS
|
||||
} else {
|
||||
if targetConfig.DNS.Enable == false {
|
||||
targetConfig.DNS.Enable = true
|
||||
}
|
||||
}
|
||||
if configParams.OverrideRule {
|
||||
targetConfig.Rule = overrideRules(patchConfig.Rule, []string{})
|
||||
} else {
|
||||
targetConfig.Rule = overrideRules(targetConfig.Rule, patchConfig.Rule)
|
||||
}
|
||||
}
|
||||
|
||||
func patchConfig() {
|
||||
log.Infoln("[Apply] patch")
|
||||
general := currentConfig.General
|
||||
controller := currentConfig.Controller
|
||||
tls := currentConfig.TLS
|
||||
tunnel.SetSniffing(general.Sniffing)
|
||||
tunnel.SetFindProcessMode(general.FindProcessMode)
|
||||
dialer.SetTcpConcurrent(general.TCPConcurrent)
|
||||
dialer.DefaultInterface.Store(general.Interface)
|
||||
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
||||
tunnel.SetMode(general.Mode)
|
||||
log.SetLevel(general.LogLevel)
|
||||
resolver.DisableIPv6 = !general.IPv6
|
||||
|
||||
route.ReCreateServer(&route.Config{
|
||||
Addr: controller.ExternalController,
|
||||
TLSAddr: controller.ExternalControllerTLS,
|
||||
UnixAddr: controller.ExternalControllerUnix,
|
||||
PipeAddr: controller.ExternalControllerPipe,
|
||||
Secret: controller.Secret,
|
||||
Certificate: tls.Certificate,
|
||||
PrivateKey: tls.PrivateKey,
|
||||
DohServer: controller.ExternalDohServer,
|
||||
IsDebug: false,
|
||||
Cors: route.Cors{
|
||||
AllowOrigins: controller.Cors.AllowOrigins,
|
||||
AllowPrivateNetwork: controller.Cors.AllowPrivateNetwork,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func updateListeners(force bool) {
|
||||
if !isRunning {
|
||||
return
|
||||
}
|
||||
if currentConfig == nil {
|
||||
return
|
||||
}
|
||||
listeners := currentConfig.Listeners
|
||||
general := currentConfig.General
|
||||
listeners := currentConfig.Listeners
|
||||
if force == true {
|
||||
stopListeners()
|
||||
}
|
||||
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
|
||||
listener.SetAllowLan(general.AllowLan)
|
||||
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
|
||||
@@ -136,7 +326,11 @@ func stopListeners() {
|
||||
listener.StopListener()
|
||||
}
|
||||
|
||||
func patchSelectGroup(mapping map[string]string) {
|
||||
func patchSelectGroup() {
|
||||
mapping := configParams.SelectedMap
|
||||
if mapping == nil {
|
||||
return
|
||||
}
|
||||
for name, proxy := range tunnel.ProxiesWithProviders() {
|
||||
outbound, ok := proxy.(*adapter.Proxy)
|
||||
if !ok {
|
||||
@@ -157,102 +351,20 @@ 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{},
|
||||
}
|
||||
}
|
||||
|
||||
func readFile(path string) ([]byte, error) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
func updateConfig(params *UpdateParams) {
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
general := currentConfig.General
|
||||
if params.MixedPort != nil {
|
||||
general.MixedPort = *params.MixedPort
|
||||
}
|
||||
if params.Sniffing != nil {
|
||||
general.Sniffing = *params.Sniffing
|
||||
tunnel.SetSniffing(general.Sniffing)
|
||||
}
|
||||
if params.FindProcessMode != nil {
|
||||
general.FindProcessMode = *params.FindProcessMode
|
||||
tunnel.SetFindProcessMode(general.FindProcessMode)
|
||||
}
|
||||
if params.TCPConcurrent != nil {
|
||||
general.TCPConcurrent = *params.TCPConcurrent
|
||||
dialer.SetTcpConcurrent(general.TCPConcurrent)
|
||||
}
|
||||
if params.Interface != nil {
|
||||
general.Interface = *params.Interface
|
||||
dialer.DefaultInterface.Store(general.Interface)
|
||||
}
|
||||
if params.UnifiedDelay != nil {
|
||||
general.UnifiedDelay = *params.UnifiedDelay
|
||||
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
||||
}
|
||||
if params.Mode != nil {
|
||||
general.Mode = *params.Mode
|
||||
tunnel.SetMode(general.Mode)
|
||||
}
|
||||
if params.LogLevel != nil {
|
||||
general.LogLevel = *params.LogLevel
|
||||
log.SetLevel(general.LogLevel)
|
||||
}
|
||||
if params.IPv6 != nil {
|
||||
general.IPv6 = *params.IPv6
|
||||
resolver.DisableIPv6 = !general.IPv6
|
||||
}
|
||||
if params.ExternalController != nil {
|
||||
currentConfig.Controller.ExternalController = *params.ExternalController
|
||||
route.ReCreateServer(&route.Config{
|
||||
Addr: currentConfig.Controller.ExternalController,
|
||||
})
|
||||
}
|
||||
|
||||
if params.Tun != nil {
|
||||
general.Tun.Enable = params.Tun.Enable
|
||||
general.Tun.AutoRoute = *params.Tun.AutoRoute
|
||||
general.Tun.Device = *params.Tun.Device
|
||||
general.Tun.RouteAddress = *params.Tun.RouteAddress
|
||||
general.Tun.DNSHijack = *params.Tun.DNSHijack
|
||||
general.Tun.Stack = *params.Tun.Stack
|
||||
}
|
||||
|
||||
updateListeners()
|
||||
}
|
||||
|
||||
func setupConfig(params *SetupParams) error {
|
||||
func applyConfig(rawConfig *config.RawConfig) error {
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
var err error
|
||||
constant.DefaultTestURL = params.TestURL
|
||||
currentConfig, err = config.ParseRawConfig(params.Config)
|
||||
currentConfig, err = config.ParseRawConfig(rawConfig)
|
||||
if err != nil {
|
||||
currentConfig, _ = config.ParseRawConfig(config.DefaultRawConfig())
|
||||
}
|
||||
hub.ApplyConfig(currentConfig)
|
||||
patchSelectGroup(params.SelectedMap)
|
||||
updateListeners()
|
||||
return err
|
||||
}
|
||||
|
||||
func UnmarshalJson(data []byte, v any) error {
|
||||
decoder := json.NewDecoder(b.NewReader(data))
|
||||
decoder.UseNumber()
|
||||
err := decoder.Decode(v)
|
||||
if configParams.IsPatch {
|
||||
patchConfig()
|
||||
} else {
|
||||
hub.ApplyConfig(currentConfig)
|
||||
patchSelectGroup()
|
||||
}
|
||||
updateListeners(false)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,12 +3,7 @@ package main
|
||||
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"
|
||||
"net/netip"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -17,34 +12,19 @@ type InitParams struct {
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
type SetupParams struct {
|
||||
Config *config.RawConfig `json:"config"`
|
||||
SelectedMap map[string]string `json:"selected-map"`
|
||||
TestURL string `json:"test-url"`
|
||||
type ConfigExtendedParams struct {
|
||||
IsPatch bool `json:"is-patch"`
|
||||
IsCompatible bool `json:"is-compatible"`
|
||||
SelectedMap map[string]string `json:"selected-map"`
|
||||
TestURL *string `json:"test-url"`
|
||||
OverrideDns bool `json:"override-dns"`
|
||||
OverrideRule bool `json:"override-rule"`
|
||||
}
|
||||
|
||||
type UpdateParams struct {
|
||||
Tun *tunSchema `json:"tun"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
MixedPort *int `json:"mixed-port"`
|
||||
FindProcessMode *P.FindProcessMode `json:"find-process-mode"`
|
||||
Mode *tunnel.TunnelMode `json:"mode"`
|
||||
LogLevel *log.LogLevel `json:"log-level"`
|
||||
IPv6 *bool `json:"ipv6"`
|
||||
Sniffing *bool `json:"sniffing"`
|
||||
TCPConcurrent *bool `json:"tcp-concurrent"`
|
||||
ExternalController *string `json:"external-controller"`
|
||||
Interface *string `json:"interface-name"`
|
||||
UnifiedDelay *bool `json:"unified-delay"`
|
||||
}
|
||||
|
||||
type tunSchema struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Device *string `yaml:"device" json:"device"`
|
||||
Stack *constant.TUNStack `yaml:"stack" json:"stack"`
|
||||
DNSHijack *[]string `yaml:"dns-hijack" json:"dns-hijack"`
|
||||
AutoRoute *bool `yaml:"auto-route" json:"auto-route"`
|
||||
RouteAddress *[]netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
|
||||
type GenerateConfigParams struct {
|
||||
ProfileId string `json:"profile-id"`
|
||||
Config config.RawConfig `json:"config" `
|
||||
Params ConfigExtendedParams `json:"params"`
|
||||
}
|
||||
|
||||
type ChangeProxyParams struct {
|
||||
@@ -84,7 +64,6 @@ const (
|
||||
asyncTestDelayMethod Method = "asyncTestDelay"
|
||||
getConnectionsMethod Method = "getConnections"
|
||||
closeConnectionsMethod Method = "closeConnections"
|
||||
resetConnectionsMethod Method = "resetConnectionsMethod"
|
||||
closeConnectionMethod Method = "closeConnection"
|
||||
getExternalProvidersMethod Method = "getExternalProviders"
|
||||
getExternalProviderMethod Method = "getExternalProvider"
|
||||
@@ -102,9 +81,8 @@ const (
|
||||
getAndroidVpnOptionsMethod Method = "getAndroidVpnOptions"
|
||||
getRunTimeMethod Method = "getRunTime"
|
||||
getCurrentProfileNameMethod Method = "getCurrentProfileName"
|
||||
getProfileMethod Method = "getProfile"
|
||||
crashMethod Method = "crash"
|
||||
setupConfigMethod Method = "setupConfig"
|
||||
getConfigMethod Method = "getConfig"
|
||||
)
|
||||
|
||||
type Method string
|
||||
|
||||
43
core/go.mod
43
core/go.mod
@@ -6,13 +6,13 @@ replace github.com/metacubex/mihomo => ./Clash.Meta
|
||||
|
||||
require (
|
||||
github.com/metacubex/mihomo v0.0.0-00010101000000-000000000000
|
||||
github.com/samber/lo v1.50.0
|
||||
github.com/samber/lo v1.49.1
|
||||
golang.org/x/sync v0.11.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/3andne/restls-client-go v0.1.6 // indirect
|
||||
github.com/RyuaNerin/go-krypto v1.3.0 // indirect
|
||||
github.com/RyuaNerin/go-krypto v1.2.4 // indirect
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
@@ -21,13 +21,13 @@ require (
|
||||
github.com/cloudflare/circl v1.3.7 // 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-0.20250507171810-1638563e3615 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/enfein/mieru/v3 v3.13.0 // indirect
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
||||
github.com/go-chi/render v1.0.3 // indirect
|
||||
@@ -36,7 +36,7 @@ require (
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.4.0 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.3.2 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.3.1 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
@@ -54,24 +54,19 @@ require (
|
||||
github.com/metacubex/bart v0.19.0 // indirect
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // indirect
|
||||
github.com/metacubex/chacha v0.1.2 // 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.51.1-0.20250511032541-4e34341cf18b // indirect
|
||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 // indirect
|
||||
github.com/metacubex/randv2 v0.2.0 // indirect
|
||||
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 // indirect
|
||||
github.com/metacubex/sing-mux v0.3.2 // indirect
|
||||
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2 // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.9 // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.3 // indirect
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 // indirect
|
||||
github.com/metacubex/sing-vmess v0.2.1 // 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-20250503140532-decbcfccbfdf // indirect
|
||||
github.com/metacubex/utls v1.7.0-alpha.3 // indirect
|
||||
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8 // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5 // indirect
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 // indirect
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 // indirect
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect
|
||||
github.com/metacubex/utls v1.7.0-alpha.1 // indirect
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
|
||||
github.com/miekg/dns v1.1.63 // indirect
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
|
||||
@@ -83,8 +78,14 @@ require (
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/sagernet/cors v1.2.1 // indirect
|
||||
github.com/sagernet/fswatch v0.1.1 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
github.com/sagernet/sing v0.5.2 // indirect
|
||||
github.com/sagernet/sing-mux v0.2.1 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
|
||||
|
||||
99
core/go.sum
99
core/go.sum
@@ -1,8 +1,8 @@
|
||||
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=
|
||||
github.com/RyuaNerin/elliptic2 v1.0.0/go.mod h1:wWB8fWrJI/6EPJkyV/r1Rj0hxUgrusmqSj8JN6yNf/A=
|
||||
github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4b4Go=
|
||||
github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8=
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
@@ -26,12 +26,12 @@ 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-0.20250507171810-1638563e3615 h1:W7mpP4uiOAbBOdDnRXT9EUdauFv7bz+ERT5rPIord00=
|
||||
github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98=
|
||||
github.com/enfein/mieru/v3 v3.13.0/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/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
|
||||
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
|
||||
@@ -39,8 +39,8 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBE
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
@@ -59,8 +59,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
|
||||
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gofrs/uuid/v5 v5.3.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0=
|
||||
github.com/gofrs/uuid/v5 v5.3.1/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
@@ -103,43 +103,32 @@ github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFb
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
||||
github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc=
|
||||
github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
|
||||
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
|
||||
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.51.1-0.20250511032541-4e34341cf18b h1:8oDU32eJ+RRhl1FodGgPfxQxtoBAiD9D40XG2XtU/SE=
|
||||
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b/go.mod h1:9R1NOzCgTcWsdWvOMlmtMuF0uKzuOpsfvEf7U3I8zM0=
|
||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 h1:B+AP/Pj2/jBDS/kCYjz/x+0BCOKfd2VODYevyeIt+Ds=
|
||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY=
|
||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 h1:m4nSxvw46JEgxMzzmnXams+ebwabcry4Ydep/zNiesQ=
|
||||
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/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-quic v0.0.0-20250511034158-b46e0e3e81b2 h1:wfmYgtECbEYo1slMtyo+2kMqscYYDSjU/TVgS3018F4=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2/go.mod h1:P1kd57U6XXmXv9PbwWdznUGT0k9bKgFJXF0fEORbIlk=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.3/go.mod h1:/WNy/Q8ahLCoPRriWuFZFD0Jy+JNp1MEQl28Zw6SaF8=
|
||||
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.6-0.20250503065609-efb9f0beb6f6 h1:TAwL91XPa6x1QK55CRm+VTzPvLPUfEr/uFDnOZArqEU=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
|
||||
github.com/metacubex/sing-vmess v0.2.1 h1:I6gM3VUjtvJ15D805EUbNH+SRBuqzJeFnuIbKYUsWZ0=
|
||||
github.com/metacubex/sing-vmess v0.2.1/go.mod h1:DsODWItJtOMZNna8Qhheg8r3tUivrcO3vWgaTYKnfTo=
|
||||
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-20250503140532-decbcfccbfdf h1:LwID1wz4tzypidd412dd4dC1H0m1TgRCQ/XvRvMJDFM=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250503140532-decbcfccbfdf/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.7.0-alpha.3 h1:cp1cEMUnoifiWrGHRzo+nCwPRveN9yPD8QaRFmfcYxA=
|
||||
github.com/metacubex/utls v1.7.0-alpha.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c h1:OB3WmMA8YPJjE36RjD9X8xlrWGJ4orxbf2R/KAE28b0=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 h1:vy/8ZYYtWUXYnOnw/NF8ThG1W/RqM/h5rkun+OXZMH0=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63/go.mod h1:eDZ2JpkSkewGmUlCoLSn2MRFn1D0jKPIys/6aogFx7U=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5 h1:hcsz5e5lqhBxn3iQQDIF60FLZ8PQT542GTQZ+1wcIGo=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.7.0-alpha.1 h1:oMFsPh2oTlALJ7vKXPJuqgy0YeiZ+q/LLw+ZdxZ80l4=
|
||||
github.com/metacubex/utls v1.7.0-alpha.1/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
|
||||
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/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
@@ -170,12 +159,25 @@ github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
|
||||
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||
github.com/sagernet/sing v0.5.2 h1:2OZQJNKGtji/66QLxbf/T/dqtK/3+fF/zuHH9tsGK7M=
|
||||
github.com/sagernet/sing v0.5.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo=
|
||||
github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
||||
@@ -187,16 +189,9 @@ github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
@@ -256,7 +251,7 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
|
||||
94
core/hub.go
94
core/hub.go
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/metacubex/mihomo/common/observable"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/mmdb"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/component/updater"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
@@ -29,8 +28,10 @@ import (
|
||||
|
||||
var (
|
||||
isInit = false
|
||||
configParams = ConfigExtendedParams{}
|
||||
externalProviders = map[string]cp.Provider{}
|
||||
logSubscriber observable.Subscription[log.Event]
|
||||
currentConfig *config.Config
|
||||
)
|
||||
|
||||
func handleInitClash(paramsString string) bool {
|
||||
@@ -51,8 +52,8 @@ func handleStartListener() bool {
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
isRunning = true
|
||||
updateListeners()
|
||||
resolver.ResetConnection()
|
||||
updateListeners(true)
|
||||
closeConnections()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -91,10 +92,30 @@ func handleValidateConfig(bytes []byte) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func handleGetProxies() map[string]constant.Proxy {
|
||||
func handleUpdateConfig(bytes []byte) string {
|
||||
var params = &GenerateConfigParams{}
|
||||
err := json.Unmarshal(bytes, params)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
configParams = params.Params
|
||||
prof := decorationConfig(params.ProfileId, params.Config)
|
||||
err = applyConfig(prof)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func handleGetProxies() string {
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
return tunnel.ProxiesWithProviders()
|
||||
data, err := json.Marshal(tunnel.ProxiesWithProviders())
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func handleChangeProxy(data string, fn func(string string)) {
|
||||
@@ -164,12 +185,21 @@ func handleGetTotalTraffic() string {
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func handleGetProfile(profileId string) string {
|
||||
prof := getRawConfigWithId(profileId)
|
||||
data, err := json.Marshal(prof)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func handleResetTraffic() {
|
||||
statistic.DefaultManager.ResetStatistic()
|
||||
}
|
||||
|
||||
func handleAsyncTestDelay(paramsString string, fn func(string)) {
|
||||
mBatch.Go(paramsString, func() (bool, error) {
|
||||
b.Go(paramsString, func() (bool, error) {
|
||||
var params = &TestDelayParams{}
|
||||
err := json.Unmarshal([]byte(paramsString), params)
|
||||
if err != nil {
|
||||
@@ -237,7 +267,13 @@ func handleGetConnections() string {
|
||||
func handleCloseConnections() bool {
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
closeConnections()
|
||||
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||
err := c.Close()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -251,13 +287,6 @@ func closeConnections() {
|
||||
})
|
||||
}
|
||||
|
||||
func handleResetConnections() bool {
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
resolver.ResetConnection()
|
||||
return true
|
||||
}
|
||||
|
||||
func handleCloseConnection(connectionId string) bool {
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
@@ -424,47 +453,10 @@ func handleSetState(params string) {
|
||||
_ = json.Unmarshal([]byte(params), state.CurrentState)
|
||||
}
|
||||
|
||||
func handleGetConfig(path string) (*config.RawConfig, error) {
|
||||
bytes, err := readFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prof, err := config.UnmarshalRawConfig(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return prof, nil
|
||||
}
|
||||
|
||||
func handleCrash() {
|
||||
panic("handle invoke crash")
|
||||
}
|
||||
|
||||
func handleUpdateConfig(bytes []byte) string {
|
||||
var params = &UpdateParams{}
|
||||
err := json.Unmarshal(bytes, params)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
updateConfig(params)
|
||||
return ""
|
||||
}
|
||||
|
||||
func handleSetupConfig(bytes []byte) string {
|
||||
var params = defaultSetupParams()
|
||||
err := UnmarshalJson(bytes, params)
|
||||
if err != nil {
|
||||
log.Errorln("unmarshalRawConfig error %v", err)
|
||||
_ = setupConfig(defaultSetupParams())
|
||||
return err.Error()
|
||||
}
|
||||
err = setupConfig(params)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
adapter.UrlTestHook = func(url string, name string, delay uint16) {
|
||||
delayData := &Delay{
|
||||
|
||||
42
core/lib.go
42
core/lib.go
@@ -39,14 +39,6 @@ func freeCString(s *C.char) {
|
||||
C.free(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
func (result ActionResult) send() {
|
||||
data, err := result.Json()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
bridge.SendToPort(result.Port, string(data))
|
||||
}
|
||||
|
||||
//export invokeAction
|
||||
func invokeAction(paramsChar *C.char, port C.longlong) {
|
||||
params := C.GoString(paramsChar)
|
||||
@@ -57,38 +49,22 @@ func invokeAction(paramsChar *C.char, port C.longlong) {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
result := ActionResult{
|
||||
Id: action.Id,
|
||||
Method: action.Method,
|
||||
Port: i,
|
||||
}
|
||||
go handleAction(action, result)
|
||||
go handleAction(action, func(data interface{}) {
|
||||
bridge.SendToPort(i, string(action.getResult(data)))
|
||||
})
|
||||
}
|
||||
|
||||
func sendMessage(message Message) {
|
||||
if messagePort == -1 {
|
||||
return
|
||||
}
|
||||
result := ActionResult{
|
||||
res, err := message.Json()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
bridge.SendToPort(messagePort, string(Action{
|
||||
Method: messageMethod,
|
||||
Port: messagePort,
|
||||
Data: message,
|
||||
}
|
||||
result.send()
|
||||
}
|
||||
|
||||
//export getConfig
|
||||
func getConfig(s *C.char) *C.char {
|
||||
path := C.GoString(s)
|
||||
config, err := handleGetConfig(path)
|
||||
if err != nil {
|
||||
return C.CString("")
|
||||
}
|
||||
marshal, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return C.CString("")
|
||||
}
|
||||
return C.CString(string(marshal))
|
||||
}.getResult(res)))
|
||||
}
|
||||
|
||||
//export startListener
|
||||
|
||||
@@ -58,7 +58,7 @@ func (t *TunHandler) handleProtect(fd int) {
|
||||
return
|
||||
}
|
||||
|
||||
Protect(t.callback, fd)
|
||||
protect(t.callback, fd)
|
||||
}
|
||||
|
||||
func (t *TunHandler) handleResolveProcess(source, target net.Addr) string {
|
||||
@@ -79,7 +79,7 @@ func (t *TunHandler) handleResolveProcess(source, target net.Addr) string {
|
||||
if version < 29 {
|
||||
uid = platform.QuerySocketUidFromProcFs(source, target)
|
||||
}
|
||||
return ResolveProcess(t.callback, protocol, source.String(), target.String(), uid)
|
||||
return resolveProcess(t.callback, protocol, source.String(), target.String(), uid)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -188,21 +188,21 @@ func handleGetCurrentProfileName() string {
|
||||
return state.CurrentState.CurrentProfileName
|
||||
}
|
||||
|
||||
func nextHandle(action *Action, result ActionResult) bool {
|
||||
func nextHandle(action *Action, result func(data interface{})) bool {
|
||||
switch action.Method {
|
||||
case getAndroidVpnOptionsMethod:
|
||||
result.success(handleGetAndroidVpnOptions())
|
||||
result(handleGetAndroidVpnOptions())
|
||||
return true
|
||||
case updateDnsMethod:
|
||||
data := action.Data.(string)
|
||||
handleUpdateDns(data)
|
||||
result.success(true)
|
||||
result(true)
|
||||
return true
|
||||
case getRunTimeMethod:
|
||||
result.success(handleGetRunTime())
|
||||
result(handleGetRunTime())
|
||||
return true
|
||||
case getCurrentProfileNameMethod:
|
||||
result.success(handleGetCurrentProfileName())
|
||||
result(handleGetCurrentProfileName())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -220,7 +220,7 @@ func quickStart(initParamsChar *C.char, paramsChar *C.char, stateParamsChar *C.c
|
||||
bridge.SendToPort(i, "init error")
|
||||
}
|
||||
handleSetState(stateParams)
|
||||
bridge.SendToPort(i, handleSetupConfig(bytes))
|
||||
bridge.SendToPort(i, handleUpdateConfig(bytes))
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
@@ -12,20 +12,14 @@ import (
|
||||
|
||||
var conn net.Conn
|
||||
|
||||
func (result ActionResult) send() {
|
||||
data, err := result.Json()
|
||||
func sendMessage(message Message) {
|
||||
res, err := message.Json()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
send(data)
|
||||
}
|
||||
|
||||
func sendMessage(message Message) {
|
||||
result := ActionResult{
|
||||
send(Action{
|
||||
Method: messageMethod,
|
||||
Data: message,
|
||||
}
|
||||
result.send()
|
||||
}.getResult(res))
|
||||
}
|
||||
|
||||
func send(data []byte) {
|
||||
@@ -67,15 +61,12 @@ func startServer(arg string) {
|
||||
return
|
||||
}
|
||||
|
||||
result := ActionResult{
|
||||
Id: action.Id,
|
||||
Method: action.Method,
|
||||
}
|
||||
|
||||
go handleAction(action, result)
|
||||
go handleAction(action, func(data interface{}) {
|
||||
send(action.getResult(data))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func nextHandle(action *Action, result ActionResult) bool {
|
||||
func nextHandle(action *Action, result func(data interface{})) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/l10n/l10n.dart';
|
||||
@@ -101,10 +100,8 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
return AppStateManager(
|
||||
child: ClashManager(
|
||||
child: ConnectivityManager(
|
||||
onConnectivityChanged: (results) async {
|
||||
if (!results.contains(ConnectivityResult.vpn)) {
|
||||
await clashCore.closeConnections();
|
||||
}
|
||||
onConnectivityChanged: () async {
|
||||
await clashCore.closeConnections();
|
||||
globalState.appController.updateLocalIp();
|
||||
globalState.appController.addCheckIpNumDebounce();
|
||||
},
|
||||
|
||||
@@ -89,39 +89,39 @@ class ClashCore {
|
||||
return clashInterface.validateConfig(data);
|
||||
}
|
||||
|
||||
Future<String> updateConfig(UpdateParams updateParams) async {
|
||||
return await clashInterface.updateConfig(updateParams);
|
||||
}
|
||||
|
||||
Future<String> setupConfig(SetupParams setupParams) async {
|
||||
return await clashInterface.setupConfig(setupParams);
|
||||
Future<String> updateConfig(UpdateConfigParams updateConfigParams) async {
|
||||
return await clashInterface.updateConfig(updateConfigParams);
|
||||
}
|
||||
|
||||
Future<List<Group>> getProxiesGroups() async {
|
||||
final proxies = await clashInterface.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)
|
||||
final proxiesRawString = await clashInterface.getProxies();
|
||||
return Isolate.run<List<Group>>(() {
|
||||
if (proxiesRawString.isEmpty) return [];
|
||||
final proxies = (json.decode(proxiesRawString) ?? {}) as Map;
|
||||
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(
|
||||
(name) => proxies[name],
|
||||
(e) => Group.fromJson(e),
|
||||
)
|
||||
.where((proxy) => proxy != null)
|
||||
.toList();
|
||||
return group;
|
||||
}).toList();
|
||||
return groupsRaw
|
||||
.map(
|
||||
(e) => Group.fromJson(e),
|
||||
)
|
||||
.toList();
|
||||
});
|
||||
}
|
||||
|
||||
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async {
|
||||
@@ -143,10 +143,6 @@ class ClashCore {
|
||||
clashInterface.closeConnections();
|
||||
}
|
||||
|
||||
resetConnections() {
|
||||
clashInterface.resetConnections();
|
||||
}
|
||||
|
||||
Future<List<ExternalProvider>> getExternalProviders() async {
|
||||
final externalProvidersRawString =
|
||||
await clashInterface.getExternalProviders();
|
||||
@@ -210,16 +206,6 @@ class ClashCore {
|
||||
return Delay.fromJson(json.decode(data));
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getConfig(String id) async {
|
||||
final profilePath = await appPath.getProfilePath(id);
|
||||
final res = await clashInterface.getConfig(profilePath);
|
||||
if (res.isSuccess) {
|
||||
return res.data as Map<String, dynamic>;
|
||||
} else {
|
||||
throw res.message;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Traffic> getTraffic() async {
|
||||
final trafficString = await clashInterface.getTraffic();
|
||||
if (trafficString.isEmpty) {
|
||||
@@ -255,6 +241,14 @@ class ClashCore {
|
||||
return int.parse(value);
|
||||
}
|
||||
|
||||
Future<ClashConfigSnippet?> getProfile(String id) async {
|
||||
final res = await clashInterface.getProfile(id);
|
||||
if (res.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return Isolate.run(() => ClashConfigSnippet.fromJson(json.decode(res)));
|
||||
}
|
||||
|
||||
resetTraffic() {
|
||||
clashInterface.resetTraffic();
|
||||
}
|
||||
|
||||
@@ -154,18 +154,18 @@ class ClashFFI {
|
||||
late final _setrlimit =
|
||||
_setrlimitPtr.asFunction<int Function(int, ffi.Pointer<rlimit>)>();
|
||||
|
||||
int wait$1(
|
||||
int wait1(
|
||||
ffi.Pointer<ffi.Int> arg0,
|
||||
) {
|
||||
return _wait$1(
|
||||
return _wait1(
|
||||
arg0,
|
||||
);
|
||||
}
|
||||
|
||||
late final _wait$1Ptr =
|
||||
late final _wait1Ptr =
|
||||
_lookup<ffi.NativeFunction<pid_t Function(ffi.Pointer<ffi.Int>)>>('wait');
|
||||
late final _wait$1 =
|
||||
_wait$1Ptr.asFunction<int Function(ffi.Pointer<ffi.Int>)>();
|
||||
late final _wait1 =
|
||||
_wait1Ptr.asFunction<int Function(ffi.Pointer<ffi.Int>)>();
|
||||
|
||||
int waitpid(
|
||||
int arg0,
|
||||
@@ -2518,20 +2518,6 @@ class ClashFFI {
|
||||
late final _invokeAction =
|
||||
_invokeActionPtr.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
|
||||
|
||||
ffi.Pointer<ffi.Char> getConfig(
|
||||
ffi.Pointer<ffi.Char> s,
|
||||
) {
|
||||
return _getConfig(
|
||||
s,
|
||||
);
|
||||
}
|
||||
|
||||
late final _getConfigPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>>('getConfig');
|
||||
late final _getConfig = _getConfigPtr
|
||||
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
|
||||
|
||||
void startListener() {
|
||||
return _startListener();
|
||||
}
|
||||
@@ -2653,29 +2639,6 @@ class ClashFFI {
|
||||
_updateDnsPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
||||
}
|
||||
|
||||
typedef __int8_t = ffi.SignedChar;
|
||||
typedef Dart__int8_t = int;
|
||||
typedef __uint8_t = ffi.UnsignedChar;
|
||||
typedef Dart__uint8_t = int;
|
||||
typedef __int16_t = ffi.Short;
|
||||
typedef Dart__int16_t = int;
|
||||
typedef __uint16_t = ffi.UnsignedShort;
|
||||
typedef Dart__uint16_t = int;
|
||||
typedef __int32_t = ffi.Int;
|
||||
typedef Dart__int32_t = int;
|
||||
typedef __uint32_t = ffi.UnsignedInt;
|
||||
typedef Dart__uint32_t = int;
|
||||
typedef __int64_t = ffi.LongLong;
|
||||
typedef Dart__int64_t = int;
|
||||
typedef __uint64_t = ffi.UnsignedLongLong;
|
||||
typedef Dart__uint64_t = int;
|
||||
typedef __darwin_intptr_t = ffi.Long;
|
||||
typedef Dart__darwin_intptr_t = int;
|
||||
typedef __darwin_natural_t = ffi.UnsignedInt;
|
||||
typedef Dart__darwin_natural_t = int;
|
||||
typedef __darwin_ct_rune_t = ffi.Int;
|
||||
typedef Dart__darwin_ct_rune_t = int;
|
||||
|
||||
final class __mbstate_t extends ffi.Union {
|
||||
@ffi.Array.multi([128])
|
||||
external ffi.Array<ffi.Char> __mbstate8;
|
||||
@@ -2684,46 +2647,6 @@ final class __mbstate_t extends ffi.Union {
|
||||
external int _mbstateL;
|
||||
}
|
||||
|
||||
typedef __darwin_mbstate_t = __mbstate_t;
|
||||
typedef __darwin_ptrdiff_t = ffi.Long;
|
||||
typedef Dart__darwin_ptrdiff_t = int;
|
||||
typedef __darwin_size_t = ffi.UnsignedLong;
|
||||
typedef Dart__darwin_size_t = int;
|
||||
typedef __builtin_va_list = ffi.Pointer<ffi.Char>;
|
||||
typedef __darwin_va_list = __builtin_va_list;
|
||||
typedef __darwin_wchar_t = ffi.Int;
|
||||
typedef Dart__darwin_wchar_t = int;
|
||||
typedef __darwin_rune_t = __darwin_wchar_t;
|
||||
typedef __darwin_wint_t = ffi.Int;
|
||||
typedef Dart__darwin_wint_t = int;
|
||||
typedef __darwin_clock_t = ffi.UnsignedLong;
|
||||
typedef Dart__darwin_clock_t = int;
|
||||
typedef __darwin_socklen_t = __uint32_t;
|
||||
typedef __darwin_ssize_t = ffi.Long;
|
||||
typedef Dart__darwin_ssize_t = int;
|
||||
typedef __darwin_time_t = ffi.Long;
|
||||
typedef Dart__darwin_time_t = int;
|
||||
typedef __darwin_blkcnt_t = __int64_t;
|
||||
typedef __darwin_blksize_t = __int32_t;
|
||||
typedef __darwin_dev_t = __int32_t;
|
||||
typedef __darwin_fsblkcnt_t = ffi.UnsignedInt;
|
||||
typedef Dart__darwin_fsblkcnt_t = int;
|
||||
typedef __darwin_fsfilcnt_t = ffi.UnsignedInt;
|
||||
typedef Dart__darwin_fsfilcnt_t = int;
|
||||
typedef __darwin_gid_t = __uint32_t;
|
||||
typedef __darwin_id_t = __uint32_t;
|
||||
typedef __darwin_ino64_t = __uint64_t;
|
||||
typedef __darwin_ino_t = __darwin_ino64_t;
|
||||
typedef __darwin_mach_port_name_t = __darwin_natural_t;
|
||||
typedef __darwin_mach_port_t = __darwin_mach_port_name_t;
|
||||
typedef __darwin_mode_t = __uint16_t;
|
||||
typedef __darwin_off_t = __int64_t;
|
||||
typedef __darwin_pid_t = __int32_t;
|
||||
typedef __darwin_sigset_t = __uint32_t;
|
||||
typedef __darwin_suseconds_t = __int32_t;
|
||||
typedef __darwin_uid_t = __uint32_t;
|
||||
typedef __darwin_useconds_t = __uint32_t;
|
||||
|
||||
final class __darwin_pthread_handler_rec extends ffi.Struct {
|
||||
external ffi
|
||||
.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>
|
||||
@@ -2808,48 +2731,6 @@ final class _opaque_pthread_t extends ffi.Struct {
|
||||
external ffi.Array<ffi.Char> __opaque;
|
||||
}
|
||||
|
||||
typedef __darwin_pthread_attr_t = _opaque_pthread_attr_t;
|
||||
typedef __darwin_pthread_cond_t = _opaque_pthread_cond_t;
|
||||
typedef __darwin_pthread_condattr_t = _opaque_pthread_condattr_t;
|
||||
typedef __darwin_pthread_key_t = ffi.UnsignedLong;
|
||||
typedef Dart__darwin_pthread_key_t = int;
|
||||
typedef __darwin_pthread_mutex_t = _opaque_pthread_mutex_t;
|
||||
typedef __darwin_pthread_mutexattr_t = _opaque_pthread_mutexattr_t;
|
||||
typedef __darwin_pthread_once_t = _opaque_pthread_once_t;
|
||||
typedef __darwin_pthread_rwlock_t = _opaque_pthread_rwlock_t;
|
||||
typedef __darwin_pthread_rwlockattr_t = _opaque_pthread_rwlockattr_t;
|
||||
typedef __darwin_pthread_t = ffi.Pointer<_opaque_pthread_t>;
|
||||
typedef __darwin_nl_item = ffi.Int;
|
||||
typedef Dart__darwin_nl_item = int;
|
||||
typedef __darwin_wctrans_t = ffi.Int;
|
||||
typedef Dart__darwin_wctrans_t = int;
|
||||
typedef __darwin_wctype_t = __uint32_t;
|
||||
typedef u_int8_t = ffi.UnsignedChar;
|
||||
typedef Dartu_int8_t = int;
|
||||
typedef u_int16_t = ffi.UnsignedShort;
|
||||
typedef Dartu_int16_t = int;
|
||||
typedef u_int32_t = ffi.UnsignedInt;
|
||||
typedef Dartu_int32_t = int;
|
||||
typedef u_int64_t = ffi.UnsignedLongLong;
|
||||
typedef Dartu_int64_t = int;
|
||||
typedef register_t = ffi.Int64;
|
||||
typedef Dartregister_t = int;
|
||||
typedef user_addr_t = u_int64_t;
|
||||
typedef user_size_t = u_int64_t;
|
||||
typedef user_ssize_t = ffi.Int64;
|
||||
typedef Dartuser_ssize_t = int;
|
||||
typedef user_long_t = ffi.Int64;
|
||||
typedef Dartuser_long_t = int;
|
||||
typedef user_ulong_t = u_int64_t;
|
||||
typedef user_time_t = ffi.Int64;
|
||||
typedef Dartuser_time_t = int;
|
||||
typedef user_off_t = ffi.Int64;
|
||||
typedef Dartuser_off_t = int;
|
||||
typedef syscall_arg_t = u_int64_t;
|
||||
typedef ptrdiff_t = __darwin_ptrdiff_t;
|
||||
typedef rsize_t = __darwin_size_t;
|
||||
typedef wint_t = __darwin_wint_t;
|
||||
|
||||
final class _GoString_ extends ffi.Struct {
|
||||
external ffi.Pointer<ffi.Char> p;
|
||||
|
||||
@@ -2857,6 +2738,10 @@ final class _GoString_ extends ffi.Struct {
|
||||
external int n;
|
||||
}
|
||||
|
||||
typedef ptrdiff_t = __darwin_ptrdiff_t;
|
||||
typedef __darwin_ptrdiff_t = ffi.Long;
|
||||
typedef Dart__darwin_ptrdiff_t = int;
|
||||
|
||||
enum idtype_t {
|
||||
P_ALL(0),
|
||||
P_PID(1),
|
||||
@@ -2869,15 +2754,10 @@ enum idtype_t {
|
||||
0 => P_ALL,
|
||||
1 => P_PID,
|
||||
2 => P_PGID,
|
||||
_ => throw ArgumentError('Unknown value for idtype_t: $value'),
|
||||
_ => throw ArgumentError("Unknown value for idtype_t: $value"),
|
||||
};
|
||||
}
|
||||
|
||||
typedef pid_t = __darwin_pid_t;
|
||||
typedef id_t = __darwin_id_t;
|
||||
typedef sig_atomic_t = ffi.Int;
|
||||
typedef Dartsig_atomic_t = int;
|
||||
|
||||
final class __darwin_arm_exception_state extends ffi.Struct {
|
||||
@__uint32_t()
|
||||
external int __exception;
|
||||
@@ -2889,6 +2769,9 @@ final class __darwin_arm_exception_state extends ffi.Struct {
|
||||
external int __far;
|
||||
}
|
||||
|
||||
typedef __uint32_t = ffi.UnsignedInt;
|
||||
typedef Dart__uint32_t = int;
|
||||
|
||||
final class __darwin_arm_exception_state64 extends ffi.Struct {
|
||||
@__uint64_t()
|
||||
external int __far;
|
||||
@@ -2900,6 +2783,9 @@ final class __darwin_arm_exception_state64 extends ffi.Struct {
|
||||
external int __exception;
|
||||
}
|
||||
|
||||
typedef __uint64_t = ffi.UnsignedLongLong;
|
||||
typedef Dart__uint64_t = int;
|
||||
|
||||
final class __darwin_arm_exception_state64_v2 extends ffi.Struct {
|
||||
@__uint64_t()
|
||||
external int __far;
|
||||
@@ -3028,9 +2914,6 @@ final class __darwin_mcontext32 extends ffi.Struct {
|
||||
|
||||
final class __darwin_mcontext64 extends ffi.Opaque {}
|
||||
|
||||
typedef mcontext_t = ffi.Pointer<__darwin_mcontext64>;
|
||||
typedef pthread_attr_t = __darwin_pthread_attr_t;
|
||||
|
||||
final class __darwin_sigaltstack extends ffi.Struct {
|
||||
external ffi.Pointer<ffi.Void> ss_sp;
|
||||
|
||||
@@ -3041,7 +2924,8 @@ final class __darwin_sigaltstack extends ffi.Struct {
|
||||
external int ss_flags;
|
||||
}
|
||||
|
||||
typedef stack_t = __darwin_sigaltstack;
|
||||
typedef __darwin_size_t = ffi.UnsignedLong;
|
||||
typedef Dart__darwin_size_t = int;
|
||||
|
||||
final class __darwin_ucontext extends ffi.Struct {
|
||||
@ffi.Int()
|
||||
@@ -3060,9 +2944,7 @@ final class __darwin_ucontext extends ffi.Struct {
|
||||
external ffi.Pointer<__darwin_mcontext64> uc_mcontext;
|
||||
}
|
||||
|
||||
typedef ucontext_t = __darwin_ucontext;
|
||||
typedef sigset_t = __darwin_sigset_t;
|
||||
typedef uid_t = __darwin_uid_t;
|
||||
typedef __darwin_sigset_t = __uint32_t;
|
||||
|
||||
final class sigval extends ffi.Union {
|
||||
@ffi.Int()
|
||||
@@ -3086,6 +2968,9 @@ final class sigevent extends ffi.Struct {
|
||||
external ffi.Pointer<pthread_attr_t> sigev_notify_attributes;
|
||||
}
|
||||
|
||||
typedef pthread_attr_t = __darwin_pthread_attr_t;
|
||||
typedef __darwin_pthread_attr_t = _opaque_pthread_attr_t;
|
||||
|
||||
final class __siginfo extends ffi.Struct {
|
||||
@ffi.Int()
|
||||
external int si_signo;
|
||||
@@ -3116,7 +3001,12 @@ final class __siginfo extends ffi.Struct {
|
||||
external ffi.Array<ffi.UnsignedLong> __pad;
|
||||
}
|
||||
|
||||
typedef siginfo_t = __siginfo;
|
||||
typedef pid_t = __darwin_pid_t;
|
||||
typedef __darwin_pid_t = __int32_t;
|
||||
typedef __int32_t = ffi.Int;
|
||||
typedef Dart__int32_t = int;
|
||||
typedef uid_t = __darwin_uid_t;
|
||||
typedef __darwin_uid_t = __uint32_t;
|
||||
|
||||
final class __sigaction_u extends ffi.Union {
|
||||
external ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Int)>>
|
||||
@@ -3130,7 +3020,7 @@ final class __sigaction_u extends ffi.Union {
|
||||
}
|
||||
|
||||
final class __sigaction extends ffi.Struct {
|
||||
external __sigaction_u __sigaction_u$1;
|
||||
external __sigaction_u __sigaction_u1;
|
||||
|
||||
external ffi.Pointer<
|
||||
ffi.NativeFunction<
|
||||
@@ -3144,8 +3034,11 @@ final class __sigaction extends ffi.Struct {
|
||||
external int sa_flags;
|
||||
}
|
||||
|
||||
typedef siginfo_t = __siginfo;
|
||||
typedef sigset_t = __darwin_sigset_t;
|
||||
|
||||
final class sigaction extends ffi.Struct {
|
||||
external __sigaction_u __sigaction_u$1;
|
||||
external __sigaction_u __sigaction_u1;
|
||||
|
||||
@sigset_t()
|
||||
external int sa_mask;
|
||||
@@ -3154,10 +3047,6 @@ final class sigaction extends ffi.Struct {
|
||||
external int sa_flags;
|
||||
}
|
||||
|
||||
typedef sig_tFunction = ffi.Void Function(ffi.Int);
|
||||
typedef Dartsig_tFunction = void Function(int);
|
||||
typedef sig_t = ffi.Pointer<ffi.NativeFunction<sig_tFunction>>;
|
||||
|
||||
final class sigvec extends ffi.Struct {
|
||||
external ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Int)>>
|
||||
sv_handler;
|
||||
@@ -3176,43 +3065,6 @@ final class sigstack extends ffi.Struct {
|
||||
external int ss_onstack;
|
||||
}
|
||||
|
||||
typedef int_least8_t = ffi.Int8;
|
||||
typedef Dartint_least8_t = int;
|
||||
typedef int_least16_t = ffi.Int16;
|
||||
typedef Dartint_least16_t = int;
|
||||
typedef int_least32_t = ffi.Int32;
|
||||
typedef Dartint_least32_t = int;
|
||||
typedef int_least64_t = ffi.Int64;
|
||||
typedef Dartint_least64_t = int;
|
||||
typedef uint_least8_t = ffi.Uint8;
|
||||
typedef Dartuint_least8_t = int;
|
||||
typedef uint_least16_t = ffi.Uint16;
|
||||
typedef Dartuint_least16_t = int;
|
||||
typedef uint_least32_t = ffi.Uint32;
|
||||
typedef Dartuint_least32_t = int;
|
||||
typedef uint_least64_t = ffi.Uint64;
|
||||
typedef Dartuint_least64_t = int;
|
||||
typedef int_fast8_t = ffi.Int8;
|
||||
typedef Dartint_fast8_t = int;
|
||||
typedef int_fast16_t = ffi.Int16;
|
||||
typedef Dartint_fast16_t = int;
|
||||
typedef int_fast32_t = ffi.Int32;
|
||||
typedef Dartint_fast32_t = int;
|
||||
typedef int_fast64_t = ffi.Int64;
|
||||
typedef Dartint_fast64_t = int;
|
||||
typedef uint_fast8_t = ffi.Uint8;
|
||||
typedef Dartuint_fast8_t = int;
|
||||
typedef uint_fast16_t = ffi.Uint16;
|
||||
typedef Dartuint_fast16_t = int;
|
||||
typedef uint_fast32_t = ffi.Uint32;
|
||||
typedef Dartuint_fast32_t = int;
|
||||
typedef uint_fast64_t = ffi.Uint64;
|
||||
typedef Dartuint_fast64_t = int;
|
||||
typedef intmax_t = ffi.Long;
|
||||
typedef Dartintmax_t = int;
|
||||
typedef uintmax_t = ffi.UnsignedLong;
|
||||
typedef Dartuintmax_t = int;
|
||||
|
||||
final class timeval extends ffi.Struct {
|
||||
@__darwin_time_t()
|
||||
external int tv_sec;
|
||||
@@ -3221,7 +3073,9 @@ final class timeval extends ffi.Struct {
|
||||
external int tv_usec;
|
||||
}
|
||||
|
||||
typedef rlim_t = __uint64_t;
|
||||
typedef __darwin_time_t = ffi.Long;
|
||||
typedef Dart__darwin_time_t = int;
|
||||
typedef __darwin_suseconds_t = __int32_t;
|
||||
|
||||
final class rusage extends ffi.Struct {
|
||||
external timeval ru_utime;
|
||||
@@ -3271,8 +3125,6 @@ final class rusage extends ffi.Struct {
|
||||
external int ru_nivcsw;
|
||||
}
|
||||
|
||||
typedef rusage_info_t = ffi.Pointer<ffi.Void>;
|
||||
|
||||
final class rusage_info_v0 extends ffi.Struct {
|
||||
@ffi.Array.multi([16])
|
||||
external ffi.Array<ffi.Uint8> ri_uuid;
|
||||
@@ -3878,8 +3730,6 @@ final class rusage_info_v6 extends ffi.Struct {
|
||||
external ffi.Array<ffi.Uint64> ri_reserved;
|
||||
}
|
||||
|
||||
typedef rusage_info_current = rusage_info_v6;
|
||||
|
||||
final class rlimit extends ffi.Struct {
|
||||
@rlim_t()
|
||||
external int rlim_cur;
|
||||
@@ -3888,6 +3738,8 @@ final class rlimit extends ffi.Struct {
|
||||
external int rlim_max;
|
||||
}
|
||||
|
||||
typedef rlim_t = __uint64_t;
|
||||
|
||||
final class proc_rlimit_control_wakeupmon extends ffi.Struct {
|
||||
@ffi.Uint32()
|
||||
external int wm_flags;
|
||||
@@ -3896,10 +3748,10 @@ final class proc_rlimit_control_wakeupmon extends ffi.Struct {
|
||||
external int wm_rate;
|
||||
}
|
||||
|
||||
final class wait extends ffi.Opaque {}
|
||||
typedef id_t = __darwin_id_t;
|
||||
typedef __darwin_id_t = __uint32_t;
|
||||
|
||||
typedef ct_rune_t = __darwin_ct_rune_t;
|
||||
typedef rune_t = __darwin_rune_t;
|
||||
final class wait extends ffi.Opaque {}
|
||||
|
||||
final class div_t extends ffi.Struct {
|
||||
@ffi.Int()
|
||||
@@ -3932,18 +3784,18 @@ final class _malloc_zone_t extends ffi.Opaque {}
|
||||
|
||||
typedef malloc_zone_t = _malloc_zone_t;
|
||||
typedef dev_t = __darwin_dev_t;
|
||||
typedef __darwin_dev_t = __int32_t;
|
||||
typedef mode_t = __darwin_mode_t;
|
||||
typedef release_object_funcFunction = ffi.Void Function(
|
||||
ffi.Pointer<ffi.Void> obj);
|
||||
typedef Dartrelease_object_funcFunction = void Function(
|
||||
ffi.Pointer<ffi.Void> obj);
|
||||
typedef release_object_func
|
||||
= ffi.Pointer<ffi.NativeFunction<release_object_funcFunction>>;
|
||||
typedef __darwin_mode_t = __uint16_t;
|
||||
typedef __uint16_t = ffi.UnsignedShort;
|
||||
typedef Dart__uint16_t = int;
|
||||
typedef protect_func = ffi.Pointer<ffi.NativeFunction<protect_funcFunction>>;
|
||||
typedef protect_funcFunction = ffi.Void Function(
|
||||
ffi.Pointer<ffi.Void> tun_interface, ffi.Int fd);
|
||||
typedef Dartprotect_funcFunction = void Function(
|
||||
ffi.Pointer<ffi.Void> tun_interface, int fd);
|
||||
typedef protect_func = ffi.Pointer<ffi.NativeFunction<protect_funcFunction>>;
|
||||
typedef resolve_process_func
|
||||
= ffi.Pointer<ffi.NativeFunction<resolve_process_funcFunction>>;
|
||||
typedef resolve_process_funcFunction = ffi.Pointer<ffi.Char> Function(
|
||||
ffi.Pointer<ffi.Void> tun_interface,
|
||||
ffi.Int protocol,
|
||||
@@ -3956,35 +3808,12 @@ typedef Dartresolve_process_funcFunction = ffi.Pointer<ffi.Char> Function(
|
||||
ffi.Pointer<ffi.Char> source,
|
||||
ffi.Pointer<ffi.Char> target,
|
||||
int uid);
|
||||
typedef resolve_process_func
|
||||
= ffi.Pointer<ffi.NativeFunction<resolve_process_funcFunction>>;
|
||||
typedef GoInt8 = ffi.SignedChar;
|
||||
typedef DartGoInt8 = int;
|
||||
typedef GoUint8 = ffi.UnsignedChar;
|
||||
typedef DartGoUint8 = int;
|
||||
typedef GoInt16 = ffi.Short;
|
||||
typedef DartGoInt16 = int;
|
||||
typedef GoUint16 = ffi.UnsignedShort;
|
||||
typedef DartGoUint16 = int;
|
||||
typedef GoInt32 = ffi.Int;
|
||||
typedef DartGoInt32 = int;
|
||||
typedef GoUint32 = ffi.UnsignedInt;
|
||||
typedef DartGoUint32 = int;
|
||||
typedef GoInt64 = ffi.LongLong;
|
||||
typedef DartGoInt64 = int;
|
||||
typedef GoUint64 = ffi.UnsignedLongLong;
|
||||
typedef DartGoUint64 = int;
|
||||
typedef GoInt = GoInt64;
|
||||
typedef GoUint = GoUint64;
|
||||
typedef GoUintptr = ffi.Size;
|
||||
typedef DartGoUintptr = int;
|
||||
typedef GoFloat32 = ffi.Float;
|
||||
typedef DartGoFloat32 = double;
|
||||
typedef GoFloat64 = ffi.Double;
|
||||
typedef DartGoFloat64 = double;
|
||||
typedef GoString = _GoString_;
|
||||
typedef GoMap = ffi.Pointer<ffi.Void>;
|
||||
typedef GoChan = ffi.Pointer<ffi.Void>;
|
||||
typedef release_object_func
|
||||
= ffi.Pointer<ffi.NativeFunction<release_object_funcFunction>>;
|
||||
typedef release_object_funcFunction = ffi.Void Function(
|
||||
ffi.Pointer<ffi.Void> obj);
|
||||
typedef Dartrelease_object_funcFunction = void Function(
|
||||
ffi.Pointer<ffi.Void> obj);
|
||||
|
||||
final class GoInterface extends ffi.Struct {
|
||||
external ffi.Pointer<ffi.Void> t;
|
||||
@@ -4002,6 +3831,12 @@ final class GoSlice extends ffi.Struct {
|
||||
external int cap;
|
||||
}
|
||||
|
||||
typedef GoInt = GoInt64;
|
||||
typedef GoInt64 = ffi.LongLong;
|
||||
typedef DartGoInt64 = int;
|
||||
typedef GoUint8 = ffi.UnsignedChar;
|
||||
typedef DartGoUint8 = int;
|
||||
|
||||
const int __has_safe_buffers = 1;
|
||||
|
||||
const int __DARWIN_ONLY_64_BIT_INO_T = 1;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:fl_clash/clash/message.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
@@ -20,15 +19,11 @@ mixin ClashInterface {
|
||||
|
||||
FutureOr<String> validateConfig(String data);
|
||||
|
||||
FutureOr<Result> getConfig(String path);
|
||||
|
||||
Future<String> asyncTestDelay(String url, String proxyName);
|
||||
|
||||
FutureOr<String> updateConfig(UpdateParams updateParams);
|
||||
FutureOr<String> updateConfig(UpdateConfigParams updateConfigParams);
|
||||
|
||||
FutureOr<String> setupConfig(SetupParams setupParams);
|
||||
|
||||
FutureOr<Map> getProxies();
|
||||
FutureOr<String> getProxies();
|
||||
|
||||
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams);
|
||||
|
||||
@@ -71,12 +66,18 @@ mixin ClashInterface {
|
||||
|
||||
FutureOr<bool> closeConnections();
|
||||
|
||||
FutureOr<bool> resetConnections();
|
||||
FutureOr<String> getProfile(String id);
|
||||
|
||||
Future<bool> setState(CoreState state);
|
||||
}
|
||||
|
||||
mixin AndroidClashInterface {
|
||||
Future<bool> setFdMap(int fd);
|
||||
|
||||
Future<bool> setProcessMap(ProcessMapItem item);
|
||||
|
||||
// Future<bool> stopTun();
|
||||
|
||||
Future<bool> updateDns(String value);
|
||||
|
||||
Future<AndroidVpnOptions?> getAndroidVpnOptions();
|
||||
@@ -89,23 +90,55 @@ mixin AndroidClashInterface {
|
||||
abstract class ClashHandlerInterface with ClashInterface {
|
||||
Map<String, Completer> callbackCompleterMap = {};
|
||||
|
||||
Future<bool> nextHandleResult(ActionResult result, Completer? completer) =>
|
||||
Future.value(false);
|
||||
|
||||
handleResult(ActionResult result) async {
|
||||
final completer = callbackCompleterMap[result.id];
|
||||
try {
|
||||
switch (result.method) {
|
||||
case ActionMethod.initClash:
|
||||
case ActionMethod.shutdown:
|
||||
case ActionMethod.getIsInit:
|
||||
case ActionMethod.startListener:
|
||||
case ActionMethod.resetTraffic:
|
||||
case ActionMethod.closeConnections:
|
||||
case ActionMethod.closeConnection:
|
||||
case ActionMethod.stopListener:
|
||||
case ActionMethod.setState:
|
||||
case ActionMethod.crash:
|
||||
completer?.complete(result.data as bool);
|
||||
return;
|
||||
case ActionMethod.changeProxy:
|
||||
case ActionMethod.getProxies:
|
||||
case ActionMethod.getTraffic:
|
||||
case ActionMethod.getTotalTraffic:
|
||||
case ActionMethod.asyncTestDelay:
|
||||
case ActionMethod.getConnections:
|
||||
case ActionMethod.getExternalProviders:
|
||||
case ActionMethod.getExternalProvider:
|
||||
case ActionMethod.validateConfig:
|
||||
case ActionMethod.updateConfig:
|
||||
case ActionMethod.updateGeoData:
|
||||
case ActionMethod.updateExternalProvider:
|
||||
case ActionMethod.sideLoadExternalProvider:
|
||||
case ActionMethod.getCountryCode:
|
||||
case ActionMethod.getMemory:
|
||||
completer?.complete(result.data as String);
|
||||
return;
|
||||
case ActionMethod.message:
|
||||
clashMessage.controller.add(result.data);
|
||||
clashMessage.controller.add(result.data as String);
|
||||
completer?.complete(true);
|
||||
return;
|
||||
case ActionMethod.getConfig:
|
||||
completer?.complete(result.toResult);
|
||||
return;
|
||||
default:
|
||||
final isHandled = await nextHandleResult(result, completer);
|
||||
if (isHandled) {
|
||||
return;
|
||||
}
|
||||
completer?.complete(result.data);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
commonPrint.log("${result.id} error $e");
|
||||
} catch (_) {
|
||||
commonPrint.log(result.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,21 +153,18 @@ abstract class ClashHandlerInterface with ClashInterface {
|
||||
dynamic data,
|
||||
Duration? timeout,
|
||||
FutureOr<T> Function()? onTimeout,
|
||||
T? defaultValue,
|
||||
}) async {
|
||||
final id = "${method.name}#${utils.id}";
|
||||
|
||||
callbackCompleterMap[id] = Completer<T>();
|
||||
|
||||
dynamic mDefaultValue = defaultValue;
|
||||
if (mDefaultValue == null) {
|
||||
if (T == String) {
|
||||
mDefaultValue = "";
|
||||
} else if (T == bool) {
|
||||
mDefaultValue = false;
|
||||
} else if (T == Map) {
|
||||
mDefaultValue = {};
|
||||
}
|
||||
dynamic defaultValue;
|
||||
|
||||
if (T == String) {
|
||||
defaultValue = "";
|
||||
}
|
||||
if (T == bool) {
|
||||
defaultValue = false;
|
||||
}
|
||||
|
||||
sendMessage(
|
||||
@@ -143,6 +173,7 @@ abstract class ClashHandlerInterface with ClashInterface {
|
||||
id: id,
|
||||
method: method,
|
||||
data: data,
|
||||
defaultValue: defaultValue,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -154,7 +185,7 @@ abstract class ClashHandlerInterface with ClashInterface {
|
||||
},
|
||||
onTimeout: onTimeout ??
|
||||
() {
|
||||
return mDefaultValue;
|
||||
return defaultValue;
|
||||
},
|
||||
functionName: id,
|
||||
);
|
||||
@@ -180,7 +211,6 @@ abstract class ClashHandlerInterface with ClashInterface {
|
||||
shutdown() async {
|
||||
return await invoke<bool>(
|
||||
method: ActionMethod.shutdown,
|
||||
timeout: Duration(seconds: 1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -207,31 +237,10 @@ abstract class ClashHandlerInterface with ClashInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> updateConfig(UpdateParams updateParams) async {
|
||||
Future<String> updateConfig(UpdateConfigParams updateConfigParams) async {
|
||||
return await invoke<String>(
|
||||
method: ActionMethod.updateConfig,
|
||||
data: json.encode(updateParams),
|
||||
timeout: Duration(minutes: 2),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result> getConfig(String path) async {
|
||||
final res = await invoke<Result>(
|
||||
method: ActionMethod.getConfig,
|
||||
data: path,
|
||||
timeout: Duration(minutes: 2),
|
||||
defaultValue: Result.success({}),
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> setupConfig(SetupParams setupParams) async {
|
||||
final data = await Isolate.run(() => json.encode(setupParams));
|
||||
return await invoke<String>(
|
||||
method: ActionMethod.setupConfig,
|
||||
data: data,
|
||||
data: json.encode(updateConfigParams),
|
||||
timeout: Duration(minutes: 2),
|
||||
);
|
||||
}
|
||||
@@ -244,8 +253,8 @@ abstract class ClashHandlerInterface with ClashInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map> getProxies() {
|
||||
return invoke<Map>(
|
||||
Future<String> getProxies() {
|
||||
return invoke<String>(
|
||||
method: ActionMethod.getProxies,
|
||||
timeout: Duration(seconds: 5),
|
||||
);
|
||||
@@ -320,16 +329,17 @@ abstract class ClashHandlerInterface with ClashInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> resetConnections() {
|
||||
Future<bool> closeConnection(String id) {
|
||||
return invoke<bool>(
|
||||
method: ActionMethod.resetConnections,
|
||||
method: ActionMethod.closeConnection,
|
||||
data: id,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> closeConnection(String id) {
|
||||
return invoke<bool>(
|
||||
method: ActionMethod.closeConnection,
|
||||
Future<String> getProfile(String id) {
|
||||
return invoke<String>(
|
||||
method: ActionMethod.getProfile,
|
||||
data: id,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,6 +62,26 @@ class ClashLib extends ClashHandlerInterface with AndroidClashInterface {
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> nextHandleResult(result, completer) async {
|
||||
switch (result.method) {
|
||||
case ActionMethod.setFdMap:
|
||||
case ActionMethod.setProcessMap:
|
||||
case ActionMethod.stopTun:
|
||||
case ActionMethod.updateDns:
|
||||
completer?.complete(result.data as bool);
|
||||
return true;
|
||||
case ActionMethod.getRunTime:
|
||||
case ActionMethod.startTun:
|
||||
case ActionMethod.getAndroidVpnOptions:
|
||||
case ActionMethod.getCurrentProfileName:
|
||||
completer?.complete(result.data as String);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
destroy() async {
|
||||
await service?.destroy();
|
||||
@@ -86,6 +106,22 @@ class ClashLib extends ClashHandlerInterface with AndroidClashInterface {
|
||||
sendPort?.send(message);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> setFdMap(int fd) {
|
||||
return invoke<bool>(
|
||||
method: ActionMethod.setFdMap,
|
||||
data: json.encode(fd),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> setProcessMap(item) {
|
||||
return invoke<bool>(
|
||||
method: ActionMethod.setProcessMap,
|
||||
data: item,
|
||||
);
|
||||
}
|
||||
|
||||
// @override
|
||||
// Future<bool> stopTun() {
|
||||
// return invoke<bool>(
|
||||
@@ -231,31 +267,9 @@ class ClashLibHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
DateTime? getRunTime() {
|
||||
final runTimeRaw = clashFFI.getRunTime();
|
||||
final runTimeString = runTimeRaw.cast<Utf8>().toDartString();
|
||||
if (runTimeString.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
|
||||
}
|
||||
|
||||
// Map<String, dynamic> getConfig(String path) {
|
||||
// final pathChar = path.toNativeUtf8().cast<Char>();
|
||||
// final configRaw = clashFFI.getConfig(pathChar);
|
||||
// final configString = configRaw.cast<Utf8>().toDartString();
|
||||
// if (configString.isEmpty) {
|
||||
// return {};
|
||||
// }
|
||||
// final config = json.decode(configString);
|
||||
// malloc.free(pathChar);
|
||||
// clashFFI.freeCString(configRaw);
|
||||
// return config;
|
||||
// }
|
||||
|
||||
Future<String> quickStart(
|
||||
InitParams initParams,
|
||||
SetupParams setupParams,
|
||||
UpdateConfigParams updateConfigParams,
|
||||
CoreState state,
|
||||
) {
|
||||
final completer = Completer<String>();
|
||||
@@ -266,7 +280,7 @@ class ClashLibHandler {
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final params = json.encode(setupParams);
|
||||
final params = json.encode(updateConfigParams);
|
||||
final initValue = json.encode(initParams);
|
||||
final stateParams = json.encode(state);
|
||||
final initParamsChar = initValue.toNativeUtf8().cast<Char>();
|
||||
@@ -283,10 +297,15 @@ class ClashLibHandler {
|
||||
malloc.free(stateParamsChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
DateTime? getRunTime() {
|
||||
final runTimeRaw = clashFFI.getRunTime();
|
||||
final runTimeString = runTimeRaw.cast<Utf8>().toDartString();
|
||||
clashFFI.freeCString(runTimeRaw);
|
||||
if (runTimeString.isEmpty) return null;
|
||||
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
|
||||
}
|
||||
}
|
||||
|
||||
ClashLib? get clashLib =>
|
||||
Platform.isAndroid && !globalState.isService ? ClashLib() : null;
|
||||
|
||||
ClashLibHandler? get clashLibHandler =>
|
||||
Platform.isAndroid ? ClashLibHandler() : null;
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class ClashMessage {
|
||||
final controller = StreamController<Map<String, Object?>>();
|
||||
final controller = StreamController<String>();
|
||||
|
||||
ClashMessage._() {
|
||||
controller.stream.listen(
|
||||
(message) {
|
||||
if (message.isEmpty) {
|
||||
if(message.isEmpty){
|
||||
return;
|
||||
}
|
||||
final m = AppMessage.fromJson(message);
|
||||
final m = AppMessage.fromJson(json.decode(message));
|
||||
for (final AppMessageListener listener in _listeners) {
|
||||
switch (m.type) {
|
||||
case AppMessageType.log:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fl_clash/clash/interface.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
@@ -50,18 +51,23 @@ class ClashService extends ClashHandlerInterface {
|
||||
await _destroySocket();
|
||||
socketCompleter.complete(socket);
|
||||
socket
|
||||
.transform(uint8ListToListIntConverter)
|
||||
.transform(utf8.decoder)
|
||||
.transform(
|
||||
StreamTransformer<Uint8List, String>.fromHandlers(
|
||||
handleData: (Uint8List data, EventSink<String> sink) {
|
||||
sink.add(utf8.decode(data, allowMalformed: true));
|
||||
},
|
||||
),
|
||||
)
|
||||
.transform(LineSplitter())
|
||||
.listen(
|
||||
(data) {
|
||||
handleResult(
|
||||
ActionResult.fromJson(
|
||||
json.decode(data.trim()),
|
||||
),
|
||||
(data) {
|
||||
handleResult(
|
||||
ActionResult.fromJson(
|
||||
json.decode(data.trim()),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}, (error, stack) {
|
||||
commonPrint.log(error.toString());
|
||||
@@ -98,13 +104,7 @@ class ClashService extends ClashHandlerInterface {
|
||||
arg,
|
||||
],
|
||||
);
|
||||
process?.stdout.listen((_) {});
|
||||
process?.stderr.listen((e) {
|
||||
final error = utf8.decode(e);
|
||||
if (error.isNotEmpty) {
|
||||
commonPrint.log(error);
|
||||
}
|
||||
});
|
||||
process!.stdout.listen((_) {});
|
||||
isStarting = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,39 +3,37 @@ export 'app_localizations.dart';
|
||||
export 'color.dart';
|
||||
export 'constant.dart';
|
||||
export 'context.dart';
|
||||
export 'converter.dart';
|
||||
export 'datetime.dart';
|
||||
export 'fixed.dart';
|
||||
export 'function.dart';
|
||||
export 'future.dart';
|
||||
export 'http.dart';
|
||||
export 'icons.dart';
|
||||
export 'iterable.dart';
|
||||
export 'javascript.dart';
|
||||
export 'keyboard.dart';
|
||||
export 'launch.dart';
|
||||
export 'link.dart';
|
||||
export 'fixed.dart';
|
||||
export 'lock.dart';
|
||||
export 'measure.dart';
|
||||
export 'mixin.dart';
|
||||
export 'navigation.dart';
|
||||
export 'navigator.dart';
|
||||
export 'network.dart';
|
||||
export 'num.dart';
|
||||
export 'utils.dart';
|
||||
export 'package.dart';
|
||||
export 'path.dart';
|
||||
export 'picker.dart';
|
||||
export 'preferences.dart';
|
||||
export 'print.dart';
|
||||
export 'protocol.dart';
|
||||
export 'proxy.dart';
|
||||
export 'render.dart';
|
||||
export 'request.dart';
|
||||
export 'scroll.dart';
|
||||
export 'string.dart';
|
||||
export 'system.dart';
|
||||
export 'text.dart';
|
||||
export 'tray.dart';
|
||||
export 'utils.dart';
|
||||
export 'window.dart';
|
||||
export 'windows.dart';
|
||||
export 'render.dart';
|
||||
export 'mixin.dart';
|
||||
export 'print.dart';
|
||||
|
||||
@@ -28,7 +28,6 @@ final defaultTextScaleFactor =
|
||||
const httpTimeoutDuration = Duration(milliseconds: 5000);
|
||||
const moreDuration = Duration(milliseconds: 100);
|
||||
const animateDuration = Duration(milliseconds: 100);
|
||||
const midDuration = Duration(milliseconds: 200);
|
||||
const commonDuration = Duration(milliseconds: 300);
|
||||
const defaultUpdateDuration = Duration(days: 1);
|
||||
const mmdbFileName = "geoip.metadb";
|
||||
@@ -103,8 +102,3 @@ const defaultPrimaryColors = [
|
||||
defaultPrimaryColor,
|
||||
0XFF665390,
|
||||
];
|
||||
|
||||
const scriptTemplate = """
|
||||
const main = (config) => {
|
||||
return config;
|
||||
}""";
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class Uint8ListToListIntConverter extends Converter<Uint8List, List<int>> {
|
||||
@override
|
||||
List<int> convert(Uint8List input) {
|
||||
return input.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Sink<Uint8List> startChunkedConversion(Sink<List<int>> sink) {
|
||||
return _Uint8ListToListIntConverterSink(sink);
|
||||
}
|
||||
}
|
||||
|
||||
class _Uint8ListToListIntConverterSink implements Sink<Uint8List> {
|
||||
const _Uint8ListToListIntConverterSink(this._target);
|
||||
|
||||
final Sink<List<int>> _target;
|
||||
|
||||
@override
|
||||
void add(Uint8List data) {
|
||||
_target.add(data.toList());
|
||||
}
|
||||
|
||||
@override
|
||||
void close() {
|
||||
_target.close();
|
||||
}
|
||||
}
|
||||
|
||||
final uint8ListToListIntConverter = Uint8ListToListIntConverter();
|
||||
@@ -18,7 +18,6 @@ class FlClashHttpOverrides extends HttpOverrides {
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext? context) {
|
||||
final client = super.createHttpClient(context);
|
||||
client.badCertificateCallback = (_, __, ___) => true;
|
||||
client.findProxy = handleFindProxy;
|
||||
return client;
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import 'package:flutter_js/flutter_js.dart';
|
||||
|
||||
class Javascript {
|
||||
static Javascript? _instance;
|
||||
|
||||
Javascript._internal();
|
||||
|
||||
JavascriptRuntime get runTime {
|
||||
return getJavascriptRuntime();
|
||||
}
|
||||
|
||||
factory Javascript() {
|
||||
_instance ??= Javascript._internal();
|
||||
return _instance!;
|
||||
}
|
||||
}
|
||||
|
||||
final js = Javascript();
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/fragments.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/views/views.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Navigation {
|
||||
@@ -15,14 +15,14 @@ class Navigation {
|
||||
keep: false,
|
||||
icon: Icon(Icons.space_dashboard),
|
||||
label: PageLabel.dashboard,
|
||||
view: DashboardView(
|
||||
fragment: DashboardFragment(
|
||||
key: GlobalObjectKey(PageLabel.dashboard),
|
||||
),
|
||||
),
|
||||
NavigationItem(
|
||||
icon: const Icon(Icons.article),
|
||||
label: PageLabel.proxies,
|
||||
view: const ProxiesView(
|
||||
fragment: const ProxiesFragment(
|
||||
key: GlobalObjectKey(
|
||||
PageLabel.proxies,
|
||||
),
|
||||
@@ -34,7 +34,7 @@ class Navigation {
|
||||
const NavigationItem(
|
||||
icon: Icon(Icons.folder),
|
||||
label: PageLabel.profiles,
|
||||
view: ProfilesView(
|
||||
fragment: ProfilesFragment(
|
||||
key: GlobalObjectKey(
|
||||
PageLabel.profiles,
|
||||
),
|
||||
@@ -43,7 +43,7 @@ class Navigation {
|
||||
const NavigationItem(
|
||||
icon: Icon(Icons.view_timeline),
|
||||
label: PageLabel.requests,
|
||||
view: RequestsView(
|
||||
fragment: RequestsFragment(
|
||||
key: GlobalObjectKey(
|
||||
PageLabel.requests,
|
||||
),
|
||||
@@ -54,7 +54,7 @@ class Navigation {
|
||||
const NavigationItem(
|
||||
icon: Icon(Icons.ballot),
|
||||
label: PageLabel.connections,
|
||||
view: ConnectionsView(
|
||||
fragment: ConnectionsFragment(
|
||||
key: GlobalObjectKey(
|
||||
PageLabel.connections,
|
||||
),
|
||||
@@ -66,7 +66,7 @@ class Navigation {
|
||||
icon: Icon(Icons.storage),
|
||||
label: PageLabel.resources,
|
||||
description: "resourcesDesc",
|
||||
view: ResourcesView(
|
||||
fragment: Resources(
|
||||
key: GlobalObjectKey(
|
||||
PageLabel.resources,
|
||||
),
|
||||
@@ -76,7 +76,7 @@ class Navigation {
|
||||
NavigationItem(
|
||||
icon: const Icon(Icons.adb),
|
||||
label: PageLabel.logs,
|
||||
view: const LogsView(
|
||||
fragment: const LogsFragment(
|
||||
key: GlobalObjectKey(
|
||||
PageLabel.logs,
|
||||
),
|
||||
@@ -89,7 +89,7 @@ class Navigation {
|
||||
const NavigationItem(
|
||||
icon: Icon(Icons.construction),
|
||||
label: PageLabel.tools,
|
||||
view: ToolsView(
|
||||
fragment: ToolsFragment(
|
||||
key: GlobalObjectKey(
|
||||
PageLabel.tools,
|
||||
),
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BaseNavigator {
|
||||
@@ -20,21 +19,6 @@ class BaseNavigator {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<T?> modal<T>(BuildContext context, Widget child) async {
|
||||
if (globalState.appState.viewMode != ViewMode.mobile) {
|
||||
return await globalState.showCommonDialog<T>(
|
||||
child: CommonModal(
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
return await Navigator.of(context).push<T>(
|
||||
CommonRoute(
|
||||
builder: (context) => child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CommonDesktopRoute<T> extends PageRoute<T> {
|
||||
@@ -234,7 +218,8 @@ class _CommonPageTransitionState extends State<CommonPageTransition> {
|
||||
begin: const _CommonEdgeShadowDecoration(),
|
||||
end: _CommonEdgeShadowDecoration(
|
||||
<Color>[
|
||||
widget.context.colorScheme.inverseSurface.withValues(alpha: 0.02),
|
||||
widget.context.colorScheme.inverseSurface
|
||||
.withValues(alpha: 0.02),
|
||||
Colors.transparent,
|
||||
],
|
||||
),
|
||||
|
||||
@@ -73,22 +73,16 @@ class AppPath {
|
||||
return join(directory.path, profilesDirectoryName);
|
||||
}
|
||||
|
||||
Future<String> getProfilePath(String id) async {
|
||||
Future<String?> getProfilePath(String? id) async {
|
||||
if (id == null) return null;
|
||||
final directory = await profilesPath;
|
||||
return join(directory, "$id.yaml");
|
||||
}
|
||||
|
||||
Future<String> getProvidersPath(String id, {String filePath = ""}) async {
|
||||
Future<String?> getProvidersPath(String? id) async {
|
||||
if (id == null) return null;
|
||||
final directory = await profilesPath;
|
||||
final path = join(
|
||||
directory,
|
||||
"providers",
|
||||
id,
|
||||
);
|
||||
if (filePath.isNotEmpty) {
|
||||
return join(path, filePath);
|
||||
}
|
||||
return path;
|
||||
return join(directory, "providers", id);
|
||||
}
|
||||
|
||||
Future<String> get tempPath async {
|
||||
|
||||
@@ -15,7 +15,7 @@ class CommonPrint {
|
||||
log(String? text) {
|
||||
final payload = "[FlClash] $text";
|
||||
debugPrint(payload);
|
||||
if (!globalState.isInit) {
|
||||
if (globalState.isService) {
|
||||
return;
|
||||
}
|
||||
globalState.appController.addLog(
|
||||
|
||||
@@ -43,16 +43,6 @@ class Request {
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Response> getTextResponseForUrl(String url) async {
|
||||
final response = await _clashDio.get(
|
||||
url,
|
||||
options: Options(
|
||||
responseType: ResponseType.plain,
|
||||
),
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<MemoryImage?> getImage(String url) async {
|
||||
if (url.isEmpty) return null;
|
||||
final response = await _dio.get<Uint8List>(
|
||||
|
||||
@@ -8,13 +8,6 @@ extension StringExtension on String {
|
||||
return RegExp(r'^(http|https|ftp)://').hasMatch(this);
|
||||
}
|
||||
|
||||
dynamic get splitByMultipleSeparators {
|
||||
final parts =
|
||||
split(RegExp(r'[, ;]+')).where((part) => part.isNotEmpty).toList();
|
||||
|
||||
return parts.length > 1 ? parts : this;
|
||||
}
|
||||
|
||||
int compareToLower(String other) {
|
||||
return toLowerCase().compareTo(
|
||||
other.toLowerCase(),
|
||||
|
||||
@@ -56,7 +56,7 @@ class System {
|
||||
|
||||
Future<AuthorizeCode> authorizeCore() async {
|
||||
if (Platform.isAndroid) {
|
||||
return AuthorizeCode.error;
|
||||
return AuthorizeCode.none;
|
||||
}
|
||||
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
|
||||
final isAdmin = await checkIsAdmin();
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
class Utils {
|
||||
Color? getDelayColor(int? delay) {
|
||||
@@ -336,63 +333,6 @@ class Utils {
|
||||
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();
|
||||
final res = await function();
|
||||
stopwatch.stop();
|
||||
commonPrint.log('耗时:${stopwatch.elapsedMilliseconds} ms');
|
||||
return res;
|
||||
}
|
||||
return await function();
|
||||
}
|
||||
}
|
||||
|
||||
final utils = Utils();
|
||||
|
||||
@@ -18,8 +18,8 @@ import 'package:path/path.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'common/common.dart';
|
||||
import 'fragments/profiles/override_profile.dart';
|
||||
import 'models/models.dart';
|
||||
import 'views/profiles/override_profile.dart';
|
||||
|
||||
class AppController {
|
||||
bool lastTunEnable = false;
|
||||
@@ -30,15 +30,10 @@ class AppController {
|
||||
|
||||
AppController(this.context, WidgetRef ref) : _ref = ref;
|
||||
|
||||
setupClashConfigDebounce() {
|
||||
debouncer.call(FunctionTag.setupClashConfig, () async {
|
||||
await setupClashConfig();
|
||||
});
|
||||
}
|
||||
|
||||
updateClashConfigDebounce() {
|
||||
debouncer.call(FunctionTag.updateClashConfig, () async {
|
||||
await updateClashConfig();
|
||||
final isPatch = globalState.appState.needApply ? false : true;
|
||||
await updateClashConfig(isPatch);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,9 +71,9 @@ class AppController {
|
||||
}
|
||||
|
||||
restartCore() async {
|
||||
commonPrint.log("restart core");
|
||||
await clashService?.reStart();
|
||||
await _initCore();
|
||||
|
||||
if (_ref.read(runTimeProvider.notifier).isStart) {
|
||||
await globalState.handleStart();
|
||||
}
|
||||
@@ -249,80 +244,55 @@ class AppController {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateClashConfig() async {
|
||||
Future<void> updateClashConfig([bool? isPatch]) async {
|
||||
commonPrint.log("update clash patch: ${isPatch ?? false}");
|
||||
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
|
||||
if (commonScaffoldState?.mounted != true) return;
|
||||
await commonScaffoldState?.loadingRun(() async {
|
||||
await _updateClashConfig();
|
||||
await _updateClashConfig(
|
||||
isPatch,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _updateClashConfig() async {
|
||||
final updateParams = _ref.read(updateParamsProvider);
|
||||
final res = await _requestAdmin(updateParams.tun.enable);
|
||||
if (res.isError) {
|
||||
return;
|
||||
}
|
||||
lastTunEnable = res.data == true;
|
||||
final message = await clashCore.updateConfig(
|
||||
updateParams.copyWith.tun(
|
||||
enable: lastTunEnable,
|
||||
),
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
}
|
||||
|
||||
Future<Result<bool>> _requestAdmin(bool enableTun) async {
|
||||
Future<void> _updateClashConfig([bool? isPatch]) async {
|
||||
final profile = _ref.watch(currentProfileProvider);
|
||||
await _ref.read(currentProfileProvider)?.checkAndUpdate();
|
||||
final patchConfig = _ref.read(patchClashConfigProvider);
|
||||
final appSetting = _ref.read(appSettingProvider);
|
||||
bool enableTun = patchConfig.tun.enable;
|
||||
if (enableTun != lastTunEnable && lastTunEnable == false) {
|
||||
final code = await system.authorizeCore();
|
||||
switch (code) {
|
||||
case AuthorizeCode.none:
|
||||
return Result.success(enableTun);
|
||||
break;
|
||||
case AuthorizeCode.success:
|
||||
lastTunEnable = enableTun;
|
||||
await restartCore();
|
||||
return Result.error("");
|
||||
return;
|
||||
case AuthorizeCode.error:
|
||||
enableTun = false;
|
||||
return Result.success(false);
|
||||
}
|
||||
}
|
||||
return Result.success(enableTun);
|
||||
}
|
||||
|
||||
Future<void> setupClashConfig() async {
|
||||
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
|
||||
if (commonScaffoldState?.mounted != true) return;
|
||||
await commonScaffoldState?.loadingRun(() async {
|
||||
await _setupClashConfig();
|
||||
});
|
||||
}
|
||||
|
||||
_setupClashConfig() async {
|
||||
await _ref.read(currentProfileProvider)?.checkAndUpdate();
|
||||
final patchConfig = _ref.read(patchClashConfigProvider);
|
||||
final res = await _requestAdmin(patchConfig.tun.enable);
|
||||
if (res.isError) {
|
||||
return;
|
||||
if (appSetting.openLogs) {
|
||||
clashCore.startLog();
|
||||
} else {
|
||||
clashCore.stopLog();
|
||||
}
|
||||
lastTunEnable = res.data == true;
|
||||
final realPatchConfig = patchConfig.copyWith.tun(enable: lastTunEnable);
|
||||
final params = await globalState.getSetupParams(
|
||||
pathConfig: realPatchConfig,
|
||||
final res = await clashCore.updateConfig(
|
||||
globalState.getUpdateConfigParams(isPatch),
|
||||
);
|
||||
final message = await clashCore.setupConfig(params);
|
||||
lastProfileModified = await _ref.read(
|
||||
currentProfileProvider.select(
|
||||
(state) => state?.profileLastModified,
|
||||
),
|
||||
);
|
||||
if (message.isNotEmpty) {
|
||||
throw message;
|
||||
if (isPatch == false) {
|
||||
_ref.read(needApplyProvider.notifier).value = false;
|
||||
}
|
||||
if (res.isNotEmpty) throw res;
|
||||
lastTunEnable = enableTun;
|
||||
lastProfileModified = await profile?.profileLastModified;
|
||||
}
|
||||
|
||||
Future _applyProfile() async {
|
||||
await clashCore.requestGc();
|
||||
await setupClashConfig();
|
||||
await updateClashConfig();
|
||||
await updateGroups();
|
||||
await updateProviders();
|
||||
}
|
||||
@@ -438,14 +408,12 @@ class AppController {
|
||||
}
|
||||
|
||||
handleExit() async {
|
||||
Future.delayed(commonDuration, () {
|
||||
system.exit();
|
||||
});
|
||||
try {
|
||||
await savePreferences();
|
||||
await updateStatus(false);
|
||||
await proxy?.stopProxy();
|
||||
await clashCore.shutdown();
|
||||
await clashService?.destroy();
|
||||
await savePreferences();
|
||||
} finally {
|
||||
system.exit();
|
||||
}
|
||||
@@ -545,9 +513,9 @@ class AppController {
|
||||
FlutterError.onError = (details) {
|
||||
commonPrint.log(details.stack.toString());
|
||||
};
|
||||
updateTray(true);
|
||||
await _initCore();
|
||||
await _initStatus();
|
||||
updateTray(true);
|
||||
autoLaunch?.updateStatus(
|
||||
_ref.read(appSettingProvider).autoLaunch,
|
||||
);
|
||||
@@ -776,15 +744,19 @@ class AppController {
|
||||
final profilePath = await appPath.getProfilePath(profileId);
|
||||
final providersPath = await appPath.getProvidersPath(profileId);
|
||||
return await Isolate.run(() async {
|
||||
final profileFile = File(profilePath);
|
||||
final isExists = await profileFile.exists();
|
||||
if (isExists) {
|
||||
profileFile.delete(recursive: true);
|
||||
if (profilePath != null) {
|
||||
final profileFile = File(profilePath);
|
||||
final isExists = await profileFile.exists();
|
||||
if (isExists) {
|
||||
profileFile.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
final providersFileDir = File(providersPath);
|
||||
final providersFileIsExists = await providersFileDir.exists();
|
||||
if (providersFileIsExists) {
|
||||
providersFileDir.delete(recursive: true);
|
||||
if (providersPath != null) {
|
||||
final providersFileDir = File(providersPath);
|
||||
final isExists = await providersFileDir.exists();
|
||||
if (isExists) {
|
||||
providersFileDir.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/views/dashboard/widgets/widgets.dart';
|
||||
import 'package:fl_clash/fragments/dashboard/widgets/widgets.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@@ -118,12 +118,7 @@ enum AccessSortType { none, name, time }
|
||||
|
||||
enum ProfileType { file, url }
|
||||
|
||||
enum ResultType {
|
||||
@JsonValue(0)
|
||||
success,
|
||||
@JsonValue(-1)
|
||||
error,
|
||||
}
|
||||
enum ResultType { success, error }
|
||||
|
||||
enum AppMessageType {
|
||||
log,
|
||||
@@ -169,13 +164,9 @@ enum DnsMode {
|
||||
|
||||
enum ExternalControllerStatus {
|
||||
@JsonValue("")
|
||||
close(""),
|
||||
close,
|
||||
@JsonValue("127.0.0.1:9090")
|
||||
open("127.0.0.1:9090");
|
||||
|
||||
final String value;
|
||||
|
||||
const ExternalControllerStatus(this.value);
|
||||
open
|
||||
}
|
||||
|
||||
enum KeyboardModifier {
|
||||
@@ -257,7 +248,6 @@ enum ActionMethod {
|
||||
shutdown,
|
||||
validateConfig,
|
||||
updateConfig,
|
||||
getConfig,
|
||||
getProxies,
|
||||
changeProxy,
|
||||
getTraffic,
|
||||
@@ -266,7 +256,6 @@ enum ActionMethod {
|
||||
asyncTestDelay,
|
||||
getConnections,
|
||||
closeConnections,
|
||||
resetConnections,
|
||||
closeConnection,
|
||||
getExternalProviders,
|
||||
getExternalProvider,
|
||||
@@ -279,10 +268,12 @@ enum ActionMethod {
|
||||
stopListener,
|
||||
getCountryCode,
|
||||
getMemory,
|
||||
getProfile,
|
||||
crash,
|
||||
setupConfig,
|
||||
|
||||
///Android,
|
||||
setFdMap,
|
||||
setProcessMap,
|
||||
setState,
|
||||
startTun,
|
||||
stopTun,
|
||||
@@ -302,7 +293,6 @@ enum WindowsHelperServiceStatus {
|
||||
|
||||
enum FunctionTag {
|
||||
updateClashConfig,
|
||||
setupClashConfig,
|
||||
updateStatus,
|
||||
updateGroups,
|
||||
addCheckIpNum,
|
||||
@@ -320,6 +310,7 @@ enum FunctionTag {
|
||||
proxiesTabChange,
|
||||
logs,
|
||||
requests,
|
||||
|
||||
}
|
||||
|
||||
enum DashboardWidget {
|
||||
@@ -494,8 +485,3 @@ enum CacheTag {
|
||||
rules,
|
||||
requests,
|
||||
}
|
||||
|
||||
enum Language {
|
||||
yaml,
|
||||
javaScript,
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ class Contributor {
|
||||
});
|
||||
}
|
||||
|
||||
class AboutView extends StatelessWidget {
|
||||
const AboutView({super.key});
|
||||
class AboutFragment extends StatelessWidget {
|
||||
const AboutFragment({super.key});
|
||||
|
||||
_checkUpdate(BuildContext context) async {
|
||||
final commonScaffoldState = context.commonScaffoldState;
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
@@ -12,14 +12,14 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class AccessView extends ConsumerStatefulWidget {
|
||||
const AccessView({super.key});
|
||||
class AccessFragment extends ConsumerStatefulWidget {
|
||||
const AccessFragment({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<AccessView> createState() => _AccessViewState();
|
||||
ConsumerState<AccessFragment> createState() => _AccessFragmentState();
|
||||
}
|
||||
|
||||
class _AccessViewState extends ConsumerState<AccessView> {
|
||||
class _AccessFragmentState extends ConsumerState<AccessFragment> {
|
||||
List<String> acceptList = [];
|
||||
List<String> rejectList = [];
|
||||
late ScrollController _controller;
|
||||
@@ -257,8 +257,8 @@ class AutoCheckUpdateItem extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ApplicationSettingView extends StatelessWidget {
|
||||
const ApplicationSettingView({super.key});
|
||||
class ApplicationSettingFragment extends StatelessWidget {
|
||||
const ApplicationSettingFragment({super.key});
|
||||
|
||||
String getLocaleString(Locale? locale) {
|
||||
if (locale == null) return appLocalizations.defaultText;
|
||||
@@ -439,7 +439,7 @@ class _WebDAVFormDialogState extends ConsumerState<WebDAVFormDialog> {
|
||||
),
|
||||
validator: (String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return appLocalizations.emptyTip(appLocalizations.account);
|
||||
return appLocalizations.accountTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@@ -465,8 +465,7 @@ class _WebDAVFormDialogState extends ConsumerState<WebDAVFormDialog> {
|
||||
),
|
||||
validator: (String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return appLocalizations
|
||||
.emptyTip(appLocalizations.password);
|
||||
return appLocalizations.passwordTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@@ -1,22 +1,23 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/fragments/config/dns.dart';
|
||||
import 'package:fl_clash/fragments/config/general.dart';
|
||||
import 'package:fl_clash/fragments/config/network.dart';
|
||||
import 'package:fl_clash/models/clash_config.dart';
|
||||
import 'package:fl_clash/providers/config.dart' show patchClashConfigProvider;
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/views/config/dns.dart';
|
||||
import 'package:fl_clash/views/config/general.dart';
|
||||
import 'package:fl_clash/views/config/network.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class ConfigView extends StatefulWidget {
|
||||
const ConfigView({super.key});
|
||||
import '../../state.dart';
|
||||
|
||||
class ConfigFragment extends StatefulWidget {
|
||||
const ConfigFragment({super.key});
|
||||
|
||||
@override
|
||||
State<ConfigView> createState() => _ConfigViewState();
|
||||
State<ConfigFragment> createState() => _ConfigFragmentState();
|
||||
}
|
||||
|
||||
class _ConfigViewState extends State<ConfigView> {
|
||||
class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> items = [
|
||||
@@ -59,12 +59,6 @@ class ListenItem extends ConsumerWidget {
|
||||
delegate: InputDelegate(
|
||||
title: appLocalizations.listen,
|
||||
value: listen,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return appLocalizations.emptyTip(appLocalizations.listen);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: (String? value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
@@ -188,12 +182,6 @@ class FakeIpRangeItem extends ConsumerWidget {
|
||||
delegate: InputDelegate(
|
||||
title: appLocalizations.fakeipRange,
|
||||
value: fakeIpRange,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return appLocalizations.emptyTip(appLocalizations.fakeipRange);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: (String? value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
@@ -499,12 +487,6 @@ class GeoipCodeItem extends ConsumerWidget {
|
||||
delegate: InputDelegate(
|
||||
title: appLocalizations.geoipCode,
|
||||
value: geoipCode,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return appLocalizations.emptyTip(appLocalizations.geoipCode);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: (String? value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
430
lib/fragments/config/general.dart
Normal file
430
lib/fragments/config/general.dart
Normal file
@@ -0,0 +1,430 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/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';
|
||||
|
||||
class LogLevelItem extends ConsumerWidget {
|
||||
const LogLevelItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final logLevel =
|
||||
ref.watch(patchClashConfigProvider.select((state) => state.logLevel));
|
||||
return ListItem<LogLevel>.options(
|
||||
leading: const Icon(Icons.info_outline),
|
||||
title: Text(appLocalizations.logLevel),
|
||||
subtitle: Text(logLevel.name),
|
||||
delegate: OptionsDelegate<LogLevel>(
|
||||
title: appLocalizations.logLevel,
|
||||
options: LogLevel.values,
|
||||
onChanged: (LogLevel? value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
logLevel: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
textBuilder: (logLevel) => logLevel.name,
|
||||
value: logLevel,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UaItem extends ConsumerWidget {
|
||||
const UaItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final globalUa =
|
||||
ref.watch(patchClashConfigProvider.select((state) => state.globalUa));
|
||||
return ListItem<String?>.options(
|
||||
leading: const Icon(Icons.computer_outlined),
|
||||
title: const Text("UA"),
|
||||
subtitle: Text(globalUa ?? appLocalizations.defaultText),
|
||||
delegate: OptionsDelegate<String?>(
|
||||
title: "UA",
|
||||
options: [
|
||||
null,
|
||||
"clash-verge/v1.6.6",
|
||||
"ClashforWindows/0.19.23",
|
||||
],
|
||||
value: globalUa,
|
||||
onChanged: (value) {
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
globalUa: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
textBuilder: (ua) => ua ?? appLocalizations.defaultText,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class KeepAliveIntervalItem extends ConsumerWidget {
|
||||
const KeepAliveIntervalItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final keepAliveInterval = ref.watch(
|
||||
patchClashConfigProvider.select((state) => state.keepAliveInterval));
|
||||
return ListItem.input(
|
||||
leading: const Icon(Icons.timer_outlined),
|
||||
title: Text(appLocalizations.keepAliveIntervalDesc),
|
||||
subtitle: Text("$keepAliveInterval ${appLocalizations.seconds}"),
|
||||
delegate: InputDelegate(
|
||||
title: appLocalizations.keepAliveIntervalDesc,
|
||||
suffixText: appLocalizations.seconds,
|
||||
resetValue: "$defaultKeepAliveInterval",
|
||||
value: "$keepAliveInterval",
|
||||
onChanged: (String? value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
globalState.safeRun(
|
||||
() {
|
||||
final intValue = int.parse(value);
|
||||
if (intValue <= 0) {
|
||||
throw "Invalid keepAliveInterval";
|
||||
}
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
keepAliveInterval: intValue,
|
||||
),
|
||||
);
|
||||
},
|
||||
silence: false,
|
||||
title: appLocalizations.keepAliveIntervalDesc,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TestUrlItem extends ConsumerWidget {
|
||||
const TestUrlItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final testUrl =
|
||||
ref.watch(appSettingProvider.select((state) => state.testUrl));
|
||||
return ListItem.input(
|
||||
leading: const Icon(Icons.timeline),
|
||||
title: Text(appLocalizations.testUrl),
|
||||
subtitle: Text(testUrl),
|
||||
delegate: InputDelegate(
|
||||
resetValue: defaultTestUrl,
|
||||
title: appLocalizations.testUrl,
|
||||
value: testUrl,
|
||||
onChanged: (String? value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
globalState.safeRun(
|
||||
() {
|
||||
if (!value.isUrl) {
|
||||
throw "Invalid url";
|
||||
}
|
||||
ref.read(appSettingProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
testUrl: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
silence: false,
|
||||
title: appLocalizations.testUrl,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MixedPortItem extends ConsumerWidget {
|
||||
const MixedPortItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final mixedPort =
|
||||
ref.watch(patchClashConfigProvider.select((state) => state.mixedPort));
|
||||
return ListItem.input(
|
||||
leading: const Icon(Icons.adjust_outlined),
|
||||
title: Text(appLocalizations.proxyPort),
|
||||
subtitle: Text("$mixedPort"),
|
||||
delegate: InputDelegate(
|
||||
title: appLocalizations.proxyPort,
|
||||
value: "$mixedPort",
|
||||
onChanged: (String? value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
globalState.safeRun(
|
||||
() {
|
||||
final mixedPort = int.parse(value);
|
||||
if (mixedPort < 1024 || mixedPort > 49151) {
|
||||
throw "Invalid port";
|
||||
}
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
mixedPort: mixedPort,
|
||||
),
|
||||
);
|
||||
},
|
||||
silence: false,
|
||||
title: appLocalizations.proxyPort,
|
||||
);
|
||||
},
|
||||
resetValue: "$defaultMixedPort",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HostsItem extends StatelessWidget {
|
||||
const HostsItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem.open(
|
||||
leading: const Icon(Icons.view_list_outlined),
|
||||
title: const Text("Hosts"),
|
||||
subtitle: Text(appLocalizations.hostsDesc),
|
||||
delegate: OpenDelegate(
|
||||
blur: false,
|
||||
title: "Hosts",
|
||||
widget: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
final hosts = ref
|
||||
.watch(patchClashConfigProvider.select((state) => state.hosts));
|
||||
return MapInputPage(
|
||||
title: "Hosts",
|
||||
map: hosts,
|
||||
titleBuilder: (item) => Text(item.key),
|
||||
subtitleBuilder: (item) => Text(item.value),
|
||||
onChange: (value) {
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
hosts: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Ipv6Item extends ConsumerWidget {
|
||||
const Ipv6Item({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final ipv6 =
|
||||
ref.watch(patchClashConfigProvider.select((state) => state.ipv6));
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.water_outlined),
|
||||
title: const Text("IPv6"),
|
||||
subtitle: Text(appLocalizations.ipv6Desc),
|
||||
delegate: SwitchDelegate(
|
||||
value: ipv6,
|
||||
onChanged: (bool value) async {
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
ipv6: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AllowLanItem extends ConsumerWidget {
|
||||
const AllowLanItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final allowLan =
|
||||
ref.watch(patchClashConfigProvider.select((state) => state.allowLan));
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.device_hub),
|
||||
title: Text(appLocalizations.allowLan),
|
||||
subtitle: Text(appLocalizations.allowLanDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: allowLan,
|
||||
onChanged: (bool value) async {
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
allowLan: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UnifiedDelayItem extends ConsumerWidget {
|
||||
const UnifiedDelayItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final unifiedDelay = ref
|
||||
.watch(patchClashConfigProvider.select((state) => state.unifiedDelay));
|
||||
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.compress_outlined),
|
||||
title: Text(appLocalizations.unifiedDelay),
|
||||
subtitle: Text(appLocalizations.unifiedDelayDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: unifiedDelay,
|
||||
onChanged: (bool value) async {
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
unifiedDelay: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FindProcessItem extends ConsumerWidget {
|
||||
const FindProcessItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final findProcess = ref.watch(patchClashConfigProvider
|
||||
.select((state) => state.findProcessMode == FindProcessMode.always));
|
||||
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.polymer_outlined),
|
||||
title: Text(appLocalizations.findProcessMode),
|
||||
subtitle: Text(appLocalizations.findProcessModeDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: findProcess,
|
||||
onChanged: (bool value) async {
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
findProcessMode:
|
||||
value ? FindProcessMode.always : FindProcessMode.off,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TcpConcurrentItem extends ConsumerWidget {
|
||||
const TcpConcurrentItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final tcpConcurrent = ref
|
||||
.watch(patchClashConfigProvider.select((state) => state.tcpConcurrent));
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.double_arrow_outlined),
|
||||
title: Text(appLocalizations.tcpConcurrent),
|
||||
subtitle: Text(appLocalizations.tcpConcurrentDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: tcpConcurrent,
|
||||
onChanged: (value) async {
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
tcpConcurrent: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GeodataLoaderItem extends ConsumerWidget {
|
||||
const GeodataLoaderItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final isMemconservative = ref.watch(patchClashConfigProvider.select(
|
||||
(state) => state.geodataLoader == GeodataLoader.memconservative));
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.memory),
|
||||
title: Text(appLocalizations.geodataLoader),
|
||||
subtitle: Text(appLocalizations.geodataLoaderDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: isMemconservative,
|
||||
onChanged: (bool value) async {
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
geodataLoader: value
|
||||
? GeodataLoader.memconservative
|
||||
: GeodataLoader.standard,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExternalControllerItem extends ConsumerWidget {
|
||||
const ExternalControllerItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final hasExternalController = ref.watch(patchClashConfigProvider.select(
|
||||
(state) => state.externalController == ExternalControllerStatus.open));
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.api_outlined),
|
||||
title: Text(appLocalizations.externalController),
|
||||
subtitle: Text(appLocalizations.externalControllerDesc),
|
||||
delegate: SwitchDelegate(
|
||||
value: hasExternalController,
|
||||
onChanged: (bool value) async {
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
externalController: value
|
||||
? ExternalControllerStatus.open
|
||||
: ExternalControllerStatus.close,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final generalItems = <Widget>[
|
||||
LogLevelItem(),
|
||||
UaItem(),
|
||||
if (system.isDesktop) KeepAliveIntervalItem(),
|
||||
TestUrlItem(),
|
||||
MixedPortItem(),
|
||||
HostsItem(),
|
||||
Ipv6Item(),
|
||||
AllowLanItem(),
|
||||
UnifiedDelayItem(),
|
||||
FindProcessItem(),
|
||||
TcpConcurrentItem(),
|
||||
GeodataLoaderItem(),
|
||||
ExternalControllerItem(),
|
||||
]
|
||||
.separated(
|
||||
const Divider(
|
||||
height: 0,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
@@ -11,14 +11,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'item.dart';
|
||||
|
||||
class ConnectionsView extends ConsumerStatefulWidget {
|
||||
const ConnectionsView({super.key});
|
||||
class ConnectionsFragment extends ConsumerStatefulWidget {
|
||||
const ConnectionsFragment({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ConnectionsView> createState() => _ConnectionsViewState();
|
||||
ConsumerState<ConnectionsFragment> createState() =>
|
||||
_ConnectionsFragmentState();
|
||||
}
|
||||
|
||||
class _ConnectionsViewState extends ConsumerState<ConnectionsView>
|
||||
class _ConnectionsFragmentState extends ConsumerState<ConnectionsFragment>
|
||||
with PageMixin {
|
||||
final _connectionsStateNotifier = ValueNotifier<ConnectionsState>(
|
||||
const ConnectionsState(),
|
||||
@@ -59,15 +60,13 @@ class _ConnectionsViewState extends ConsumerState<ConnectionsView>
|
||||
|
||||
_updateConnections() async {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (mounted) {
|
||||
_connectionsStateNotifier.value =
|
||||
_connectionsStateNotifier.value.copyWith(
|
||||
connections: await clashCore.getConnections(),
|
||||
);
|
||||
timer = Timer(Duration(seconds: 1), () async {
|
||||
_updateConnections();
|
||||
});
|
||||
}
|
||||
_connectionsStateNotifier.value =
|
||||
_connectionsStateNotifier.value.copyWith(
|
||||
connections: await clashCore.getConnections(),
|
||||
);
|
||||
timer = Timer(Duration(seconds: 1), () async {
|
||||
_updateConnections();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -114,7 +113,7 @@ class _ConnectionsViewState extends ConsumerState<ConnectionsView>
|
||||
final connections = state.list;
|
||||
if (connections.isEmpty) {
|
||||
return NullStatus(
|
||||
label: appLocalizations.nullTip(appLocalizations.connections),
|
||||
label: appLocalizations.nullConnectionsDesc,
|
||||
);
|
||||
}
|
||||
return CommonScrollBar(
|
||||
210
lib/fragments/connection/item.dart
Normal file
210
lib/fragments/connection/item.dart
Normal file
@@ -0,0 +1,210 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
import 'package:fl_clash/providers/config.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class ConnectionItem extends ConsumerWidget {
|
||||
final Connection connection;
|
||||
final Function(String)? onClickKeyword;
|
||||
final Widget? trailing;
|
||||
|
||||
const ConnectionItem({
|
||||
super.key,
|
||||
required this.connection,
|
||||
this.onClickKeyword,
|
||||
this.trailing,
|
||||
});
|
||||
|
||||
Future<ImageProvider?> _getPackageIcon(Connection connection) async {
|
||||
return await app?.getPackageIcon(connection.metadata.process);
|
||||
}
|
||||
|
||||
String _getSourceText(Connection connection) {
|
||||
final metadata = connection.metadata;
|
||||
if (metadata.process.isEmpty) {
|
||||
return connection.start.lastUpdateTimeDesc;
|
||||
}
|
||||
return "${metadata.process} · ${connection.start.lastUpdateTimeDesc}";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final value = ref.watch(
|
||||
patchClashConfigProvider.select(
|
||||
(state) =>
|
||||
state.findProcessMode == FindProcessMode.always &&
|
||||
Platform.isAndroid,
|
||||
),
|
||||
);
|
||||
final title = Text(
|
||||
connection.desc,
|
||||
style: context.textTheme.bodyLarge,
|
||||
);
|
||||
final subTitle = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Text(
|
||||
_getSourceText(connection),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 6,
|
||||
spacing: 6,
|
||||
children: [
|
||||
for (final chain in connection.chains)
|
||||
CommonChip(
|
||||
label: chain,
|
||||
onPressed: () {
|
||||
if (onClickKeyword == null) return;
|
||||
onClickKeyword!(chain);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
return CommonPopupBox(
|
||||
targetBuilder: (open) {
|
||||
return ListItem(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
||||
leading: value
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
if (onClickKeyword == null) return;
|
||||
final process = connection.metadata.process;
|
||||
if (process.isEmpty) return;
|
||||
onClickKeyword!(process);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: FutureBuilder<ImageProvider?>(
|
||||
future: _getPackageIcon(connection),
|
||||
builder: (_, snapshot) {
|
||||
if (!snapshot.hasData && snapshot.data == null) {
|
||||
return Container();
|
||||
} else {
|
||||
return Image(
|
||||
image: snapshot.data!,
|
||||
gaplessPlayback: true,
|
||||
width: 48,
|
||||
height: 48,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
title: title,
|
||||
subtitle: subTitle,
|
||||
trailing: trailing,
|
||||
);
|
||||
// return InkWell(
|
||||
// child: GestureDetector(
|
||||
// onLongPressStart: (details) {
|
||||
// if (!system.isDesktop) {
|
||||
// return;
|
||||
// }
|
||||
// open(
|
||||
// offset: details.localPosition.translate(
|
||||
// 0,
|
||||
// -12,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// onSecondaryTapDown: (details) {
|
||||
// if (!system.isDesktop) {
|
||||
// return;
|
||||
// }
|
||||
// open(
|
||||
// offset: details.localPosition.translate(
|
||||
// 0,
|
||||
// -12,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// child: ListItem(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// horizontal: 16,
|
||||
// vertical: 4,
|
||||
// ),
|
||||
// tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
||||
// leading: value
|
||||
// ? GestureDetector(
|
||||
// onTap: () {
|
||||
// if (onClickKeyword == null) return;
|
||||
// final process = connection.metadata.process;
|
||||
// if (process.isEmpty) return;
|
||||
// onClickKeyword!(process);
|
||||
// },
|
||||
// child: Container(
|
||||
// margin: const EdgeInsets.only(top: 4),
|
||||
// width: 48,
|
||||
// height: 48,
|
||||
// child: FutureBuilder<ImageProvider?>(
|
||||
// future: _getPackageIcon(connection),
|
||||
// builder: (_, snapshot) {
|
||||
// if (!snapshot.hasData && snapshot.data == null) {
|
||||
// return Container();
|
||||
// } else {
|
||||
// return Image(
|
||||
// image: snapshot.data!,
|
||||
// gaplessPlayback: true,
|
||||
// width: 48,
|
||||
// height: 48,
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// : null,
|
||||
// title: title,
|
||||
// subtitle: subTitle,
|
||||
// trailing: trailing,
|
||||
// ),
|
||||
// ),
|
||||
// onTap: () {},
|
||||
// );
|
||||
},
|
||||
popup: CommonPopupMenu(
|
||||
minWidth: 160,
|
||||
items: [
|
||||
PopupMenuItemData(
|
||||
label: "编辑规则",
|
||||
onPressed: () {
|
||||
// _handleShowEditExtendPage(context);
|
||||
},
|
||||
),
|
||||
PopupMenuItemData(
|
||||
label: "设置直连",
|
||||
onPressed: () {},
|
||||
),
|
||||
PopupMenuItemData(
|
||||
label: "一键屏蔽",
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,14 +11,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'item.dart';
|
||||
|
||||
class RequestsView extends ConsumerStatefulWidget {
|
||||
const RequestsView({super.key});
|
||||
class RequestsFragment extends ConsumerStatefulWidget {
|
||||
const RequestsFragment({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<RequestsView> createState() => _RequestsViewState();
|
||||
ConsumerState<RequestsFragment> createState() => _RequestsFragmentState();
|
||||
}
|
||||
|
||||
class _RequestsViewState extends ConsumerState<RequestsView> with PageMixin {
|
||||
class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
with PageMixin {
|
||||
final _requestsStateNotifier = ValueNotifier<ConnectionsState>(
|
||||
const ConnectionsState(loading: true),
|
||||
);
|
||||
@@ -119,7 +120,7 @@ class _RequestsViewState extends ConsumerState<RequestsView> with PageMixin {
|
||||
return;
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
if(mounted){
|
||||
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
|
||||
connections: _requests,
|
||||
);
|
||||
@@ -186,6 +187,11 @@ class _RequestsViewState extends ConsumerState<RequestsView> with PageMixin {
|
||||
builder: (_, state, __) {
|
||||
_preLoad();
|
||||
final connections = state.list;
|
||||
if (connections.isEmpty) {
|
||||
return NullStatus(
|
||||
label: appLocalizations.nullRequestsDesc,
|
||||
);
|
||||
}
|
||||
final items = connections
|
||||
.map<Widget>(
|
||||
(connection) => ConnectionItem(
|
||||
@@ -204,8 +210,7 @@ class _RequestsViewState extends ConsumerState<RequestsView> with PageMixin {
|
||||
.toList();
|
||||
final content = connections.isEmpty
|
||||
? NullStatus(
|
||||
label:
|
||||
appLocalizations.nullTip(appLocalizations.requests),
|
||||
label: appLocalizations.nullRequestsDesc,
|
||||
)
|
||||
: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
@@ -9,14 +9,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'widgets/start_button.dart';
|
||||
|
||||
class DashboardView extends ConsumerStatefulWidget {
|
||||
const DashboardView({super.key});
|
||||
class DashboardFragment extends ConsumerStatefulWidget {
|
||||
const DashboardFragment({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<DashboardView> createState() => _DashboardViewState();
|
||||
ConsumerState<DashboardFragment> createState() => _DashboardFragmentState();
|
||||
}
|
||||
|
||||
class _DashboardViewState extends ConsumerState<DashboardView> with PageMixin {
|
||||
class _DashboardFragmentState extends ConsumerState<DashboardFragment>
|
||||
with PageMixin {
|
||||
final key = GlobalKey<SuperGridState>();
|
||||
|
||||
@override
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/fragments/config/network.dart';
|
||||
import 'package:fl_clash/providers/config.dart';
|
||||
import 'package:fl_clash/views/config/network.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
143
lib/fragments/dashboard/widgets/start_button.dart
Normal file
143
lib/fragments/dashboard/widgets/start_button.dart
Normal file
@@ -0,0 +1,143 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class StartButton extends StatefulWidget {
|
||||
const StartButton({super.key});
|
||||
|
||||
@override
|
||||
State<StartButton> createState() => _StartButtonState();
|
||||
}
|
||||
|
||||
class _StartButtonState extends State<StartButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
bool isStart = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
isStart = globalState.appState.runTime != null;
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
value: isStart ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
handleSwitchStart() {
|
||||
isStart = !isStart;
|
||||
updateController();
|
||||
debouncer.call(
|
||||
FunctionTag.updateStatus,
|
||||
() {
|
||||
globalState.appController.updateStatus(isStart);
|
||||
},
|
||||
duration: moreDuration,
|
||||
);
|
||||
}
|
||||
|
||||
updateController() {
|
||||
if (isStart) {
|
||||
_controller.forward();
|
||||
} else {
|
||||
_controller.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer(
|
||||
builder: (_, ref, child) {
|
||||
final state = ref.watch(startButtonSelectorStateProvider);
|
||||
if (!state.isInit || !state.hasProfile) {
|
||||
return Container();
|
||||
}
|
||||
ref.listenManual(
|
||||
runTimeProvider.select((state) => state != null),
|
||||
(prev, next) {
|
||||
if (next != isStart) {
|
||||
isStart = next;
|
||||
updateController();
|
||||
}
|
||||
},
|
||||
fireImmediately: true,
|
||||
);
|
||||
final textWidth = globalState.measure
|
||||
.computeTextSize(
|
||||
Text(
|
||||
utils.getTimeDifference(
|
||||
DateTime.now(),
|
||||
),
|
||||
style: context.textTheme.titleMedium?.toSoftBold,
|
||||
),
|
||||
)
|
||||
.width +
|
||||
16;
|
||||
return AnimatedBuilder(
|
||||
animation: _controller.view,
|
||||
builder: (_, child) {
|
||||
return SizedBox(
|
||||
width: 56 + textWidth * _controller.value,
|
||||
height: 56,
|
||||
child: FloatingActionButton(
|
||||
heroTag: null,
|
||||
onPressed: () {
|
||||
handleSwitchStart();
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedIcon(
|
||||
icon: AnimatedIcons.play_pause,
|
||||
progress: _controller,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ClipRect(
|
||||
child: OverflowBox(
|
||||
maxWidth: textWidth,
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: child!,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
final runTime = ref.watch(runTimeProvider);
|
||||
final text = utils.getTimeText(runTime);
|
||||
return Text(
|
||||
text,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.toSoftBold
|
||||
.copyWith(color: context.colorScheme.onPrimaryContainer),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,8 @@ extension IntlExt on Intl {
|
||||
Intl.message("action_$messageText");
|
||||
}
|
||||
|
||||
class HotKeyView extends StatelessWidget {
|
||||
const HotKeyView({super.key});
|
||||
class HotKeyFragment extends StatelessWidget {
|
||||
const HotKeyFragment({super.key});
|
||||
|
||||
String getSubtitle(HotKeyAction hotKeyAction) {
|
||||
final key = hotKeyAction.key;
|
||||
@@ -10,14 +10,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../models/models.dart';
|
||||
import '../widgets/widgets.dart';
|
||||
|
||||
class LogsView extends ConsumerStatefulWidget {
|
||||
const LogsView({super.key});
|
||||
class LogsFragment extends ConsumerStatefulWidget {
|
||||
const LogsFragment({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<LogsView> createState() => _LogsViewState();
|
||||
ConsumerState<LogsFragment> createState() => _LogsFragmentState();
|
||||
}
|
||||
|
||||
class _LogsViewState extends ConsumerState<LogsView> with PageMixin {
|
||||
class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
final _logsStateNotifier = ValueNotifier<LogsState>(
|
||||
LogsState(loading: true),
|
||||
);
|
||||
@@ -213,9 +213,7 @@ class _LogsViewState extends ConsumerState<LogsView> with PageMixin {
|
||||
.toList();
|
||||
final content = logs.isEmpty
|
||||
? NullStatus(
|
||||
label: appLocalizations.nullTip(
|
||||
appLocalizations.logs,
|
||||
),
|
||||
label: appLocalizations.nullLogsDesc,
|
||||
)
|
||||
: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
@@ -4,10 +4,10 @@ import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AddProfileView extends StatelessWidget {
|
||||
class AddProfile extends StatelessWidget {
|
||||
final BuildContext context;
|
||||
|
||||
const AddProfileView({
|
||||
const AddProfile({
|
||||
super.key,
|
||||
required this.context,
|
||||
});
|
||||
@@ -38,21 +38,7 @@ class AddProfileView extends StatelessWidget {
|
||||
|
||||
_toAdd() async {
|
||||
final url = await globalState.showCommonDialog<String>(
|
||||
child: InputDialog(
|
||||
autovalidateMode: AutovalidateMode.onUnfocus,
|
||||
title: appLocalizations.importFromURL,
|
||||
labelText: appLocalizations.url,
|
||||
value: '',
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return appLocalizations.emptyTip("").trim();
|
||||
}
|
||||
if (!value.isUrl) {
|
||||
return appLocalizations.urlTip("").trim();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
child: const URLFormDialog(),
|
||||
);
|
||||
if (url != null) {
|
||||
_handleAddProfileFormURL(url);
|
||||
@@ -11,21 +11,21 @@ import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EditProfileView extends StatefulWidget {
|
||||
class EditProfile extends StatefulWidget {
|
||||
final Profile profile;
|
||||
final BuildContext context;
|
||||
|
||||
const EditProfileView({
|
||||
const EditProfile({
|
||||
super.key,
|
||||
required this.context,
|
||||
required this.profile,
|
||||
});
|
||||
|
||||
@override
|
||||
State<EditProfileView> createState() => _EditProfileViewState();
|
||||
State<EditProfile> createState() => _EditProfileState();
|
||||
}
|
||||
|
||||
class _EditProfileViewState extends State<EditProfileView> {
|
||||
class _EditProfileState extends State<EditProfile> {
|
||||
late TextEditingController labelController;
|
||||
late TextEditingController urlController;
|
||||
late TextEditingController autoUpdateDurationController;
|
||||
@@ -47,6 +47,7 @@ class _EditProfileViewState extends State<EditProfileView> {
|
||||
text: widget.profile.autoUpdateDuration.inMinutes.toString(),
|
||||
);
|
||||
appPath.getProfilePath(widget.profile.id).then((path) async {
|
||||
if (path == null) return;
|
||||
fileInfoNotifier.value = await _getFileInfo(path);
|
||||
});
|
||||
}
|
||||
@@ -142,40 +143,36 @@ class _EditProfileViewState extends State<EditProfileView> {
|
||||
_editProfileFile() async {
|
||||
if (rawText == null) {
|
||||
final profilePath = await appPath.getProfilePath(widget.profile.id);
|
||||
if (profilePath == null) return;
|
||||
final file = File(profilePath);
|
||||
if (await file.exists()) {
|
||||
rawText = await file.readAsString();
|
||||
}
|
||||
rawText = await file.readAsString();
|
||||
}
|
||||
if (!mounted) return;
|
||||
final title = widget.profile.label ?? widget.profile.id;
|
||||
final editorPage = EditorPage(
|
||||
title: title,
|
||||
content: rawText!,
|
||||
onSave: (context, _, content) {
|
||||
_handleSaveEdit(context, content);
|
||||
},
|
||||
onPop: (context, _, content) async {
|
||||
if (content == rawText) {
|
||||
return true;
|
||||
}
|
||||
final res = await globalState.showMessage(
|
||||
title: title,
|
||||
message: TextSpan(
|
||||
text: appLocalizations.hasCacheChange,
|
||||
),
|
||||
);
|
||||
if (res == true && context.mounted) {
|
||||
_handleSaveEdit(context, content);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
);
|
||||
final data = await BaseNavigator.modal<String>(
|
||||
final data = await BaseNavigator.push<String>(
|
||||
globalState.homeScaffoldKey.currentContext!,
|
||||
editorPage,
|
||||
EditorPage(
|
||||
title: title,
|
||||
content: rawText!,
|
||||
onSave: _handleSaveEdit,
|
||||
onPop: (context, data) async {
|
||||
if (data == rawText) {
|
||||
return true;
|
||||
}
|
||||
final res = await globalState.showMessage(
|
||||
title: title,
|
||||
message: TextSpan(
|
||||
text: appLocalizations.hasCacheChange,
|
||||
),
|
||||
);
|
||||
if (res == true && context.mounted) {
|
||||
_handleSaveEdit(context, data);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
),
|
||||
);
|
||||
if (data == null) {
|
||||
return;
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fl_clash/clash/core.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
@@ -8,27 +10,26 @@ import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class OverrideProfileView extends StatefulWidget {
|
||||
class OverrideProfile extends StatefulWidget {
|
||||
final String profileId;
|
||||
|
||||
const OverrideProfileView({
|
||||
const OverrideProfile({
|
||||
super.key,
|
||||
required this.profileId,
|
||||
});
|
||||
|
||||
@override
|
||||
State<OverrideProfileView> createState() => _OverrideProfileViewState();
|
||||
State<OverrideProfile> createState() => _OverrideProfileState();
|
||||
}
|
||||
|
||||
class _OverrideProfileViewState extends State<OverrideProfileView> {
|
||||
class _OverrideProfileState extends State<OverrideProfile> {
|
||||
final _controller = ScrollController();
|
||||
double _currentMaxWidth = 0;
|
||||
|
||||
_initState(WidgetRef ref) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Future.delayed(Duration(milliseconds: 300), () async {
|
||||
final rawConfig = await clashCore.getConfig(widget.profileId);
|
||||
final snippet = ClashConfigSnippet.fromJson(rawConfig);
|
||||
final snippet = await clashCore.getProfile(widget.profileId);
|
||||
final overrideData = ref.read(
|
||||
getProfileOverrideDataProvider(widget.profileId),
|
||||
);
|
||||
@@ -43,23 +44,19 @@ class _OverrideProfileViewState extends State<OverrideProfileView> {
|
||||
}
|
||||
|
||||
_handleSave(WidgetRef ref, OverrideData overrideData) {
|
||||
ref.read(needApplyProvider.notifier).value = true;
|
||||
ref.read(profilesProvider.notifier).updateProfile(
|
||||
widget.profileId,
|
||||
(state) => state.copyWith(
|
||||
overrideData: overrideData,
|
||||
),
|
||||
);
|
||||
globalState.appController.setupClashConfigDebounce();
|
||||
}
|
||||
|
||||
_handleDelete(WidgetRef ref) async {
|
||||
final res = await globalState.showMessage(
|
||||
title: appLocalizations.tip,
|
||||
message: TextSpan(
|
||||
text: appLocalizations.deleteMultipTip(
|
||||
appLocalizations.rule,
|
||||
),
|
||||
),
|
||||
message: TextSpan(text: appLocalizations.deleteRuleTip),
|
||||
);
|
||||
if (res != true) {
|
||||
return;
|
||||
@@ -84,9 +81,7 @@ class _OverrideProfileViewState extends State<OverrideProfileView> {
|
||||
},
|
||||
);
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
selectedRules: {},
|
||||
),
|
||||
(state) => state.copyWith(isEdit: false, selectedRules: {}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -121,39 +116,7 @@ class _OverrideProfileViewState extends State<OverrideProfileView> {
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Consumer(
|
||||
builder: (_, ref, child) {
|
||||
final scriptMode = ref.watch(scriptStateProvider
|
||||
.select((state) => state.realId != null));
|
||||
if (!scriptMode) {
|
||||
return SizedBox();
|
||||
}
|
||||
return child!;
|
||||
},
|
||||
child: ListItem(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 0,
|
||||
),
|
||||
title: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(Icons.info),
|
||||
Text(
|
||||
appLocalizations.overrideInvalidTip,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 8,
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
@@ -205,12 +168,16 @@ class _OverrideProfileViewState extends State<OverrideProfileView> {
|
||||
},
|
||||
child: Consumer(
|
||||
builder: (_, ref, ___) {
|
||||
final editCount = ref.watch(
|
||||
final vm2 = ref.watch(
|
||||
profileOverrideStateProvider.select(
|
||||
(state) => state.selectedRules.length,
|
||||
(state) => VM2(
|
||||
a: state.isEdit,
|
||||
b: state.selectedRules.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
final isEdit = editCount != 0;
|
||||
final isEdit = vm2.a;
|
||||
final editCount = vm2.b;
|
||||
return CommonScaffold(
|
||||
title: appLocalizations.override,
|
||||
body: _buildContent(),
|
||||
@@ -304,6 +271,7 @@ class _OverrideProfileViewState extends State<OverrideProfileView> {
|
||||
onExit: () {
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
isEdit: false,
|
||||
selectedRules: {},
|
||||
),
|
||||
);
|
||||
@@ -379,7 +347,7 @@ class RuleTitle extends ConsumerWidget {
|
||||
(state) {
|
||||
final overrideRule = state.overrideData?.rule;
|
||||
return VM3(
|
||||
a: state.selectedRules.isNotEmpty,
|
||||
a: state.isEdit,
|
||||
b: state.selectedRules.containsAll(
|
||||
overrideRule?.rules.map((item) => item.id).toSet() ?? {},
|
||||
),
|
||||
@@ -477,59 +445,87 @@ class RuleContent extends ConsumerWidget {
|
||||
required this.maxWidth,
|
||||
});
|
||||
|
||||
Widget _buildItem({
|
||||
required Rule rule,
|
||||
required bool isSelected,
|
||||
required VoidCallback onTab,
|
||||
required BuildContext context,
|
||||
}) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
),
|
||||
child: CommonCard(
|
||||
padding: EdgeInsets.zero,
|
||||
radius: 18,
|
||||
type: CommonCardType.filled,
|
||||
isSelected: isSelected,
|
||||
// decoration: BoxDecoration(
|
||||
// color: isSelected
|
||||
// ? context.colorScheme.secondaryContainer.opacity80
|
||||
// : context.colorScheme.surfaceContainer,
|
||||
// borderRadius: BorderRadius.circular(18),
|
||||
// ),
|
||||
onPressed: () {
|
||||
onTab();
|
||||
},
|
||||
child: ListTile(
|
||||
minTileHeight: 0,
|
||||
minVerticalPadding: 0,
|
||||
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
trailing: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CommonCheckBox(
|
||||
value: isSelected,
|
||||
isCircle: true,
|
||||
onChanged: (_) {
|
||||
onTab();
|
||||
},
|
||||
),
|
||||
),
|
||||
title: Text(rule.value),
|
||||
),
|
||||
),
|
||||
),
|
||||
Widget _proxyDecorator(
|
||||
Widget child,
|
||||
int index,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (_, Widget? child) {
|
||||
final double animValue = Curves.easeInOut.transform(animation.value);
|
||||
final double scale = lerpDouble(1, 1.02, animValue)!;
|
||||
return Transform.scale(
|
||||
scale: scale,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
_handleSelect(WidgetRef ref, String ruleId) {
|
||||
Widget _buildItem(Rule rule, int index) {
|
||||
return Consumer(
|
||||
builder: (context, ref, ___) {
|
||||
final vm2 = ref.watch(profileOverrideStateProvider.select(
|
||||
(item) => VM2(
|
||||
a: item.isEdit,
|
||||
b: item.selectedRules.contains(rule.id),
|
||||
),
|
||||
));
|
||||
final isEdit = vm2.a;
|
||||
final isSelected = vm2.b;
|
||||
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? context.colorScheme.secondaryContainer.opacity80
|
||||
: context.colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: ListTile(
|
||||
minTileHeight: 0,
|
||||
minVerticalPadding: 0,
|
||||
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
trailing: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: !isEdit
|
||||
? ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: const Icon(Icons.drag_handle),
|
||||
)
|
||||
: CommonCheckBox(
|
||||
value: isSelected,
|
||||
isCircle: true,
|
||||
onChanged: (_) {
|
||||
_handleSelect(ref, rule);
|
||||
},
|
||||
),
|
||||
),
|
||||
title: Text(rule.value),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_handleSelect(WidgetRef ref, ruleId) {
|
||||
if (!ref.read(profileOverrideStateProvider).isEdit) {
|
||||
return;
|
||||
}
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) {
|
||||
final newSelectedRules = Set<String>.from(state.selectedRules);
|
||||
@@ -547,21 +543,19 @@ class RuleContent extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final vm3 = ref.watch(
|
||||
final vm2 = ref.watch(
|
||||
profileOverrideStateProvider.select(
|
||||
(state) {
|
||||
final overrideRule = state.overrideData?.rule;
|
||||
return VM3(
|
||||
return VM2(
|
||||
a: overrideRule?.rules ?? [],
|
||||
b: overrideRule?.type ?? OverrideRuleType.added,
|
||||
c: state.selectedRules,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
final rules = vm3.a;
|
||||
final type = vm3.b;
|
||||
final selectedRules = vm3.c;
|
||||
final rules = vm2.a;
|
||||
final type = vm2.b;
|
||||
if (rules.isEmpty) {
|
||||
return SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
@@ -598,20 +592,31 @@ class RuleContent extends ConsumerWidget {
|
||||
tag: CacheTag.rules,
|
||||
itemBuilder: (context, index) {
|
||||
final rule = rules[index];
|
||||
return ReorderableDragStartListener(
|
||||
return GestureDetector(
|
||||
key: ObjectKey(rule),
|
||||
index: index,
|
||||
child: _buildItem(
|
||||
rule: rule,
|
||||
isSelected: selectedRules.contains(rule.id),
|
||||
onTab: () {
|
||||
_handleSelect(ref, rule.id);
|
||||
},
|
||||
context: context,
|
||||
rule,
|
||||
index,
|
||||
),
|
||||
onTap: () {
|
||||
_handleSelect(ref, rule.id);
|
||||
},
|
||||
onLongPress: () {
|
||||
if (ref.read(profileOverrideStateProvider).isEdit) {
|
||||
return;
|
||||
}
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
isEdit: true,
|
||||
selectedRules: {
|
||||
rule.id,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
proxyDecorator: proxyDecorator,
|
||||
proxyDecorator: _proxyDecorator,
|
||||
itemCount: rules.length,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
if (oldIndex < newIndex) {
|
||||
@@ -813,8 +818,7 @@ class _AddRuleDialogState extends State<AddRuleDialog> {
|
||||
? FormField(
|
||||
validator: (_) {
|
||||
if (_ruleProviderController.text.isEmpty) {
|
||||
return appLocalizations
|
||||
.emptyTip(appLocalizations.ruleProviders);
|
||||
return appLocalizations.ruleProviderEmptyTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@@ -837,8 +841,7 @@ class _AddRuleDialogState extends State<AddRuleDialog> {
|
||||
),
|
||||
validator: (_) {
|
||||
if (_contentController.text.isEmpty) {
|
||||
return appLocalizations
|
||||
.emptyTip(appLocalizations.content);
|
||||
return appLocalizations.contentEmptyTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@@ -850,8 +853,7 @@ class _AddRuleDialogState extends State<AddRuleDialog> {
|
||||
? FormField(
|
||||
validator: (_) {
|
||||
if (_subRuleController.text.isEmpty) {
|
||||
return appLocalizations
|
||||
.emptyTip(appLocalizations.subRule);
|
||||
return appLocalizations.subRuleEmptyTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@@ -870,9 +872,7 @@ class _AddRuleDialogState extends State<AddRuleDialog> {
|
||||
: FormField<String>(
|
||||
validator: (_) {
|
||||
if (_ruleTargetController.text.isEmpty) {
|
||||
return appLocalizations.emptyTip(
|
||||
appLocalizations.ruleTarget,
|
||||
);
|
||||
return appLocalizations.ruleTargetEmptyTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@@ -2,26 +2,25 @@ import 'dart:ui';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/profiles/edit_profile.dart';
|
||||
import 'package:fl_clash/fragments/profiles/override_profile.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/views/profiles/edit_profile.dart';
|
||||
import 'package:fl_clash/views/profiles/override_profile.dart';
|
||||
import 'package:fl_clash/views/profiles/scripts.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'add_profile.dart';
|
||||
|
||||
class ProfilesView extends StatefulWidget {
|
||||
const ProfilesView({super.key});
|
||||
class ProfilesFragment extends StatefulWidget {
|
||||
const ProfilesFragment({super.key});
|
||||
|
||||
@override
|
||||
State<ProfilesView> createState() => _ProfilesViewState();
|
||||
State<ProfilesFragment> createState() => _ProfilesFragmentState();
|
||||
}
|
||||
|
||||
class _ProfilesViewState extends State<ProfilesView> with PageMixin {
|
||||
class _ProfilesFragmentState extends State<ProfilesFragment> with PageMixin {
|
||||
Function? applyConfigDebounce;
|
||||
|
||||
_handleShowAddExtendPage() {
|
||||
@@ -30,7 +29,7 @@ class _ProfilesViewState extends State<ProfilesView> with PageMixin {
|
||||
builder: (_, type) {
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: AddProfileView(
|
||||
body: AddProfile(
|
||||
context: globalState.navigatorKey.currentState!.context,
|
||||
),
|
||||
title: "${appLocalizations.add}${appLocalizations.profile}",
|
||||
@@ -83,26 +82,6 @@ class _ProfilesViewState extends State<ProfilesView> with PageMixin {
|
||||
},
|
||||
icon: const Icon(Icons.sync),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showExtend(
|
||||
context,
|
||||
builder: (_, type) {
|
||||
return ScriptsView();
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: Consumer(
|
||||
builder: (context, ref, __) {
|
||||
final isScriptMode = ref.watch(
|
||||
scriptStateProvider.select((state) => state.realId != null));
|
||||
return Icon(
|
||||
Icons.functions,
|
||||
color: isScriptMode ? context.colorScheme.primary : null,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
final profiles = globalState.config.profiles;
|
||||
@@ -200,7 +179,7 @@ class ProfileItem extends StatelessWidget {
|
||||
final res = await globalState.showMessage(
|
||||
title: appLocalizations.tip,
|
||||
message: TextSpan(
|
||||
text: appLocalizations.deleteTip(appLocalizations.profile),
|
||||
text: appLocalizations.deleteProfileTip,
|
||||
),
|
||||
);
|
||||
if (res != true) {
|
||||
@@ -237,7 +216,7 @@ class ProfileItem extends StatelessWidget {
|
||||
builder: (_, type) {
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: EditProfileView(
|
||||
body: EditProfile(
|
||||
profile: profile,
|
||||
context: context,
|
||||
),
|
||||
@@ -307,12 +286,11 @@ class ProfileItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
_handlePushGenProfilePage(BuildContext context, String id) {
|
||||
final overrideProfileView = OverrideProfileView(
|
||||
profileId: id,
|
||||
);
|
||||
BaseNavigator.modal(
|
||||
BaseNavigator.push(
|
||||
context,
|
||||
overrideProfileView,
|
||||
OverrideProfile(
|
||||
profileId: id,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -494,10 +472,7 @@ class _ReorderableProfilesSheetState extends State<ReorderableProfilesSheet> {
|
||||
)
|
||||
],
|
||||
body: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 32,
|
||||
top: 16,
|
||||
),
|
||||
padding: EdgeInsets.only(bottom: 32),
|
||||
child: ReorderableListView.builder(
|
||||
buildDefaultDragHandles: false,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/proxies/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/views/proxies/common.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@@ -133,6 +133,7 @@ class ProxyCard extends StatelessWidget {
|
||||
ref.watch(getSelectedProxyNameProvider(groupName));
|
||||
return CommonCard(
|
||||
key: key,
|
||||
enterAnimated: true,
|
||||
onPressed: () {
|
||||
_changeProxy(ref);
|
||||
},
|
||||
@@ -176,8 +177,8 @@ class ProxyCard extends StatelessWidget {
|
||||
proxy.type,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: context
|
||||
.textTheme.bodySmall?.color?.opacity80,
|
||||
color:
|
||||
context.textTheme.bodySmall?.color?.opacity80,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -16,14 +16,14 @@ import 'common.dart';
|
||||
|
||||
typedef GroupNameProxiesMap = Map<String, List<Proxy>>;
|
||||
|
||||
class ProxiesListView extends StatefulWidget {
|
||||
const ProxiesListView({super.key});
|
||||
class ProxiesListFragment extends StatefulWidget {
|
||||
const ProxiesListFragment({super.key});
|
||||
|
||||
@override
|
||||
State<ProxiesListView> createState() => _ProxiesListViewState();
|
||||
State<ProxiesListFragment> createState() => _ProxiesListFragmentState();
|
||||
}
|
||||
|
||||
class _ProxiesListViewState extends State<ProxiesListView> {
|
||||
class _ProxiesListFragmentState extends State<ProxiesListFragment> {
|
||||
final _controller = ScrollController();
|
||||
final _headerStateNotifier = ValueNotifier<ProxiesListHeaderSelectorState>(
|
||||
const ProxiesListHeaderSelectorState(
|
||||
@@ -259,7 +259,7 @@ class _ProxiesListViewState extends State<ProxiesListView> {
|
||||
ref.watch(themeSettingProvider.select((state) => state.textScale));
|
||||
if (state.groupNames.isEmpty) {
|
||||
return NullStatus(
|
||||
label: appLocalizations.nullTip(appLocalizations.proxies),
|
||||
label: appLocalizations.nullProxies,
|
||||
);
|
||||
}
|
||||
final items = _buildItems(
|
||||
@@ -357,7 +357,10 @@ class ListHeader extends StatefulWidget {
|
||||
State<ListHeader> createState() => _ListHeaderState();
|
||||
}
|
||||
|
||||
class _ListHeaderState extends State<ListHeader> {
|
||||
class _ListHeaderState extends State<ListHeader>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _iconTurns;
|
||||
var isLock = false;
|
||||
|
||||
String get icon => widget.group.icon;
|
||||
@@ -382,6 +385,39 @@ class _ListHeaderState extends State<ListHeader> {
|
||||
widget.onChange(groupName);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
vsync: this,
|
||||
);
|
||||
_iconTurns = _animationController.drive(
|
||||
Tween<double>(begin: 0.0, end: 0.5),
|
||||
);
|
||||
if (isExpand) {
|
||||
_animationController.value = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(ListHeader oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.isExpand != widget.isExpand) {
|
||||
if (isExpand) {
|
||||
_animationController.forward();
|
||||
} else {
|
||||
_animationController.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildIcon() {
|
||||
return Consumer(
|
||||
builder: (_, ref, child) {
|
||||
@@ -563,13 +599,21 @@ class _ListHeaderState extends State<ListHeader> {
|
||||
SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: () {
|
||||
_handleChange(groupName);
|
||||
AnimatedBuilder(
|
||||
animation: _animationController.view,
|
||||
builder: (_, __) {
|
||||
return IconButton.filledTonal(
|
||||
onPressed: () {
|
||||
_handleChange(groupName);
|
||||
},
|
||||
icon: RotationTransition(
|
||||
turns: _iconTurns,
|
||||
child: const Icon(
|
||||
Icons.expand_more,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: CommonExpandIcon(
|
||||
expand: isExpand,
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
@@ -1,40 +1,32 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/proxies/list.dart';
|
||||
import 'package:fl_clash/fragments/proxies/providers.dart';
|
||||
import 'package:fl_clash/models/common.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/views/proxies/list.dart';
|
||||
import 'package:fl_clash/views/proxies/providers.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'common.dart';
|
||||
import 'setting.dart';
|
||||
import 'tab.dart';
|
||||
|
||||
class ProxiesView extends ConsumerStatefulWidget {
|
||||
const ProxiesView({super.key});
|
||||
class ProxiesFragment extends ConsumerStatefulWidget {
|
||||
const ProxiesFragment({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ProxiesView> createState() => _ProxiesViewState();
|
||||
ConsumerState<ProxiesFragment> createState() => _ProxiesFragmentState();
|
||||
}
|
||||
|
||||
class _ProxiesViewState extends ConsumerState<ProxiesView> with PageMixin {
|
||||
final GlobalKey<ProxiesTabViewState> _proxiesTabKey = GlobalKey();
|
||||
class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
|
||||
with PageMixin {
|
||||
final GlobalKey<ProxiesTabFragmentState> _proxiesTabKey = GlobalKey();
|
||||
bool _hasProviders = false;
|
||||
bool _isTab = false;
|
||||
|
||||
@override
|
||||
get actions => [
|
||||
if (_isTab)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_proxiesTabKey.currentState?.scrollToGroupSelected();
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.adjust,
|
||||
weight: 1,
|
||||
),
|
||||
),
|
||||
CommonPopupBox(
|
||||
targetBuilder: (open) {
|
||||
return IconButton(
|
||||
@@ -49,6 +41,7 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> with PageMixin {
|
||||
);
|
||||
},
|
||||
popup: CommonPopupMenu(
|
||||
minWidth: 180,
|
||||
items: [
|
||||
PopupMenuItemData(
|
||||
icon: Icons.tune,
|
||||
@@ -84,23 +77,30 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> with PageMixin {
|
||||
);
|
||||
},
|
||||
),
|
||||
if (!_isTab)
|
||||
PopupMenuItemData(
|
||||
icon: Icons.style_outlined,
|
||||
label: appLocalizations.iconConfiguration,
|
||||
onPressed: () {
|
||||
showExtend(
|
||||
context,
|
||||
builder: (_, type) {
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: const _IconConfigView(),
|
||||
title: appLocalizations.iconConfiguration,
|
||||
_isTab
|
||||
? PopupMenuItemData(
|
||||
icon: Icons.adjust_outlined,
|
||||
label: "聚焦",
|
||||
onPressed: () {
|
||||
_proxiesTabKey.currentState?.scrollToGroupSelected();
|
||||
},
|
||||
)
|
||||
: PopupMenuItemData(
|
||||
icon: Icons.style_outlined,
|
||||
label: appLocalizations.iconConfiguration,
|
||||
onPressed: () {
|
||||
showExtend(
|
||||
context,
|
||||
builder: (_, type) {
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: const _IconConfigView(),
|
||||
title: appLocalizations.iconConfiguration,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -115,9 +115,7 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> with PageMixin {
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
ref.read(proxiesQueryProvider.notifier).value = "";
|
||||
}
|
||||
ref.read(proxiesQueryProvider.notifier).value = "";
|
||||
});
|
||||
}
|
||||
|
||||
@@ -125,7 +123,10 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> with PageMixin {
|
||||
get floatingActionButton => _isTab
|
||||
? DelayTestButton(
|
||||
onClick: () async {
|
||||
await _proxiesTabKey.currentState?.delayTestCurrentGroup();
|
||||
await delayTest(
|
||||
currentTabProxies,
|
||||
currentTabTestUrl,
|
||||
);
|
||||
},
|
||||
)
|
||||
: null;
|
||||
@@ -208,12 +209,6 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> with PageMixin {
|
||||
_isTab = next.type == ProxiesType.tab;
|
||||
initPageState();
|
||||
return;
|
||||
} else {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
ref.read(proxiesQueryProvider.notifier).value = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -228,10 +223,10 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> with PageMixin {
|
||||
),
|
||||
);
|
||||
return switch (proxiesType) {
|
||||
ProxiesType.tab => ProxiesTabView(
|
||||
ProxiesType.tab => ProxiesTabFragment(
|
||||
key: _proxiesTabKey,
|
||||
),
|
||||
ProxiesType.list => const ProxiesListView(),
|
||||
ProxiesType.list => const ProxiesListFragment(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/common.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 '../../models/common.dart';
|
||||
import 'card.dart';
|
||||
import 'common.dart';
|
||||
|
||||
List<Proxy> currentTabProxies = [];
|
||||
String? currentTabTestUrl;
|
||||
|
||||
typedef GroupNameKeyMap = Map<String, GlobalObjectKey<ProxyGroupViewState>>;
|
||||
|
||||
class ProxiesTabView extends ConsumerStatefulWidget {
|
||||
const ProxiesTabView({super.key});
|
||||
class ProxiesTabFragment extends ConsumerStatefulWidget {
|
||||
const ProxiesTabFragment({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ProxiesTabView> createState() => ProxiesTabViewState();
|
||||
ConsumerState<ProxiesTabFragment> createState() => ProxiesTabFragmentState();
|
||||
}
|
||||
|
||||
class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
|
||||
class ProxiesTabFragmentState extends ConsumerState<ProxiesTabFragment>
|
||||
with TickerProviderStateMixin {
|
||||
TabController? _tabController;
|
||||
final _hasMoreButtonNotifier = ValueNotifier<bool>(false);
|
||||
@@ -43,15 +46,6 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
|
||||
_keyMap[currentGroupName]?.currentState?.scrollToSelected();
|
||||
}
|
||||
|
||||
delayTestCurrentGroup() async {
|
||||
final currentGroupName = globalState.appController.getCurrentGroupName();
|
||||
final currentState = _keyMap[currentGroupName]?.currentState;
|
||||
await delayTest(
|
||||
currentState?.proxies ?? [],
|
||||
currentState?.testUrl,
|
||||
);
|
||||
}
|
||||
|
||||
_buildMoreButton() {
|
||||
return Consumer(
|
||||
builder: (_, ref, ___) {
|
||||
@@ -133,6 +127,8 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
|
||||
return;
|
||||
}
|
||||
final currentGroup = currentGroups[groupIndex];
|
||||
currentTabProxies = currentGroup.all;
|
||||
currentTabTestUrl = currentGroup.testUrl;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
globalState.appController.updateCurrentGroupName(
|
||||
currentGroup.name,
|
||||
@@ -187,7 +183,7 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
|
||||
final groupNames = state.groupNames;
|
||||
if (groupNames.isEmpty) {
|
||||
return NullStatus(
|
||||
label: appLocalizations.nullTip(appLocalizations.proxies),
|
||||
label: appLocalizations.nullProxies,
|
||||
);
|
||||
}
|
||||
final GroupNameKeyMap keyMap = {};
|
||||
@@ -287,9 +283,6 @@ class ProxyGroupView extends ConsumerStatefulWidget {
|
||||
class ProxyGroupViewState extends ConsumerState<ProxyGroupView> {
|
||||
final _controller = ScrollController();
|
||||
|
||||
List<Proxy> proxies = [];
|
||||
String? testUrl;
|
||||
|
||||
String get groupName => widget.groupName;
|
||||
|
||||
@override
|
||||
@@ -302,12 +295,17 @@ class ProxyGroupViewState extends ConsumerState<ProxyGroupView> {
|
||||
if (_controller.position.maxScrollExtent == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final sortedProxies = globalState.appController.getSortProxies(
|
||||
currentTabProxies,
|
||||
currentTabTestUrl,
|
||||
);
|
||||
_controller.animateTo(
|
||||
min(
|
||||
16 +
|
||||
getScrollToSelectedOffset(
|
||||
groupName: groupName,
|
||||
proxies: proxies,
|
||||
proxies: sortedProxies,
|
||||
),
|
||||
_controller.position.maxScrollExtent,
|
||||
),
|
||||
@@ -326,9 +324,6 @@ class ProxyGroupViewState extends ConsumerState<ProxyGroupView> {
|
||||
proxies,
|
||||
state.testUrl,
|
||||
);
|
||||
this.proxies = sortedProxies;
|
||||
testUrl = state.testUrl;
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: CommonAutoHiddenScrollBar(
|
||||
@@ -23,8 +23,8 @@ class GeoItem {
|
||||
});
|
||||
}
|
||||
|
||||
class ResourcesView extends StatelessWidget {
|
||||
const ResourcesView({super.key});
|
||||
class Resources extends StatelessWidget {
|
||||
const Resources({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -1,6 +1,7 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
@@ -35,8 +36,8 @@ class FontFamilyItem {
|
||||
});
|
||||
}
|
||||
|
||||
class ThemeView extends StatelessWidget {
|
||||
const ThemeView({super.key});
|
||||
class ThemeFragment extends StatelessWidget {
|
||||
const ThemeFragment({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -202,12 +203,7 @@ class _PrimaryColorItemState extends ConsumerState<_PrimaryColorItem> {
|
||||
return;
|
||||
}
|
||||
final res = await globalState.showMessage(
|
||||
message: TextSpan(
|
||||
text: appLocalizations.deleteTip(
|
||||
appLocalizations.colorSchemes,
|
||||
),
|
||||
),
|
||||
);
|
||||
message: TextSpan(text: appLocalizations.deleteColorTip));
|
||||
if (res != true) {
|
||||
return;
|
||||
}
|
||||
@@ -245,11 +241,7 @@ class _PrimaryColorItemState extends ConsumerState<_PrimaryColorItem> {
|
||||
themeSettingProvider.select((state) => state.primaryColors.contains(res)),
|
||||
);
|
||||
if (isExists && mounted) {
|
||||
context.showNotifier(
|
||||
appLocalizations.existsTip(
|
||||
appLocalizations.colorSchemes,
|
||||
),
|
||||
);
|
||||
context.showNotifier(appLocalizations.colorExists);
|
||||
return;
|
||||
}
|
||||
ref.read(themeSettingProvider.notifier).updateState(
|
||||
@@ -1,33 +1,31 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/fragments/about.dart';
|
||||
import 'package:fl_clash/fragments/access.dart';
|
||||
import 'package:fl_clash/fragments/application_setting.dart';
|
||||
import 'package:fl_clash/fragments/config/config.dart';
|
||||
import 'package:fl_clash/fragments/hotkey.dart';
|
||||
import 'package:fl_clash/l10n/l10n.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/views/about.dart';
|
||||
import 'package:fl_clash/views/access.dart';
|
||||
import 'package:fl_clash/views/application_setting.dart';
|
||||
import 'package:fl_clash/views/config/config.dart';
|
||||
import 'package:fl_clash/views/hotkey.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path/path.dart' show dirname, join;
|
||||
|
||||
import 'backup_and_recovery.dart';
|
||||
import 'developer.dart';
|
||||
import 'theme.dart';
|
||||
import 'package:path/path.dart' show dirname, join;
|
||||
|
||||
class ToolsView extends ConsumerStatefulWidget {
|
||||
const ToolsView({super.key});
|
||||
class ToolsFragment extends ConsumerStatefulWidget {
|
||||
const ToolsFragment({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ToolsView> createState() => _ToolboxViewState();
|
||||
ConsumerState<ToolsFragment> createState() => _ToolboxFragmentState();
|
||||
}
|
||||
|
||||
class _ToolboxViewState extends ConsumerState<ToolsView> {
|
||||
class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
|
||||
_buildNavigationMenuItem(NavigationItem navigationItem) {
|
||||
return ListItem.open(
|
||||
leading: navigationItem.icon,
|
||||
@@ -37,7 +35,7 @@ class _ToolboxViewState extends ConsumerState<ToolsView> {
|
||||
: null,
|
||||
delegate: OpenDelegate(
|
||||
title: Intl.message(navigationItem.label.name),
|
||||
widget: navigationItem.view,
|
||||
widget: navigationItem.fragment,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -161,7 +159,7 @@ class _ThemeItem extends StatelessWidget {
|
||||
subtitle: Text(appLocalizations.themeDesc),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.theme,
|
||||
widget: const ThemeView(),
|
||||
widget: const ThemeFragment(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -195,7 +193,7 @@ class _HotkeyItem extends StatelessWidget {
|
||||
subtitle: Text(appLocalizations.hotkeyManagementDesc),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.hotkeyManagement,
|
||||
widget: const HotKeyView(),
|
||||
widget: const HotKeyFragment(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -231,7 +229,7 @@ class _AccessItem extends StatelessWidget {
|
||||
subtitle: Text(appLocalizations.accessControlDesc),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.appAccessControl,
|
||||
widget: const AccessView(),
|
||||
widget: const AccessFragment(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -248,7 +246,7 @@ class _ConfigItem extends StatelessWidget {
|
||||
subtitle: Text(appLocalizations.basicConfigDesc),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.override,
|
||||
widget: const ConfigView(),
|
||||
widget: const ConfigFragment(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -265,7 +263,7 @@ class _SettingItem extends StatelessWidget {
|
||||
subtitle: Text(appLocalizations.applicationDesc),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.application,
|
||||
widget: const ApplicationSettingView(),
|
||||
widget: const ApplicationSettingFragment(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -300,7 +298,7 @@ class _InfoItem extends StatelessWidget {
|
||||
title: Text(appLocalizations.about),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.about,
|
||||
widget: const AboutView(),
|
||||
widget: const AboutFragment(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -20,25 +20,7 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'en';
|
||||
|
||||
static String m0(label) =>
|
||||
"Are you sure you want to delete the selected ${label}?";
|
||||
|
||||
static String m1(label) =>
|
||||
"Are you sure you want to delete the current ${label}?";
|
||||
|
||||
static String m2(label) => "${label} cannot be empty";
|
||||
|
||||
static String m3(label) => "Current ${label} already exists";
|
||||
|
||||
static String m4(label) => "No ${label} at the moment";
|
||||
|
||||
static String m5(label) => "${label} must be a number";
|
||||
|
||||
static String m6(label) => "${label} must be between 1024 and 49151";
|
||||
|
||||
static String m7(count) => "${count} items have been selected";
|
||||
|
||||
static String m8(label) => "${label} must be a url";
|
||||
static String m0(count) => "${count} items have been selected";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
@@ -54,6 +36,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"The selected application will be excluded from VPN",
|
||||
),
|
||||
"account": MessageLookupByLibrary.simpleMessage("Account"),
|
||||
"accountTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Account cannot be empty",
|
||||
),
|
||||
"action": MessageLookupByLibrary.simpleMessage("Action"),
|
||||
"action_mode": MessageLookupByLibrary.simpleMessage("Switch mode"),
|
||||
"action_proxy": MessageLookupByLibrary.simpleMessage("System proxy"),
|
||||
@@ -164,7 +149,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"clearData": MessageLookupByLibrary.simpleMessage("Clear Data"),
|
||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("Export clipboard"),
|
||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("Clipboard import"),
|
||||
"color": MessageLookupByLibrary.simpleMessage("Color"),
|
||||
"colorExists": MessageLookupByLibrary.simpleMessage(
|
||||
"Current color already exists",
|
||||
),
|
||||
"colorSchemes": MessageLookupByLibrary.simpleMessage("Color schemes"),
|
||||
"columns": MessageLookupByLibrary.simpleMessage("Columns"),
|
||||
"compatible": MessageLookupByLibrary.simpleMessage("Compatibility mode"),
|
||||
@@ -179,6 +166,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
|
||||
"contactMe": MessageLookupByLibrary.simpleMessage("Contact me"),
|
||||
"content": MessageLookupByLibrary.simpleMessage("Content"),
|
||||
"contentEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Content cannot be empty",
|
||||
),
|
||||
"contentScheme": MessageLookupByLibrary.simpleMessage("Content"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
|
||||
"copyEnvVar": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -206,8 +196,15 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"delay": MessageLookupByLibrary.simpleMessage("Delay"),
|
||||
"delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"),
|
||||
"delete": MessageLookupByLibrary.simpleMessage("Delete"),
|
||||
"deleteMultipTip": m0,
|
||||
"deleteTip": m1,
|
||||
"deleteColorTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Are you sure you want to delete the current color?",
|
||||
),
|
||||
"deleteProfileTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Sure you want to delete the current profile?",
|
||||
),
|
||||
"deleteRuleTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Are you sure you want to delete the selected rule?",
|
||||
),
|
||||
"desc": MessageLookupByLibrary.simpleMessage(
|
||||
"A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.",
|
||||
),
|
||||
@@ -239,7 +236,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"domain": MessageLookupByLibrary.simpleMessage("Domain"),
|
||||
"download": MessageLookupByLibrary.simpleMessage("Download"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("Edit"),
|
||||
"emptyTip": m2,
|
||||
"en": MessageLookupByLibrary.simpleMessage("English"),
|
||||
"enableOverride": MessageLookupByLibrary.simpleMessage("Enable override"),
|
||||
"entries": MessageLookupByLibrary.simpleMessage(" entries"),
|
||||
@@ -247,7 +243,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"excludeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"When the app is in the background, the app is hidden from the recent task",
|
||||
),
|
||||
"existsTip": m3,
|
||||
"exit": MessageLookupByLibrary.simpleMessage("Exit"),
|
||||
"expand": MessageLookupByLibrary.simpleMessage("Standard"),
|
||||
"expirationTime": MessageLookupByLibrary.simpleMessage("Expiration time"),
|
||||
@@ -333,7 +328,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Intelligent selection",
|
||||
),
|
||||
"internet": MessageLookupByLibrary.simpleMessage("Internet"),
|
||||
"interval": MessageLookupByLibrary.simpleMessage("Interval"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -348,6 +342,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Tcp keep alive interval",
|
||||
),
|
||||
"key": MessageLookupByLibrary.simpleMessage("Key"),
|
||||
"keyExists": MessageLookupByLibrary.simpleMessage(
|
||||
"The current key already exists",
|
||||
),
|
||||
"language": MessageLookupByLibrary.simpleMessage("Language"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("Layout"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("Light"),
|
||||
@@ -384,7 +381,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Modify the default system exit event",
|
||||
),
|
||||
"minutes": MessageLookupByLibrary.simpleMessage("Minutes"),
|
||||
"mixedPort": MessageLookupByLibrary.simpleMessage("Mixed Port"),
|
||||
"mode": MessageLookupByLibrary.simpleMessage("Mode"),
|
||||
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Monochrome"),
|
||||
"months": MessageLookupByLibrary.simpleMessage("Months"),
|
||||
@@ -423,14 +419,22 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"noResolve": MessageLookupByLibrary.simpleMessage("No resolve IP"),
|
||||
"none": MessageLookupByLibrary.simpleMessage("none"),
|
||||
"notEmpty": MessageLookupByLibrary.simpleMessage("Cannot be empty"),
|
||||
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
|
||||
"The current proxy group cannot be selected.",
|
||||
),
|
||||
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"No connections",
|
||||
),
|
||||
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Unable to obtain core info",
|
||||
),
|
||||
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("No logs"),
|
||||
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"No profile, Please add a profile",
|
||||
),
|
||||
"nullTip": m4,
|
||||
"numberTip": m5,
|
||||
"nullProxies": MessageLookupByLibrary.simpleMessage("No proxies"),
|
||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"),
|
||||
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
|
||||
"onlyIcon": MessageLookupByLibrary.simpleMessage("Icon"),
|
||||
"onlyOtherApps": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -456,21 +460,18 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Turning it on will override the DNS options in the profile",
|
||||
),
|
||||
"overrideInvalidTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Does not take effect in script mode",
|
||||
),
|
||||
"overrideOriginRules": MessageLookupByLibrary.simpleMessage(
|
||||
"Override the original rule",
|
||||
),
|
||||
"palette": MessageLookupByLibrary.simpleMessage("Palette"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Password"),
|
||||
"passwordTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Password cannot be empty",
|
||||
),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("Paste"),
|
||||
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage(
|
||||
"Please bind WebDAV",
|
||||
),
|
||||
"pleaseEnterScriptName": MessageLookupByLibrary.simpleMessage(
|
||||
"Please enter a script name",
|
||||
),
|
||||
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
|
||||
"Please enter the admin password",
|
||||
),
|
||||
@@ -481,10 +482,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Please upload a valid QR code",
|
||||
),
|
||||
"port": MessageLookupByLibrary.simpleMessage("Port"),
|
||||
"portConflictTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Please enter a different port",
|
||||
),
|
||||
"portTip": m6,
|
||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Prioritize the use of DOH\'s http/3",
|
||||
),
|
||||
@@ -553,7 +550,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Override",
|
||||
),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("Recovery success"),
|
||||
"redirPort": MessageLookupByLibrary.simpleMessage("Redir Port"),
|
||||
"redo": MessageLookupByLibrary.simpleMessage("redo"),
|
||||
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
|
||||
"remote": MessageLookupByLibrary.simpleMessage("Remote"),
|
||||
@@ -564,7 +560,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Recovery data from WebDAV",
|
||||
),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("Remove"),
|
||||
"rename": MessageLookupByLibrary.simpleMessage("Rename"),
|
||||
"requests": MessageLookupByLibrary.simpleMessage("Requests"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"View recently request records",
|
||||
@@ -591,8 +586,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"ru": MessageLookupByLibrary.simpleMessage("Russian"),
|
||||
"rule": MessageLookupByLibrary.simpleMessage("Rule"),
|
||||
"ruleName": MessageLookupByLibrary.simpleMessage("Rule name"),
|
||||
"ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Rule provider cannot be empty",
|
||||
),
|
||||
"ruleProviders": MessageLookupByLibrary.simpleMessage("Rule providers"),
|
||||
"ruleTarget": MessageLookupByLibrary.simpleMessage("Rule target"),
|
||||
"ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Rule target cannot be empty",
|
||||
),
|
||||
"save": MessageLookupByLibrary.simpleMessage("Save"),
|
||||
"saveChanges": MessageLookupByLibrary.simpleMessage(
|
||||
"Do you want to save the changes?",
|
||||
@@ -600,12 +601,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"saveTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Are you sure you want to save?",
|
||||
),
|
||||
"script": MessageLookupByLibrary.simpleMessage("Script"),
|
||||
"search": MessageLookupByLibrary.simpleMessage("Search"),
|
||||
"seconds": MessageLookupByLibrary.simpleMessage("Seconds"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("Select all"),
|
||||
"selected": MessageLookupByLibrary.simpleMessage("Selected"),
|
||||
"selectedCountTitle": m7,
|
||||
"selectedCountTitle": m0,
|
||||
"settings": MessageLookupByLibrary.simpleMessage("Settings"),
|
||||
"show": MessageLookupByLibrary.simpleMessage("Show"),
|
||||
"shrink": MessageLookupByLibrary.simpleMessage("Shrink"),
|
||||
@@ -614,7 +614,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Start in the background",
|
||||
),
|
||||
"size": MessageLookupByLibrary.simpleMessage("Size"),
|
||||
"socksPort": MessageLookupByLibrary.simpleMessage("Socks Port"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("Sort"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("Source"),
|
||||
"sourceIp": MessageLookupByLibrary.simpleMessage("Source IP"),
|
||||
@@ -630,6 +629,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("Style"),
|
||||
"subRule": MessageLookupByLibrary.simpleMessage("Sub rule"),
|
||||
"subRuleEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Sub rule content cannot be empty",
|
||||
),
|
||||
"submit": MessageLookupByLibrary.simpleMessage("Submit"),
|
||||
"sync": MessageLookupByLibrary.simpleMessage("Sync"),
|
||||
"system": MessageLookupByLibrary.simpleMessage("System"),
|
||||
@@ -663,7 +665,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"toggle": MessageLookupByLibrary.simpleMessage("Toggle"),
|
||||
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("TonalSpot"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("Tools"),
|
||||
"tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxy Port"),
|
||||
"trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"),
|
||||
"tun": MessageLookupByLibrary.simpleMessage("TUN"),
|
||||
"tunDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -679,17 +680,18 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Remove extra delays such as handshaking",
|
||||
),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("Unknown"),
|
||||
"unnamed": MessageLookupByLibrary.simpleMessage("Unnamed"),
|
||||
"update": MessageLookupByLibrary.simpleMessage("Update"),
|
||||
"upload": MessageLookupByLibrary.simpleMessage("Upload"),
|
||||
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
||||
"urlDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Obtain profile through URL",
|
||||
),
|
||||
"urlTip": m8,
|
||||
"useHosts": MessageLookupByLibrary.simpleMessage("Use hosts"),
|
||||
"useSystemHosts": MessageLookupByLibrary.simpleMessage("Use system hosts"),
|
||||
"value": MessageLookupByLibrary.simpleMessage("Value"),
|
||||
"valueExists": MessageLookupByLibrary.simpleMessage(
|
||||
"The current value already exists",
|
||||
),
|
||||
"vibrantScheme": MessageLookupByLibrary.simpleMessage("Vibrant"),
|
||||
"view": MessageLookupByLibrary.simpleMessage("View"),
|
||||
"vpnDesc": MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
@@ -20,23 +20,7 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'ja';
|
||||
|
||||
static String m0(label) => "選択された${label}を削除してもよろしいですか?";
|
||||
|
||||
static String m1(label) => "現在の${label}を削除してもよろしいですか?";
|
||||
|
||||
static String m2(label) => "${label}は空欄にできません";
|
||||
|
||||
static String m3(label) => "現在の${label}は既に存在しています";
|
||||
|
||||
static String m4(label) => "現在${label}はありません";
|
||||
|
||||
static String m5(label) => "${label}は数字でなければなりません";
|
||||
|
||||
static String m6(label) => "${label} は 1024 から 49151 の間でなければなりません";
|
||||
|
||||
static String m7(count) => "${count} 項目が選択されています";
|
||||
|
||||
static String m8(label) => "${label}はURLである必要があります";
|
||||
static String m0(count) => "${count} 項目が選択されています";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
@@ -52,6 +36,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"選択したアプリをVPNから除外",
|
||||
),
|
||||
"account": MessageLookupByLibrary.simpleMessage("アカウント"),
|
||||
"accountTip": MessageLookupByLibrary.simpleMessage("アカウントは必須です"),
|
||||
"action": MessageLookupByLibrary.simpleMessage("アクション"),
|
||||
"action_mode": MessageLookupByLibrary.simpleMessage("モード切替"),
|
||||
"action_proxy": MessageLookupByLibrary.simpleMessage("システムプロキシ"),
|
||||
@@ -122,7 +107,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"clearData": MessageLookupByLibrary.simpleMessage("データを消去"),
|
||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("クリップボードにエクスポート"),
|
||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("クリップボードからインポート"),
|
||||
"color": MessageLookupByLibrary.simpleMessage("カラー"),
|
||||
"colorExists": MessageLookupByLibrary.simpleMessage("この色は既に存在します"),
|
||||
"colorSchemes": MessageLookupByLibrary.simpleMessage("カラースキーム"),
|
||||
"columns": MessageLookupByLibrary.simpleMessage("列"),
|
||||
"compatible": MessageLookupByLibrary.simpleMessage("互換モード"),
|
||||
@@ -135,6 +120,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("接続性:"),
|
||||
"contactMe": MessageLookupByLibrary.simpleMessage("連絡する"),
|
||||
"content": MessageLookupByLibrary.simpleMessage("内容"),
|
||||
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容は必須です"),
|
||||
"contentScheme": MessageLookupByLibrary.simpleMessage("コンテンツテーマ"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("コピー"),
|
||||
"copyEnvVar": MessageLookupByLibrary.simpleMessage("環境変数をコピー"),
|
||||
@@ -158,8 +144,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"delay": MessageLookupByLibrary.simpleMessage("遅延"),
|
||||
"delaySort": MessageLookupByLibrary.simpleMessage("遅延順"),
|
||||
"delete": MessageLookupByLibrary.simpleMessage("削除"),
|
||||
"deleteMultipTip": m0,
|
||||
"deleteTip": m1,
|
||||
"deleteColorTip": MessageLookupByLibrary.simpleMessage("現在の色を削除しますか?"),
|
||||
"deleteProfileTip": MessageLookupByLibrary.simpleMessage(
|
||||
"現在のプロファイルを削除しますか?",
|
||||
),
|
||||
"deleteRuleTip": MessageLookupByLibrary.simpleMessage("選択したルールを削除しますか?"),
|
||||
"desc": MessageLookupByLibrary.simpleMessage(
|
||||
"ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。",
|
||||
),
|
||||
@@ -181,7 +170,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"domain": MessageLookupByLibrary.simpleMessage("ドメイン"),
|
||||
"download": MessageLookupByLibrary.simpleMessage("ダウンロード"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("編集"),
|
||||
"emptyTip": m2,
|
||||
"en": MessageLookupByLibrary.simpleMessage("英語"),
|
||||
"enableOverride": MessageLookupByLibrary.simpleMessage("上書きを有効化"),
|
||||
"entries": MessageLookupByLibrary.simpleMessage(" エントリ"),
|
||||
@@ -189,7 +177,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"excludeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"アプリがバックグラウンド時に最近のタスクから非表示",
|
||||
),
|
||||
"existsTip": m3,
|
||||
"exit": MessageLookupByLibrary.simpleMessage("終了"),
|
||||
"expand": MessageLookupByLibrary.simpleMessage("標準"),
|
||||
"expirationTime": MessageLookupByLibrary.simpleMessage("有効期限"),
|
||||
@@ -251,7 +238,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("正しいホットキーを入力"),
|
||||
"intelligentSelected": MessageLookupByLibrary.simpleMessage("インテリジェント選択"),
|
||||
"internet": MessageLookupByLibrary.simpleMessage("インターネット"),
|
||||
"interval": MessageLookupByLibrary.simpleMessage("インターバル"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"),
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"),
|
||||
@@ -262,6 +248,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"TCPキープアライブ間隔",
|
||||
),
|
||||
"key": MessageLookupByLibrary.simpleMessage("キー"),
|
||||
"keyExists": MessageLookupByLibrary.simpleMessage("現在のキーは既に存在します"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("言語"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("レイアウト"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("ライト"),
|
||||
@@ -288,7 +275,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"システムの終了イベントを変更",
|
||||
),
|
||||
"minutes": MessageLookupByLibrary.simpleMessage("分"),
|
||||
"mixedPort": MessageLookupByLibrary.simpleMessage("混合ポート"),
|
||||
"mode": MessageLookupByLibrary.simpleMessage("モード"),
|
||||
"monochromeScheme": MessageLookupByLibrary.simpleMessage("モノクローム"),
|
||||
"months": MessageLookupByLibrary.simpleMessage("月"),
|
||||
@@ -319,14 +305,18 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"noResolve": MessageLookupByLibrary.simpleMessage("IPを解決しない"),
|
||||
"none": MessageLookupByLibrary.simpleMessage("なし"),
|
||||
"notEmpty": MessageLookupByLibrary.simpleMessage("空欄不可"),
|
||||
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
|
||||
"現在のプロキシグループは選択できません",
|
||||
),
|
||||
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage("接続なし"),
|
||||
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage("コア情報を取得できません"),
|
||||
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("ログがありません"),
|
||||
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"プロファイルがありません。追加してください",
|
||||
),
|
||||
"nullTip": m4,
|
||||
"numberTip": m5,
|
||||
"nullProxies": MessageLookupByLibrary.simpleMessage("プロキシなし"),
|
||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("リクエストなし"),
|
||||
"oneColumn": MessageLookupByLibrary.simpleMessage("1列"),
|
||||
"onlyIcon": MessageLookupByLibrary.simpleMessage("アイコンのみ"),
|
||||
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("サードパーティアプリのみ"),
|
||||
@@ -344,19 +334,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"有効化するとプロファイルのDNS設定を上書き",
|
||||
),
|
||||
"overrideInvalidTip": MessageLookupByLibrary.simpleMessage(
|
||||
"スクリプトモードでは有効になりません",
|
||||
),
|
||||
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを上書き"),
|
||||
"palette": MessageLookupByLibrary.simpleMessage("パレット"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("パスワード"),
|
||||
"passwordTip": MessageLookupByLibrary.simpleMessage("パスワードは必須です"),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("貼り付け"),
|
||||
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage(
|
||||
"WebDAVをバインドしてください",
|
||||
),
|
||||
"pleaseEnterScriptName": MessageLookupByLibrary.simpleMessage(
|
||||
"スクリプト名を入力してください",
|
||||
),
|
||||
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
|
||||
"管理者パスワードを入力",
|
||||
),
|
||||
@@ -367,8 +352,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"有効なQRコードをアップロードしてください",
|
||||
),
|
||||
"port": MessageLookupByLibrary.simpleMessage("ポート"),
|
||||
"portConflictTip": MessageLookupByLibrary.simpleMessage("別のポートを入力してください"),
|
||||
"portTip": m6,
|
||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage("DOHのHTTP/3を優先使用"),
|
||||
"pressKeyboard": MessageLookupByLibrary.simpleMessage("キーボードを押してください"),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("プレビュー"),
|
||||
@@ -419,7 +402,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"オーバーライド",
|
||||
),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("復元成功"),
|
||||
"redirPort": MessageLookupByLibrary.simpleMessage("Redirポート"),
|
||||
"redo": MessageLookupByLibrary.simpleMessage("やり直す"),
|
||||
"regExp": MessageLookupByLibrary.simpleMessage("正規表現"),
|
||||
"remote": MessageLookupByLibrary.simpleMessage("リモート"),
|
||||
@@ -430,7 +412,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"WebDAVからデータを復元",
|
||||
),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("削除"),
|
||||
"rename": MessageLookupByLibrary.simpleMessage("リネーム"),
|
||||
"requests": MessageLookupByLibrary.simpleMessage("リクエスト"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage("最近のリクエスト記録を表示"),
|
||||
"reset": MessageLookupByLibrary.simpleMessage("リセット"),
|
||||
@@ -451,24 +432,26 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"ru": MessageLookupByLibrary.simpleMessage("ロシア語"),
|
||||
"rule": MessageLookupByLibrary.simpleMessage("ルール"),
|
||||
"ruleName": MessageLookupByLibrary.simpleMessage("ルール名"),
|
||||
"ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"ルールプロバイダーは必須です",
|
||||
),
|
||||
"ruleProviders": MessageLookupByLibrary.simpleMessage("ルールプロバイダー"),
|
||||
"ruleTarget": MessageLookupByLibrary.simpleMessage("ルール対象"),
|
||||
"ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage("ルール対象は必須です"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("保存"),
|
||||
"saveChanges": MessageLookupByLibrary.simpleMessage("変更を保存しますか?"),
|
||||
"saveTip": MessageLookupByLibrary.simpleMessage("保存してもよろしいですか?"),
|
||||
"script": MessageLookupByLibrary.simpleMessage("スクリプト"),
|
||||
"search": MessageLookupByLibrary.simpleMessage("検索"),
|
||||
"seconds": MessageLookupByLibrary.simpleMessage("秒"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("すべて選択"),
|
||||
"selected": MessageLookupByLibrary.simpleMessage("選択済み"),
|
||||
"selectedCountTitle": m7,
|
||||
"selectedCountTitle": m0,
|
||||
"settings": MessageLookupByLibrary.simpleMessage("設定"),
|
||||
"show": MessageLookupByLibrary.simpleMessage("表示"),
|
||||
"shrink": MessageLookupByLibrary.simpleMessage("縮小"),
|
||||
"silentLaunch": MessageLookupByLibrary.simpleMessage("バックグラウンド起動"),
|
||||
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("バックグラウンドで起動"),
|
||||
"size": MessageLookupByLibrary.simpleMessage("サイズ"),
|
||||
"socksPort": MessageLookupByLibrary.simpleMessage("Socksポート"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("並び替え"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("ソース"),
|
||||
"sourceIp": MessageLookupByLibrary.simpleMessage("送信元IP"),
|
||||
@@ -482,6 +465,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("VPNを停止中..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("スタイル"),
|
||||
"subRule": MessageLookupByLibrary.simpleMessage("サブルール"),
|
||||
"subRuleEmptyTip": MessageLookupByLibrary.simpleMessage("サブルールの内容は必須です"),
|
||||
"submit": MessageLookupByLibrary.simpleMessage("送信"),
|
||||
"sync": MessageLookupByLibrary.simpleMessage("同期"),
|
||||
"system": MessageLookupByLibrary.simpleMessage("システム"),
|
||||
@@ -509,7 +493,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"toggle": MessageLookupByLibrary.simpleMessage("トグル"),
|
||||
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("トーンスポット"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("ツール"),
|
||||
"tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxyポート"),
|
||||
"trafficUsage": MessageLookupByLibrary.simpleMessage("トラフィック使用量"),
|
||||
"tun": MessageLookupByLibrary.simpleMessage("TUN"),
|
||||
"tunDesc": MessageLookupByLibrary.simpleMessage("管理者モードでのみ有効"),
|
||||
@@ -523,15 +506,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"ハンドシェイクなどの余分な遅延を削除",
|
||||
),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("不明"),
|
||||
"unnamed": MessageLookupByLibrary.simpleMessage("無題"),
|
||||
"update": MessageLookupByLibrary.simpleMessage("更新"),
|
||||
"upload": MessageLookupByLibrary.simpleMessage("アップロード"),
|
||||
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
||||
"urlDesc": MessageLookupByLibrary.simpleMessage("URL経由でプロファイルを取得"),
|
||||
"urlTip": m8,
|
||||
"useHosts": MessageLookupByLibrary.simpleMessage("ホストを使用"),
|
||||
"useSystemHosts": MessageLookupByLibrary.simpleMessage("システムホストを使用"),
|
||||
"value": MessageLookupByLibrary.simpleMessage("値"),
|
||||
"valueExists": MessageLookupByLibrary.simpleMessage("現在の値は既に存在します"),
|
||||
"vibrantScheme": MessageLookupByLibrary.simpleMessage("ビブラント"),
|
||||
"view": MessageLookupByLibrary.simpleMessage("表示"),
|
||||
"vpnDesc": MessageLookupByLibrary.simpleMessage("VPN関連設定の変更"),
|
||||
|
||||
@@ -20,24 +20,7 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'ru';
|
||||
|
||||
static String m0(label) =>
|
||||
"Вы уверены, что хотите удалить выбранные ${label}?";
|
||||
|
||||
static String m1(label) => "Вы уверены, что хотите удалить текущий ${label}?";
|
||||
|
||||
static String m2(label) => "${label} не может быть пустым";
|
||||
|
||||
static String m3(label) => "Текущий ${label} уже существует";
|
||||
|
||||
static String m4(label) => "Сейчас ${label} нет";
|
||||
|
||||
static String m5(label) => "${label} должно быть числом";
|
||||
|
||||
static String m6(label) => "${label} должен быть числом от 1024 до 49151";
|
||||
|
||||
static String m7(count) => "Выбрано ${count} элементов";
|
||||
|
||||
static String m8(label) => "${label} должен быть URL";
|
||||
static String m0(count) => "Выбрано ${count} элементов";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
@@ -53,6 +36,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Выбранные приложения будут исключены из VPN",
|
||||
),
|
||||
"account": MessageLookupByLibrary.simpleMessage("Аккаунт"),
|
||||
"accountTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Аккаунт не может быть пустым",
|
||||
),
|
||||
"action": MessageLookupByLibrary.simpleMessage("Действие"),
|
||||
"action_mode": MessageLookupByLibrary.simpleMessage("Переключить режим"),
|
||||
"action_proxy": MessageLookupByLibrary.simpleMessage("Системный прокси"),
|
||||
@@ -169,7 +155,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"clipboardImport": MessageLookupByLibrary.simpleMessage(
|
||||
"Импорт из буфера обмена",
|
||||
),
|
||||
"color": MessageLookupByLibrary.simpleMessage("Цвет"),
|
||||
"colorExists": MessageLookupByLibrary.simpleMessage(
|
||||
"Этот цвет уже существует",
|
||||
),
|
||||
"colorSchemes": MessageLookupByLibrary.simpleMessage("Цветовые схемы"),
|
||||
"columns": MessageLookupByLibrary.simpleMessage("Столбцы"),
|
||||
"compatible": MessageLookupByLibrary.simpleMessage("Режим совместимости"),
|
||||
@@ -184,6 +172,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("Связь:"),
|
||||
"contactMe": MessageLookupByLibrary.simpleMessage("Свяжитесь со мной"),
|
||||
"content": MessageLookupByLibrary.simpleMessage("Содержание"),
|
||||
"contentEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Содержание не может быть пустым",
|
||||
),
|
||||
"contentScheme": MessageLookupByLibrary.simpleMessage("Контентная тема"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("Копировать"),
|
||||
"copyEnvVar": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -213,8 +204,15 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"delay": MessageLookupByLibrary.simpleMessage("Задержка"),
|
||||
"delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"),
|
||||
"delete": MessageLookupByLibrary.simpleMessage("Удалить"),
|
||||
"deleteMultipTip": m0,
|
||||
"deleteTip": m1,
|
||||
"deleteColorTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Удалить текущий цвет?",
|
||||
),
|
||||
"deleteProfileTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Вы уверены, что хотите удалить текущий профиль?",
|
||||
),
|
||||
"deleteRuleTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Вы уверены, что хотите удалить выбранное правило?",
|
||||
),
|
||||
"desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Многоплатформенный прокси-клиент на основе ClashMeta, простой и удобный в использовании, с открытым исходным кодом и без рекламы.",
|
||||
),
|
||||
@@ -248,7 +246,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"domain": MessageLookupByLibrary.simpleMessage("Домен"),
|
||||
"download": MessageLookupByLibrary.simpleMessage("Скачивание"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("Редактировать"),
|
||||
"emptyTip": m2,
|
||||
"en": MessageLookupByLibrary.simpleMessage("Английский"),
|
||||
"enableOverride": MessageLookupByLibrary.simpleMessage(
|
||||
"Включить переопределение",
|
||||
@@ -260,7 +257,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"excludeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Когда приложение находится в фоновом режиме, оно скрыто из последних задач",
|
||||
),
|
||||
"existsTip": m3,
|
||||
"exit": MessageLookupByLibrary.simpleMessage("Выход"),
|
||||
"expand": MessageLookupByLibrary.simpleMessage("Стандартный"),
|
||||
"expirationTime": MessageLookupByLibrary.simpleMessage("Время истечения"),
|
||||
@@ -354,7 +350,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Интеллектуальный выбор",
|
||||
),
|
||||
"internet": MessageLookupByLibrary.simpleMessage("Интернет"),
|
||||
"interval": MessageLookupByLibrary.simpleMessage("Интервал"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("Внутренний IP"),
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -369,6 +364,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Интервал поддержания TCP-соединения",
|
||||
),
|
||||
"key": MessageLookupByLibrary.simpleMessage("Ключ"),
|
||||
"keyExists": MessageLookupByLibrary.simpleMessage(
|
||||
"Текущий ключ уже существует",
|
||||
),
|
||||
"language": MessageLookupByLibrary.simpleMessage("Язык"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("Макет"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("Светлый"),
|
||||
@@ -409,7 +407,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Изменить стандартное событие выхода из системы",
|
||||
),
|
||||
"minutes": MessageLookupByLibrary.simpleMessage("Минут"),
|
||||
"mixedPort": MessageLookupByLibrary.simpleMessage("Смешанный порт"),
|
||||
"mode": MessageLookupByLibrary.simpleMessage("Режим"),
|
||||
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Монохром"),
|
||||
"months": MessageLookupByLibrary.simpleMessage("Месяцев"),
|
||||
@@ -450,14 +447,22 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"noResolve": MessageLookupByLibrary.simpleMessage("Не разрешать IP"),
|
||||
"none": MessageLookupByLibrary.simpleMessage("Нет"),
|
||||
"notEmpty": MessageLookupByLibrary.simpleMessage("Не может быть пустым"),
|
||||
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Текущая группа прокси не может быть выбрана.",
|
||||
),
|
||||
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Нет соединений",
|
||||
),
|
||||
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Не удалось получить информацию о ядре",
|
||||
),
|
||||
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("Нет логов"),
|
||||
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Нет профиля, пожалуйста, добавьте профиль",
|
||||
),
|
||||
"nullTip": m4,
|
||||
"numberTip": m5,
|
||||
"nullProxies": MessageLookupByLibrary.simpleMessage("Нет прокси"),
|
||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("Нет запросов"),
|
||||
"oneColumn": MessageLookupByLibrary.simpleMessage("Один столбец"),
|
||||
"onlyIcon": MessageLookupByLibrary.simpleMessage("Только иконка"),
|
||||
"onlyOtherApps": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -485,21 +490,18 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Включение переопределит настройки DNS в профиле",
|
||||
),
|
||||
"overrideInvalidTip": MessageLookupByLibrary.simpleMessage(
|
||||
"В скриптовом режиме не действует",
|
||||
),
|
||||
"overrideOriginRules": MessageLookupByLibrary.simpleMessage(
|
||||
"Переопределить оригинальное правило",
|
||||
),
|
||||
"palette": MessageLookupByLibrary.simpleMessage("Палитра"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Пароль"),
|
||||
"passwordTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Пароль не может быть пустым",
|
||||
),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("Вставить"),
|
||||
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, привяжите WebDAV",
|
||||
),
|
||||
"pleaseEnterScriptName": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, введите название скрипта",
|
||||
),
|
||||
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, введите пароль администратора",
|
||||
),
|
||||
@@ -510,10 +512,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Пожалуйста, загрузите действительный QR-код",
|
||||
),
|
||||
"port": MessageLookupByLibrary.simpleMessage("Порт"),
|
||||
"portConflictTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Введите другой порт",
|
||||
),
|
||||
"portTip": m6,
|
||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Приоритетное использование HTTP/3 для DOH",
|
||||
),
|
||||
@@ -588,7 +586,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановление успешно",
|
||||
),
|
||||
"redirPort": MessageLookupByLibrary.simpleMessage("Redir-порт"),
|
||||
"redo": MessageLookupByLibrary.simpleMessage("Повторить"),
|
||||
"regExp": MessageLookupByLibrary.simpleMessage("Регулярное выражение"),
|
||||
"remote": MessageLookupByLibrary.simpleMessage("Удаленный"),
|
||||
@@ -599,7 +596,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Восстановление данных с WebDAV",
|
||||
),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("Удалить"),
|
||||
"rename": MessageLookupByLibrary.simpleMessage("Переименовать"),
|
||||
"requests": MessageLookupByLibrary.simpleMessage("Запросы"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Просмотр последних записей запросов",
|
||||
@@ -630,19 +626,24 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"ru": MessageLookupByLibrary.simpleMessage("Русский"),
|
||||
"rule": MessageLookupByLibrary.simpleMessage("Правило"),
|
||||
"ruleName": MessageLookupByLibrary.simpleMessage("Название правила"),
|
||||
"ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Поставщик правил не может быть пустым",
|
||||
),
|
||||
"ruleProviders": MessageLookupByLibrary.simpleMessage("Провайдеры правил"),
|
||||
"ruleTarget": MessageLookupByLibrary.simpleMessage("Цель правила"),
|
||||
"ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Цель правила не может быть пустой",
|
||||
),
|
||||
"save": MessageLookupByLibrary.simpleMessage("Сохранить"),
|
||||
"saveChanges": MessageLookupByLibrary.simpleMessage("Сохранить изменения?"),
|
||||
"saveTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Вы уверены, что хотите сохранить?",
|
||||
),
|
||||
"script": MessageLookupByLibrary.simpleMessage("Скрипт"),
|
||||
"search": MessageLookupByLibrary.simpleMessage("Поиск"),
|
||||
"seconds": MessageLookupByLibrary.simpleMessage("Секунд"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("Выбрать все"),
|
||||
"selected": MessageLookupByLibrary.simpleMessage("Выбрано"),
|
||||
"selectedCountTitle": m7,
|
||||
"selectedCountTitle": m0,
|
||||
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
|
||||
"show": MessageLookupByLibrary.simpleMessage("Показать"),
|
||||
"shrink": MessageLookupByLibrary.simpleMessage("Сжать"),
|
||||
@@ -651,7 +652,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Запуск в фоновом режиме",
|
||||
),
|
||||
"size": MessageLookupByLibrary.simpleMessage("Размер"),
|
||||
"socksPort": MessageLookupByLibrary.simpleMessage("Socks-порт"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("Сортировка"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("Источник"),
|
||||
"sourceIp": MessageLookupByLibrary.simpleMessage("Исходный IP"),
|
||||
@@ -667,6 +667,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("Остановка VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("Стиль"),
|
||||
"subRule": MessageLookupByLibrary.simpleMessage("Подправило"),
|
||||
"subRuleEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Содержание подправила не может быть пустым",
|
||||
),
|
||||
"submit": MessageLookupByLibrary.simpleMessage("Отправить"),
|
||||
"sync": MessageLookupByLibrary.simpleMessage("Синхронизация"),
|
||||
"system": MessageLookupByLibrary.simpleMessage("Система"),
|
||||
@@ -700,7 +703,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"toggle": MessageLookupByLibrary.simpleMessage("Переключить"),
|
||||
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("Тональный акцент"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("Инструменты"),
|
||||
"tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxy-порт"),
|
||||
"trafficUsage": MessageLookupByLibrary.simpleMessage(
|
||||
"Использование трафика",
|
||||
),
|
||||
@@ -720,19 +722,20 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Убрать дополнительные задержки, такие как рукопожатие",
|
||||
),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("Неизвестно"),
|
||||
"unnamed": MessageLookupByLibrary.simpleMessage("Без имени"),
|
||||
"update": MessageLookupByLibrary.simpleMessage("Обновить"),
|
||||
"upload": MessageLookupByLibrary.simpleMessage("Загрузка"),
|
||||
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
||||
"urlDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Получить профиль через URL",
|
||||
),
|
||||
"urlTip": m8,
|
||||
"useHosts": MessageLookupByLibrary.simpleMessage("Использовать hosts"),
|
||||
"useSystemHosts": MessageLookupByLibrary.simpleMessage(
|
||||
"Использовать системные hosts",
|
||||
),
|
||||
"value": MessageLookupByLibrary.simpleMessage("Значение"),
|
||||
"valueExists": MessageLookupByLibrary.simpleMessage(
|
||||
"Текущее значение уже существует",
|
||||
),
|
||||
"vibrantScheme": MessageLookupByLibrary.simpleMessage("Яркие"),
|
||||
"view": MessageLookupByLibrary.simpleMessage("Просмотр"),
|
||||
"vpnDesc": MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
@@ -20,23 +20,7 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'zh_CN';
|
||||
|
||||
static String m0(label) => "确定删除选中的${label}吗?";
|
||||
|
||||
static String m1(label) => "确定删除当前${label}吗?";
|
||||
|
||||
static String m2(label) => "${label}不能为空";
|
||||
|
||||
static String m3(label) => "${label}当前已存在";
|
||||
|
||||
static String m4(label) => "暂无${label}";
|
||||
|
||||
static String m5(label) => "${label}必须为数字";
|
||||
|
||||
static String m6(label) => "${label} 必须在 1024 到 49151 之间";
|
||||
|
||||
static String m7(count) => "已选择 ${count} 项";
|
||||
|
||||
static String m8(label) => "${label}必须为URL";
|
||||
static String m0(count) => "已选择 ${count} 项";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
@@ -50,6 +34,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"选中应用将会被排除在VPN之外",
|
||||
),
|
||||
"account": MessageLookupByLibrary.simpleMessage("账号"),
|
||||
"accountTip": MessageLookupByLibrary.simpleMessage("账号不能为空"),
|
||||
"action": MessageLookupByLibrary.simpleMessage("操作"),
|
||||
"action_mode": MessageLookupByLibrary.simpleMessage("切换模式"),
|
||||
"action_proxy": MessageLookupByLibrary.simpleMessage("系统代理"),
|
||||
@@ -112,7 +97,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"clearData": MessageLookupByLibrary.simpleMessage("清除数据"),
|
||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
|
||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
|
||||
"color": MessageLookupByLibrary.simpleMessage("颜色"),
|
||||
"colorExists": MessageLookupByLibrary.simpleMessage("该颜色已存在"),
|
||||
"colorSchemes": MessageLookupByLibrary.simpleMessage("配色方案"),
|
||||
"columns": MessageLookupByLibrary.simpleMessage("列数"),
|
||||
"compatible": MessageLookupByLibrary.simpleMessage("兼容模式"),
|
||||
@@ -125,6 +110,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
||||
"contactMe": MessageLookupByLibrary.simpleMessage("联系我"),
|
||||
"content": MessageLookupByLibrary.simpleMessage("内容"),
|
||||
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容不能为空"),
|
||||
"contentScheme": MessageLookupByLibrary.simpleMessage("内容主题"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
||||
"copyEnvVar": MessageLookupByLibrary.simpleMessage("复制环境变量"),
|
||||
@@ -146,8 +132,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"delay": MessageLookupByLibrary.simpleMessage("延迟"),
|
||||
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
|
||||
"delete": MessageLookupByLibrary.simpleMessage("删除"),
|
||||
"deleteMultipTip": m0,
|
||||
"deleteTip": m1,
|
||||
"deleteColorTip": MessageLookupByLibrary.simpleMessage("确定删除当前颜色吗?"),
|
||||
"deleteProfileTip": MessageLookupByLibrary.simpleMessage("确定要删除当前配置吗?"),
|
||||
"deleteRuleTip": MessageLookupByLibrary.simpleMessage("确定要删除选中的规则吗?"),
|
||||
"desc": MessageLookupByLibrary.simpleMessage(
|
||||
"基于ClashMeta的多平台代理客户端,简单易用,开源无广告。",
|
||||
),
|
||||
@@ -167,13 +154,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"domain": MessageLookupByLibrary.simpleMessage("域名"),
|
||||
"download": MessageLookupByLibrary.simpleMessage("下载"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("编辑"),
|
||||
"emptyTip": m2,
|
||||
"en": MessageLookupByLibrary.simpleMessage("英语"),
|
||||
"enableOverride": MessageLookupByLibrary.simpleMessage("启用覆写"),
|
||||
"entries": MessageLookupByLibrary.simpleMessage("个条目"),
|
||||
"exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"),
|
||||
"excludeDesc": MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"),
|
||||
"existsTip": m3,
|
||||
"exit": MessageLookupByLibrary.simpleMessage("退出"),
|
||||
"expand": MessageLookupByLibrary.simpleMessage("标准"),
|
||||
"expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"),
|
||||
@@ -227,7 +212,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("请输入正确的快捷键"),
|
||||
"intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"),
|
||||
"internet": MessageLookupByLibrary.simpleMessage("互联网"),
|
||||
"interval": MessageLookupByLibrary.simpleMessage("间隔"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
|
||||
@@ -236,6 +220,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
|
||||
"keepAliveIntervalDesc": MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
|
||||
"key": MessageLookupByLibrary.simpleMessage("键"),
|
||||
"keyExists": MessageLookupByLibrary.simpleMessage("当前键已存在"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("语言"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("布局"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("浅色"),
|
||||
@@ -260,7 +245,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
|
||||
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
|
||||
"minutes": MessageLookupByLibrary.simpleMessage("分钟"),
|
||||
"mixedPort": MessageLookupByLibrary.simpleMessage("混合端口"),
|
||||
"mode": MessageLookupByLibrary.simpleMessage("模式"),
|
||||
"monochromeScheme": MessageLookupByLibrary.simpleMessage("单色"),
|
||||
"months": MessageLookupByLibrary.simpleMessage("月"),
|
||||
@@ -287,10 +271,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"noProxyDesc": MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"),
|
||||
"noResolve": MessageLookupByLibrary.simpleMessage("不解析IP"),
|
||||
"none": MessageLookupByLibrary.simpleMessage("无"),
|
||||
"notEmpty": MessageLookupByLibrary.simpleMessage("不能为空"),
|
||||
"notSelectedTip": MessageLookupByLibrary.simpleMessage("当前代理组无法选中"),
|
||||
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage("暂无连接"),
|
||||
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage("无法获取内核信息"),
|
||||
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("暂无日志"),
|
||||
"nullProfileDesc": MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
|
||||
"nullTip": m4,
|
||||
"numberTip": m5,
|
||||
"nullProxies": MessageLookupByLibrary.simpleMessage("暂无代理"),
|
||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
|
||||
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
|
||||
"onlyIcon": MessageLookupByLibrary.simpleMessage("仅图标"),
|
||||
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("仅第三方应用"),
|
||||
@@ -306,13 +294,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"overrideDesc": MessageLookupByLibrary.simpleMessage("覆写代理相关配置"),
|
||||
"overrideDns": MessageLookupByLibrary.simpleMessage("覆写DNS"),
|
||||
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage("开启后将覆盖配置中的DNS选项"),
|
||||
"overrideInvalidTip": MessageLookupByLibrary.simpleMessage("在脚本模式下不生效"),
|
||||
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("覆盖原始规则"),
|
||||
"palette": MessageLookupByLibrary.simpleMessage("调色板"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("密码"),
|
||||
"passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("粘贴"),
|
||||
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage("请绑定WebDAV"),
|
||||
"pleaseEnterScriptName": MessageLookupByLibrary.simpleMessage("请输入脚本名称"),
|
||||
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
|
||||
"请输入管理员密码",
|
||||
),
|
||||
@@ -321,8 +308,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"请上传有效的二维码",
|
||||
),
|
||||
"port": MessageLookupByLibrary.simpleMessage("端口"),
|
||||
"portConflictTip": MessageLookupByLibrary.simpleMessage("请输入不同的端口"),
|
||||
"portTip": m6,
|
||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage("优先使用DOH的http/3"),
|
||||
"pressKeyboard": MessageLookupByLibrary.simpleMessage("请按下按键"),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("预览"),
|
||||
@@ -367,14 +352,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("兼容"),
|
||||
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage("覆盖"),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
|
||||
"redirPort": MessageLookupByLibrary.simpleMessage("Redir端口"),
|
||||
"redo": MessageLookupByLibrary.simpleMessage("重做"),
|
||||
"regExp": MessageLookupByLibrary.simpleMessage("正则"),
|
||||
"remote": MessageLookupByLibrary.simpleMessage("远程"),
|
||||
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
|
||||
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过WebDAV恢复数据"),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("移除"),
|
||||
"rename": MessageLookupByLibrary.simpleMessage("重命名"),
|
||||
"requests": MessageLookupByLibrary.simpleMessage("请求"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"),
|
||||
"reset": MessageLookupByLibrary.simpleMessage("重置"),
|
||||
@@ -393,24 +376,24 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"ru": MessageLookupByLibrary.simpleMessage("俄语"),
|
||||
"rule": MessageLookupByLibrary.simpleMessage("规则"),
|
||||
"ruleName": MessageLookupByLibrary.simpleMessage("规则名称"),
|
||||
"ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage("规则提供者不能为空"),
|
||||
"ruleProviders": MessageLookupByLibrary.simpleMessage("规则提供者"),
|
||||
"ruleTarget": MessageLookupByLibrary.simpleMessage("规则目标"),
|
||||
"ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage("规则目标不能为空"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("保存"),
|
||||
"saveChanges": MessageLookupByLibrary.simpleMessage("是否保存更改?"),
|
||||
"saveTip": MessageLookupByLibrary.simpleMessage("确定要保存吗?"),
|
||||
"script": MessageLookupByLibrary.simpleMessage("脚本"),
|
||||
"search": MessageLookupByLibrary.simpleMessage("搜索"),
|
||||
"seconds": MessageLookupByLibrary.simpleMessage("秒"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("全选"),
|
||||
"selected": MessageLookupByLibrary.simpleMessage("已选择"),
|
||||
"selectedCountTitle": m7,
|
||||
"selectedCountTitle": m0,
|
||||
"settings": MessageLookupByLibrary.simpleMessage("设置"),
|
||||
"show": MessageLookupByLibrary.simpleMessage("显示"),
|
||||
"shrink": MessageLookupByLibrary.simpleMessage("紧凑"),
|
||||
"silentLaunch": MessageLookupByLibrary.simpleMessage("静默启动"),
|
||||
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("后台启动"),
|
||||
"size": MessageLookupByLibrary.simpleMessage("尺寸"),
|
||||
"socksPort": MessageLookupByLibrary.simpleMessage("Socks端口"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("排序"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("来源"),
|
||||
"sourceIp": MessageLookupByLibrary.simpleMessage("源IP"),
|
||||
@@ -424,6 +407,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("风格"),
|
||||
"subRule": MessageLookupByLibrary.simpleMessage("子规则"),
|
||||
"subRuleEmptyTip": MessageLookupByLibrary.simpleMessage("子规则内容不能为空"),
|
||||
"submit": MessageLookupByLibrary.simpleMessage("提交"),
|
||||
"sync": MessageLookupByLibrary.simpleMessage("同步"),
|
||||
"system": MessageLookupByLibrary.simpleMessage("系统"),
|
||||
@@ -449,7 +433,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"toggle": MessageLookupByLibrary.simpleMessage("切换"),
|
||||
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("调性点缀"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("工具"),
|
||||
"tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxy端口"),
|
||||
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),
|
||||
"tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
|
||||
"tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"),
|
||||
@@ -461,15 +444,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"),
|
||||
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("未知"),
|
||||
"unnamed": MessageLookupByLibrary.simpleMessage("未命名"),
|
||||
"update": MessageLookupByLibrary.simpleMessage("更新"),
|
||||
"upload": MessageLookupByLibrary.simpleMessage("上传"),
|
||||
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
||||
"urlDesc": MessageLookupByLibrary.simpleMessage("通过URL获取配置文件"),
|
||||
"urlTip": m8,
|
||||
"useHosts": MessageLookupByLibrary.simpleMessage("使用Hosts"),
|
||||
"useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"),
|
||||
"value": MessageLookupByLibrary.simpleMessage("值"),
|
||||
"valueExists": MessageLookupByLibrary.simpleMessage("当前值已存在"),
|
||||
"vibrantScheme": MessageLookupByLibrary.simpleMessage("活力"),
|
||||
"view": MessageLookupByLibrary.simpleMessage("查看"),
|
||||
"vpnDesc": MessageLookupByLibrary.simpleMessage("修改VPN相关设置"),
|
||||
|
||||
@@ -140,6 +140,16 @@ class AppLocalizations {
|
||||
return Intl.message('Core info', name: 'coreInfo', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Unable to obtain core info`
|
||||
String get nullCoreInfoDesc {
|
||||
return Intl.message(
|
||||
'Unable to obtain core info',
|
||||
name: 'nullCoreInfoDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Network speed`
|
||||
String get networkSpeed {
|
||||
return Intl.message(
|
||||
@@ -205,6 +215,11 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `No logs`
|
||||
String get nullLogsDesc {
|
||||
return Intl.message('No logs', name: 'nullLogsDesc', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Settings`
|
||||
String get settings {
|
||||
return Intl.message('Settings', name: 'settings', desc: '', args: []);
|
||||
@@ -1145,6 +1160,26 @@ class AppLocalizations {
|
||||
return Intl.message('Password', name: 'password', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Password cannot be empty`
|
||||
String get passwordTip {
|
||||
return Intl.message(
|
||||
'Password cannot be empty',
|
||||
name: 'passwordTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Account cannot be empty`
|
||||
String get accountTip {
|
||||
return Intl.message(
|
||||
'Account cannot be empty',
|
||||
name: 'accountTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Check for updates`
|
||||
String get checkUpdate {
|
||||
return Intl.message(
|
||||
@@ -1430,6 +1465,26 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `No requests`
|
||||
String get nullRequestsDesc {
|
||||
return Intl.message(
|
||||
'No requests',
|
||||
name: 'nullRequestsDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `No connections`
|
||||
String get nullConnectionsDesc {
|
||||
return Intl.message(
|
||||
'No connections',
|
||||
name: 'nullConnectionsDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Intranet IP`
|
||||
String get intranetIP {
|
||||
return Intl.message('Intranet IP', name: 'intranetIP', desc: '', args: []);
|
||||
@@ -1645,6 +1700,16 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Sure you want to delete the current profile?`
|
||||
String get deleteProfileTip {
|
||||
return Intl.message(
|
||||
'Sure you want to delete the current profile?',
|
||||
name: 'deleteProfileTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Pure black mode`
|
||||
String get pureBlackMode {
|
||||
return Intl.message(
|
||||
@@ -1865,6 +1930,16 @@ class AppLocalizations {
|
||||
return Intl.message('Value', name: 'value', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Cannot be empty`
|
||||
String get notEmpty {
|
||||
return Intl.message(
|
||||
'Cannot be empty',
|
||||
name: 'notEmpty',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Add Hosts`
|
||||
String get hostsDesc {
|
||||
return Intl.message('Add Hosts', name: 'hostsDesc', desc: '', args: []);
|
||||
@@ -2540,6 +2615,11 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `No proxies`
|
||||
String get nullProxies {
|
||||
return Intl.message('No proxies', name: 'nullProxies', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Copy success`
|
||||
String get copySuccess {
|
||||
return Intl.message(
|
||||
@@ -2585,6 +2665,26 @@ class AppLocalizations {
|
||||
return Intl.message('Listen', name: 'listen', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `The current key already exists`
|
||||
String get keyExists {
|
||||
return Intl.message(
|
||||
'The current key already exists',
|
||||
name: 'keyExists',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `The current value already exists`
|
||||
String get valueExists {
|
||||
return Intl.message(
|
||||
'The current value already exists',
|
||||
name: 'valueExists',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `undo`
|
||||
String get undo {
|
||||
return Intl.message('undo', name: 'undo', desc: '', args: []);
|
||||
@@ -2635,6 +2735,16 @@ class AppLocalizations {
|
||||
return Intl.message('Add rule', name: 'addRule', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Rule provider cannot be empty`
|
||||
String get ruleProviderEmptyTip {
|
||||
return Intl.message(
|
||||
'Rule provider cannot be empty',
|
||||
name: 'ruleProviderEmptyTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Rule name`
|
||||
String get ruleName {
|
||||
return Intl.message('Rule name', name: 'ruleName', desc: '', args: []);
|
||||
@@ -2645,16 +2755,46 @@ class AppLocalizations {
|
||||
return Intl.message('Content', name: 'content', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Content cannot be empty`
|
||||
String get contentEmptyTip {
|
||||
return Intl.message(
|
||||
'Content cannot be empty',
|
||||
name: 'contentEmptyTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Sub rule`
|
||||
String get subRule {
|
||||
return Intl.message('Sub rule', name: 'subRule', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Sub rule content cannot be empty`
|
||||
String get subRuleEmptyTip {
|
||||
return Intl.message(
|
||||
'Sub rule content cannot be empty',
|
||||
name: 'subRuleEmptyTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Rule target`
|
||||
String get ruleTarget {
|
||||
return Intl.message('Rule target', name: 'ruleTarget', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Rule target cannot be empty`
|
||||
String get ruleTargetEmptyTip {
|
||||
return Intl.message(
|
||||
'Rule target cannot be empty',
|
||||
name: 'ruleTargetEmptyTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Source IP`
|
||||
String get sourceIp {
|
||||
return Intl.message('Source IP', name: 'sourceIp', desc: '', args: []);
|
||||
@@ -2705,6 +2845,16 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Are you sure you want to delete the selected rule?`
|
||||
String get deleteRuleTip {
|
||||
return Intl.message(
|
||||
'Are you sure you want to delete the selected rule?',
|
||||
name: 'deleteRuleTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Do you want to save the changes?`
|
||||
String get saveChanges {
|
||||
return Intl.message(
|
||||
@@ -2755,6 +2905,26 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Are you sure you want to delete the current color?`
|
||||
String get deleteColorTip {
|
||||
return Intl.message(
|
||||
'Are you sure you want to delete the current color?',
|
||||
name: 'deleteColorTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Current color already exists`
|
||||
String get colorExists {
|
||||
return Intl.message(
|
||||
'Current color already exists',
|
||||
name: 'colorExists',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Color schemes`
|
||||
String get colorSchemes {
|
||||
return Intl.message(
|
||||
@@ -2949,161 +3119,6 @@ class AppLocalizations {
|
||||
String get logsTest {
|
||||
return Intl.message('Logs test', name: 'logsTest', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `{label} cannot be empty`
|
||||
String emptyTip(Object label) {
|
||||
return Intl.message(
|
||||
'$label cannot be empty',
|
||||
name: 'emptyTip',
|
||||
desc: '',
|
||||
args: [label],
|
||||
);
|
||||
}
|
||||
|
||||
/// `{label} must be a url`
|
||||
String urlTip(Object label) {
|
||||
return Intl.message(
|
||||
'$label must be a url',
|
||||
name: 'urlTip',
|
||||
desc: '',
|
||||
args: [label],
|
||||
);
|
||||
}
|
||||
|
||||
/// `{label} must be a number`
|
||||
String numberTip(Object label) {
|
||||
return Intl.message(
|
||||
'$label must be a number',
|
||||
name: 'numberTip',
|
||||
desc: '',
|
||||
args: [label],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Interval`
|
||||
String get interval {
|
||||
return Intl.message('Interval', name: 'interval', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Current {label} already exists`
|
||||
String existsTip(Object label) {
|
||||
return Intl.message(
|
||||
'Current $label already exists',
|
||||
name: 'existsTip',
|
||||
desc: '',
|
||||
args: [label],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Are you sure you want to delete the current {label}?`
|
||||
String deleteTip(Object label) {
|
||||
return Intl.message(
|
||||
'Are you sure you want to delete the current $label?',
|
||||
name: 'deleteTip',
|
||||
desc: '',
|
||||
args: [label],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Are you sure you want to delete the selected {label}?`
|
||||
String deleteMultipTip(Object label) {
|
||||
return Intl.message(
|
||||
'Are you sure you want to delete the selected $label?',
|
||||
name: 'deleteMultipTip',
|
||||
desc: '',
|
||||
args: [label],
|
||||
);
|
||||
}
|
||||
|
||||
/// `No {label} at the moment`
|
||||
String nullTip(Object label) {
|
||||
return Intl.message(
|
||||
'No $label at the moment',
|
||||
name: 'nullTip',
|
||||
desc: '',
|
||||
args: [label],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Script`
|
||||
String get script {
|
||||
return Intl.message('Script', name: 'script', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Color`
|
||||
String get color {
|
||||
return Intl.message('Color', name: 'color', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Rename`
|
||||
String get rename {
|
||||
return Intl.message('Rename', name: 'rename', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Unnamed`
|
||||
String get unnamed {
|
||||
return Intl.message('Unnamed', name: 'unnamed', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Please enter a script name`
|
||||
String get pleaseEnterScriptName {
|
||||
return Intl.message(
|
||||
'Please enter a script name',
|
||||
name: 'pleaseEnterScriptName',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Does not take effect in script mode`
|
||||
String get overrideInvalidTip {
|
||||
return Intl.message(
|
||||
'Does not take effect in script mode',
|
||||
name: 'overrideInvalidTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Mixed Port`
|
||||
String get mixedPort {
|
||||
return Intl.message('Mixed Port', name: 'mixedPort', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Socks Port`
|
||||
String get socksPort {
|
||||
return Intl.message('Socks Port', name: 'socksPort', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Redir Port`
|
||||
String get redirPort {
|
||||
return Intl.message('Redir Port', name: 'redirPort', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Tproxy Port`
|
||||
String get tproxyPort {
|
||||
return Intl.message('Tproxy Port', name: 'tproxyPort', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `{label} must be between 1024 and 49151`
|
||||
String portTip(Object label) {
|
||||
return Intl.message(
|
||||
'$label must be between 1024 and 49151',
|
||||
name: 'portTip',
|
||||
desc: '',
|
||||
args: [label],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Please enter a different port`
|
||||
String get portConflictTip {
|
||||
return Intl.message(
|
||||
'Please enter a different port',
|
||||
name: 'portConflictTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fl_clash/models/core.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
import 'package:fl_clash/plugins/tile.dart';
|
||||
import 'package:fl_clash/plugins/vpn.dart';
|
||||
@@ -16,7 +17,6 @@ import 'application.dart';
|
||||
import 'clash/core.dart';
|
||||
import 'clash/lib.dart';
|
||||
import 'common/common.dart';
|
||||
import 'models/models.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
globalState.isService = false;
|
||||
@@ -74,35 +74,27 @@ Future<void> _service(List<String> flags) async {
|
||||
app?.tip(appLocalizations.startVpn);
|
||||
final homeDirPath = await appPath.homeDirPath;
|
||||
final version = await system.version;
|
||||
final clashConfig = globalState.config.patchClashConfig.copyWith.tun(
|
||||
enable: false,
|
||||
clashLibHandler
|
||||
.quickStart(
|
||||
InitParams(
|
||||
homeDir: homeDirPath,
|
||||
version: version,
|
||||
),
|
||||
globalState.getUpdateConfigParams(),
|
||||
globalState.getCoreState(),
|
||||
)
|
||||
.then(
|
||||
(res) async {
|
||||
if (res.isNotEmpty) {
|
||||
await vpn?.stop();
|
||||
exit(0);
|
||||
}
|
||||
await vpn?.start(
|
||||
clashLibHandler.getAndroidVpnOptions(),
|
||||
);
|
||||
clashLibHandler.startListener();
|
||||
},
|
||||
);
|
||||
Future(() async {
|
||||
final profileId = globalState.config.currentProfileId;
|
||||
if (profileId == null) {
|
||||
return;
|
||||
}
|
||||
final params = await globalState.getSetupParams(
|
||||
pathConfig: clashConfig,
|
||||
);
|
||||
final res = await clashLibHandler.quickStart(
|
||||
InitParams(
|
||||
homeDir: homeDirPath,
|
||||
version: version,
|
||||
),
|
||||
params,
|
||||
globalState.getCoreState(),
|
||||
);
|
||||
debugPrint(res);
|
||||
if (res.isNotEmpty) {
|
||||
await vpn?.stop();
|
||||
exit(0);
|
||||
}
|
||||
await vpn?.start(
|
||||
clashLibHandler.getAndroidVpnOptions(),
|
||||
);
|
||||
clashLibHandler.startListener();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class _ClashContainerState extends ConsumerState<ClashManager>
|
||||
void initState() {
|
||||
super.initState();
|
||||
clashMessage.addListener(this);
|
||||
ref.listenManual(needSetupProvider, (prev, next) {
|
||||
ref.listenManual(currentProfileIdProvider, (prev, next) {
|
||||
if (prev != next) {
|
||||
globalState.appController.handleChangeProfile();
|
||||
}
|
||||
@@ -42,23 +42,11 @@ class _ClashContainerState extends ConsumerState<ClashManager>
|
||||
await clashCore.setState(next);
|
||||
}
|
||||
});
|
||||
ref.listenManual(updateParamsProvider, (prev, next) {
|
||||
ref.listenManual(clashConfigStateProvider, (prev, next) {
|
||||
if (prev != next) {
|
||||
globalState.appController.updateClashConfigDebounce();
|
||||
}
|
||||
});
|
||||
|
||||
ref.listenManual(
|
||||
appSettingProvider.select((state) => state.openLogs),
|
||||
(prev, next) {
|
||||
if (next) {
|
||||
clashCore.startLog();
|
||||
} else {
|
||||
clashCore.stopLog();
|
||||
}
|
||||
},
|
||||
fireImmediately: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ConnectivityManager extends StatefulWidget {
|
||||
final Function(List<ConnectivityResult> results)? onConnectivityChanged;
|
||||
final VoidCallback? onConnectivityChanged;
|
||||
final Widget child;
|
||||
|
||||
const ConnectivityManager({
|
||||
@@ -23,9 +23,9 @@ class _ConnectivityManagerState extends State<ConnectivityManager> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
subscription = Connectivity().onConnectivityChanged.listen((results) async {
|
||||
subscription = Connectivity().onConnectivityChanged.listen((_) async {
|
||||
if (widget.onConnectivityChanged != null) {
|
||||
widget.onConnectivityChanged!(results);
|
||||
widget.onConnectivityChanged!();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ class MessageManagerState extends State<MessageManager> {
|
||||
id: utils.uuidV4,
|
||||
text: text,
|
||||
);
|
||||
commonPrint.log(text);
|
||||
_bufferMessages.add(commonMessage);
|
||||
await _showMessage();
|
||||
}
|
||||
@@ -88,32 +87,32 @@ class MessageManagerState extends State<MessageManager> {
|
||||
child: messages.isEmpty
|
||||
? SizedBox()
|
||||
: LayoutBuilder(
|
||||
key: Key(messages.last.id),
|
||||
builder: (_, constraints) {
|
||||
return Card(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
elevation: 10,
|
||||
color: context.colorScheme.surfaceContainerHigh,
|
||||
child: Container(
|
||||
width: min(
|
||||
constraints.maxWidth,
|
||||
500,
|
||||
),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 16,
|
||||
),
|
||||
child: Text(
|
||||
messages.last.text,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
key: Key(messages.last.id),
|
||||
builder: (_, constraints) {
|
||||
return Card(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
elevation: 10,
|
||||
color: context.colorScheme.surfaceContainerHigh,
|
||||
child: Container(
|
||||
width: min(
|
||||
constraints.maxWidth,
|
||||
500,
|
||||
),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 16,
|
||||
),
|
||||
child: Text(
|
||||
messages.last.text,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -32,6 +32,7 @@ class AppState with _$AppState {
|
||||
required FixedList<Traffic> traffics,
|
||||
required Traffic totalTraffic,
|
||||
@Default("") String proxiesQuery,
|
||||
@Default(false) bool needApply,
|
||||
}) = _AppState;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user