From afbc5adb0591c62edbdc268db6b08e03440e101e Mon Sep 17 00:00:00 2001 From: chen08209 Date: Fri, 2 May 2025 02:24:12 +0800 Subject: [PATCH] Support override script Support proxies search Support svg display Optimize config persistence Add some scenes auto close connections Update core Optimize more details --- .github/workflows/build.yaml | 12 +- android/app/build.gradle | 97 - android/app/build.gradle.kts | 93 + android/app/src/main/AndroidManifest.xml | 6 +- .../kotlin/com/follow/clash/GlobalState.kt | 13 + .../kotlin/com/follow/clash/MainActivity.kt | 1 + .../com/follow/clash/plugins/ServicePlugin.kt | 1 + .../com/follow/clash/plugins/VpnPlugin.kt | 9 +- .../clash/services/BaseServiceInterface.kt | 6 +- .../clash/services/FlClashTileService.kt | 1 + .../main/res/drawable-hdpi/ic_stat_name.png | Bin 618 -> 0 bytes .../main/res/drawable-mdpi/ic_stat_name.png | Bin 423 -> 0 bytes .../main/res/drawable-xhdpi/ic_stat_name.png | Bin 803 -> 0 bytes .../main/res/drawable-xxhdpi/ic_stat_name.png | Bin 1194 -> 0 bytes .../res/drawable-xxxhdpi/ic_stat_name.png | Bin 1615 -> 0 bytes android/app/src/main/res/drawable/ic.xml | 17 + .../src/main/res/mipmap-xhdpi/ic_banner.png | Bin 0 -> 4300 bytes android/build.gradle | 33 - android/build.gradle.kts | 22 + android/core/build.gradle.kts | 4 +- android/core/consumer-rules.pro | 0 android/core/src/main/cpp/CMakeLists.txt | 2 + android/core/src/main/cpp/core.cpp | 49 +- android/core/src/main/cpp/jni_helper.cpp | 29 +- android/core/src/main/cpp/jni_helper.h | 11 +- android/gradle.properties | 2 +- android/settings.gradle | 27 - android/settings.gradle.kts | 29 + arb/intl_en.arb | 44 +- arb/intl_ja.arb | 44 +- arb/intl_ru.arb | 44 +- arb/intl_zh_CN.arb | 44 +- core/Clash.Meta | 2 +- core/action.go | 118 +- core/android_bride.go | 4 +- core/common.go | 320 +-- core/constant.go | 46 +- core/go.mod | 45 +- core/go.sum | 103 +- core/hub.go | 89 +- core/lib.go | 42 +- core/lib_android.go | 16 +- core/server.go | 25 +- lib/application.dart | 6 +- lib/clash/core.dart | 81 +- lib/clash/generated/clash_ffi.dart | 285 ++- lib/clash/interface.dart | 124 +- lib/clash/lib.dart | 76 +- lib/clash/message.dart | 7 +- lib/clash/service.dart | 32 +- lib/common/common.dart | 11 +- lib/common/constant.dart | 13 +- lib/common/converter.dart | 32 + lib/common/function.dart | 10 +- lib/common/http.dart | 1 + lib/common/navigation.dart | 20 +- lib/common/navigator.dart | 19 +- lib/common/path.dart | 30 +- lib/common/print.dart | 2 +- lib/common/render.dart | 4 +- lib/common/request.dart | 10 + lib/common/string.dart | 22 + lib/common/system.dart | 97 +- lib/common/utils.dart | 71 +- lib/controller.dart | 185 +- lib/enum/enum.dart | 38 +- lib/fragments/config/general.dart | 430 ---- lib/fragments/connection/item.dart | 210 -- lib/fragments/dashboard/dashboard.dart | 138 -- .../dashboard/widgets/start_button.dart | 143 -- lib/fragments/proxies/proxies.dart | 179 -- lib/l10n/intl/messages_en.dart | 100 +- lib/l10n/intl/messages_ja.dart | 70 +- lib/l10n/intl/messages_ru.dart | 99 +- lib/l10n/intl/messages_zh_CN.dart | 62 +- lib/l10n/l10n.dart | 360 +-- lib/main.dart | 53 +- lib/manager/app_state_manager.dart | 35 +- lib/manager/clash_manager.dart | 17 +- lib/manager/connectivity_manager.dart | 6 +- lib/manager/hotkey_manager.dart | 57 +- lib/manager/message_manager.dart | 51 +- lib/manager/theme_manager.dart | 2 +- lib/manager/vpn_manager.dart | 2 +- lib/manager/window_manager.dart | 9 +- lib/models/app.dart | 4 +- lib/models/clash_config.dart | 45 +- lib/models/common.dart | 64 +- lib/models/config.dart | 34 +- lib/models/core.dart | 154 +- lib/models/generated/app.freezed.dart | 123 +- .../generated/clash_config.freezed.dart | 791 ++++++- lib/models/generated/clash_config.g.dart | 78 +- lib/models/generated/common.freezed.dart | 384 +++- lib/models/generated/common.g.dart | 13 + lib/models/generated/config.freezed.dart | 275 ++- lib/models/generated/config.g.dart | 23 +- lib/models/generated/core.freezed.dart | 1978 ++++++----------- lib/models/generated/core.g.dart | 180 +- lib/models/generated/selector.freezed.dart | 268 ++- lib/models/profile.dart | 5 +- lib/models/selector.dart | 14 +- lib/pages/editor.dart | 288 ++- lib/pages/home.dart | 2 +- lib/plugins/vpn.dart | 2 + lib/providers/app.dart | 38 +- lib/providers/config.dart | 51 + lib/providers/generated/app.g.dart | 47 +- lib/providers/generated/config.g.dart | 15 + lib/providers/generated/state.g.dart | 93 +- lib/providers/state.dart | 128 +- lib/state.dart | 337 ++- lib/{fragments => views}/about.dart | 18 +- lib/{fragments => views}/access.dart | 81 +- .../application_setting.dart | 4 +- .../backup_and_recovery.dart | 43 +- lib/{fragments => views}/config/config.dart | 17 +- lib/{fragments => views}/config/dns.dart | 18 + lib/views/config/general.dart | 793 +++++++ lib/{fragments => views}/config/network.dart | 30 +- .../connection/connections.dart | 27 +- lib/views/connection/item.dart | 169 ++ .../connection/requests.dart | 27 +- lib/views/dashboard/dashboard.dart | 319 +++ .../dashboard/widgets/intranet_ip.dart | 0 .../dashboard/widgets/memory_info.dart | 0 .../dashboard/widgets/network_detection.dart | 109 +- .../dashboard/widgets/network_speed.dart | 0 .../dashboard/widgets/outbound_mode.dart | 0 .../dashboard/widgets/quick_options.dart | 5 +- lib/views/dashboard/widgets/start_button.dart | 148 ++ .../dashboard/widgets/traffic_usage.dart | 0 .../dashboard/widgets/widgets.dart | 0 lib/{fragments => views}/developer.dart | 2 - lib/{fragments => views}/hotkey.dart | 112 +- lib/{fragments => views}/logs.dart | 14 +- .../profiles/add_profile.dart | 20 +- .../profiles/edit_profile.dart | 63 +- .../profiles/override_profile.dart | 255 ++- .../profiles/profiles.dart | 55 +- lib/views/profiles/scripts.dart | 249 +++ lib/{fragments => views}/proxies/card.dart | 7 +- lib/{fragments => views}/proxies/common.dart | 2 +- lib/{fragments => views}/proxies/list.dart | 89 +- .../proxies/providers.dart | 0 lib/views/proxies/proxies.dart | 277 +++ lib/{fragments => views}/proxies/setting.dart | 0 lib/{fragments => views}/proxies/tab.dart | 42 +- lib/{fragments => views}/resources.dart | 4 +- lib/{fragments => views}/theme.dart | 18 +- lib/{fragments => views}/tools.dart | 36 +- .../fragments.dart => views/views.dart} | 0 lib/widgets/card.dart | 9 +- lib/widgets/dialog.dart | 25 + lib/widgets/effect.dart | 83 + lib/widgets/icon.dart | 25 +- lib/widgets/input.dart | 161 +- lib/widgets/list.dart | 5 + lib/widgets/null_status.dart | 1 + lib/widgets/pop_scope.dart | 37 + lib/widgets/popup.dart | 37 +- lib/widgets/scaffold.dart | 56 +- lib/widgets/sheet.dart | 2 +- lib/widgets/super_grid.dart | 276 +-- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + linux/my_application.cc | 33 +- macos/Flutter/GeneratedPluginRegistrant.swift | 4 + macos/Podfile.lock | 32 +- pubspec.lock | 412 ++-- pubspec.yaml | 25 +- services/helper/build.rs | 1 + .../flutter/generated_plugin_registrant.cc | 6 +- windows/flutter/generated_plugins.cmake | 2 +- 174 files changed, 8940 insertions(+), 5433 deletions(-) delete mode 100644 android/app/build.gradle create mode 100644 android/app/build.gradle.kts delete mode 100644 android/app/src/main/res/drawable-hdpi/ic_stat_name.png delete mode 100644 android/app/src/main/res/drawable-mdpi/ic_stat_name.png delete mode 100644 android/app/src/main/res/drawable-xhdpi/ic_stat_name.png delete mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_stat_name.png delete mode 100644 android/app/src/main/res/drawable-xxxhdpi/ic_stat_name.png create mode 100644 android/app/src/main/res/drawable/ic.xml create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_banner.png delete mode 100644 android/build.gradle create mode 100644 android/build.gradle.kts delete mode 100644 android/core/consumer-rules.pro delete mode 100644 android/settings.gradle create mode 100644 android/settings.gradle.kts create mode 100644 lib/common/converter.dart delete mode 100644 lib/fragments/config/general.dart delete mode 100644 lib/fragments/connection/item.dart delete mode 100644 lib/fragments/dashboard/dashboard.dart delete mode 100644 lib/fragments/dashboard/widgets/start_button.dart delete mode 100644 lib/fragments/proxies/proxies.dart rename lib/{fragments => views}/about.dart (93%) rename lib/{fragments => views}/access.dart (91%) rename lib/{fragments => views}/application_setting.dart (98%) rename lib/{fragments => views}/backup_and_recovery.dart (92%) rename lib/{fragments => views}/config/config.dart (85%) rename lib/{fragments => views}/config/dns.dart (97%) create mode 100644 lib/views/config/general.dart rename lib/{fragments => views}/config/network.dart (93%) rename lib/{fragments => views}/connection/connections.dart (85%) create mode 100644 lib/views/connection/item.dart rename lib/{fragments => views}/connection/requests.dart (92%) create mode 100644 lib/views/dashboard/dashboard.dart rename lib/{fragments => views}/dashboard/widgets/intranet_ip.dart (100%) rename lib/{fragments => views}/dashboard/widgets/memory_info.dart (100%) rename lib/{fragments => views}/dashboard/widgets/network_detection.dart (70%) rename lib/{fragments => views}/dashboard/widgets/network_speed.dart (100%) rename lib/{fragments => views}/dashboard/widgets/outbound_mode.dart (100%) rename lib/{fragments => views}/dashboard/widgets/quick_options.dart (98%) create mode 100644 lib/views/dashboard/widgets/start_button.dart rename lib/{fragments => views}/dashboard/widgets/traffic_usage.dart (100%) rename lib/{fragments => views}/dashboard/widgets/widgets.dart (100%) rename lib/{fragments => views}/developer.dart (98%) rename lib/{fragments => views}/hotkey.dart (72%) rename lib/{fragments => views}/logs.dart (95%) rename lib/{fragments => views}/profiles/add_profile.dart (85%) rename lib/{fragments => views}/profiles/edit_profile.dart (91%) rename lib/{fragments => views}/profiles/override_profile.dart (85%) rename lib/{fragments => views}/profiles/profiles.dart (91%) create mode 100644 lib/views/profiles/scripts.dart rename lib/{fragments => views}/proxies/card.dart (97%) rename lib/{fragments => views}/proxies/common.dart (97%) rename lib/{fragments => views}/proxies/list.dart (90%) rename lib/{fragments => views}/proxies/providers.dart (100%) create mode 100644 lib/views/proxies/proxies.dart rename lib/{fragments => views}/proxies/setting.dart (100%) rename lib/{fragments => views}/proxies/tab.dart (92%) rename lib/{fragments => views}/resources.dart (99%) rename lib/{fragments => views}/theme.dart (98%) rename lib/{fragments => views}/tools.dart (91%) rename lib/{fragments/fragments.dart => views/views.dart} (100%) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f4d078f..1b63ca6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -63,11 +63,19 @@ jobs: cache-dependency-path: | core/go.sum - - name: Setup Flutter + - name: Setup Flutter Master + if: startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm') uses: subosito/flutter-action@v2 with: - channel: ${{ (startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')) && 'master' || 'stable' }} + channel: 'master' cache: true + - name: Setup Flutter + if: ${{ !(startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')) }} + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + flutter-version: 3.29.3 - name: Get Flutter Dependency run: flutter pub get diff --git a/android/app/build.gradle b/android/app/build.gradle deleted file mode 100644 index ab0a450..0000000 --- a/android/app/build.gradle +++ /dev/null @@ -1,97 +0,0 @@ -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 { - 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" - } -} \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..337dedf --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,93 @@ +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") + } +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ece0d77..dad0f94 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + @@ -22,6 +25,7 @@ @@ -88,7 +92,7 @@ diff --git a/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt b/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt index 052b958..f47ff57 100644 --- a/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt +++ b/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt @@ -7,6 +7,10 @@ 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 @@ -33,6 +37,15 @@ 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) ?: "" } diff --git a/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt b/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt index c404697..de930ca 100644 --- a/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt +++ b/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt @@ -17,6 +17,7 @@ class MainActivity : FlutterActivity() { override fun onDestroy() { GlobalState.flutterEngine = null + GlobalState.runState.value = RunState.STOP super.onDestroy() } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt index d0f3010..62db45d 100644 --- a/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt +++ b/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt @@ -51,6 +51,7 @@ data object ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler { } } + private fun handleDestroy() { GlobalState.destroyServiceEngine() } diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt index 5ae81c1..9a73f77 100644 --- a/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt +++ b/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt @@ -200,6 +200,13 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler { timerJob = null } + + suspend fun getStatus(): Boolean? { + return withContext(Dispatchers.Default) { + flutterMethodChannel.awaitResult("status", null) + } + } + private fun handleStartService() { if (flClashService == null) { bindService() @@ -248,9 +255,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() } } diff --git a/android/app/src/main/kotlin/com/follow/clash/services/BaseServiceInterface.kt b/android/app/src/main/kotlin/com/follow/clash/services/BaseServiceInterface.kt index 5a8bd72..287267b 100644 --- a/android/app/src/main/kotlin/com/follow/clash/services/BaseServiceInterface.kt +++ b/android/app/src/main/kotlin/com/follow/clash/services/BaseServiceInterface.kt @@ -16,7 +16,6 @@ import com.follow.clash.MainActivity import com.follow.clash.R import com.follow.clash.extensions.getActionPendingIntent import com.follow.clash.models.VpnOptions -import io.flutter.Log import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers @@ -54,7 +53,7 @@ fun Service.createFlClashNotificationBuilder(): Deferred") channel = NotificationChannel( - GlobalState.NOTIFICATION_CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW + GlobalState.NOTIFICATION_CHANNEL, "SERVICE_CHANNEL", NotificationManager.IMPORTANCE_LOW ) manager?.createNotificationChannel(channel) } diff --git a/android/app/src/main/kotlin/com/follow/clash/services/FlClashTileService.kt b/android/app/src/main/kotlin/com/follow/clash/services/FlClashTileService.kt index ed0aac0..76e4083 100644 --- a/android/app/src/main/kotlin/com/follow/clash/services/FlClashTileService.kt +++ b/android/app/src/main/kotlin/com/follow/clash/services/FlClashTileService.kt @@ -33,6 +33,7 @@ class FlClashTileService : TileService() { override fun onStartListening() { super.onStartListening() + GlobalState.syncStatus() GlobalState.runState.value?.let { updateTile(it) } GlobalState.runState.observeForever(observer) } diff --git a/android/app/src/main/res/drawable-hdpi/ic_stat_name.png b/android/app/src/main/res/drawable-hdpi/ic_stat_name.png deleted file mode 100644 index 188d2752ca76b091924a65977bcbe9cef9103f7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 618 zcmV-w0+s!VP)1K*A^9O%C}J*%iSD9yr46`%buTcjwyL{_~eHvvxd(uW$`# z@rMY|g|;=|2cFWbmM}jjla@UvwRnvuHLoy)b>TJqn(k@H3SPmjnpp_*b8_DD&52_j zZ{tqQEDT}C@MXHEDIVj1<`u^LoIJ4nbFzS^HLoQIJcz4O)Ce~xmo3(ugb?~f%Cc~C zGJ!iZCmMu*meP7W!u`Q@J$N18;v(L~-C=eilCUi1EqJ)`xdVrBIlXLyH`p2G=Z1h$ zO-=ZSBNogFpHG-SUSx48%+HPa|M&)eO9I`4@TLWG!skAmvgYo>i7>w~1a;#9>#efIPUb@*(zWR?86DpArD}*+=$gu)d=GxG&5Y{WN}%|b?j*teQ2g_%sSL|L#2wQ0r9`C z3(Gq_)`QXkt9`PJbJQpT<%jX9`6spe%{GQxblpG8Z-I{cmmoTaEdT%j07*qoM6N<$ Ef|qkG*Z=?k diff --git a/android/app/src/main/res/drawable-mdpi/ic_stat_name.png b/android/app/src/main/res/drawable-mdpi/ic_stat_name.png deleted file mode 100644 index 9e22a16141ac9cb084dd287fa7c1c6594d747263..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 423 zcmV;Y0a*TtP)X4cc0={zu|hH=eh2_yEm2k?-*lpc!1A%ioKet3x+yy9p8=1nPzK( zfo7b=CmF6#)=n%arg0o^-MuO{wEH8J295vyMp4EYY|?%xgu8JUznm$TH(bMZ?T13R z4Oj5hnX2Xxd$b=3g&};9A>|zN+KmOp8(j-JnEj4aD=2`8~pvvpzopX3}K;XGzF^Q##DU%qNT zMMlpP!Qhy2FTHKeB!a>Hz_^EZ5vDa)H;gUG8(e875yrpV_V5LZ*sqyC;TvUF-?YL| RhbjO7002ovPDHLkV1oY%!u0?E diff --git a/android/app/src/main/res/drawable-xhdpi/ic_stat_name.png b/android/app/src/main/res/drawable-xhdpi/ic_stat_name.png deleted file mode 100644 index a0e0795fdd96ce9c6179cd55731c8b0db08436e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 803 zcmV+;1Kj+HP)D+FGK%?-ertE|ZTBBPaNHuQ5FA}gT zUTUpOAwVBoh|eTgv9xN<%%J&1yrPK`vPJ6#ho<#-2!BbKq};-4tse}URpCbbR1hOM z?u)x6v_^Xbfkx#x8y^?MNXIptq&k;lb0t=2k6_TO*8YEW z`Hs7AsMZe-O?OI|q@2RBS~oK+{crz1&cJf1SA)yUAM93RL-g~KLxYe1G8M%KT!)oX zrxMqlg1y`;OnB+NRW2Ey!+kR9Rn zj0rlw+aXjPzL2m{o|<2!vR~Va#r#?4c42P&Z2qo)kdDtK=I{93Rrv~R!CD>DV`=+x z4LD3c%QXbWyl$SHgA!@q%2o8A8tVa1ca~_+Yt)zyj zm*Aku1=JTw|D|E`uIH$+>LWPdIrDDPe`(k~)0@=Q>LWPdIkOSf<;us4 zy`MQv-H%9eW;A>mq6#c$Hc%IgBG(1}hDTBk*Ejo4-(DMSRgGt1E?XLxKB^*Hr&qGdXEOE69Qs}vfTh0eBNEN7lZdd}QM9Zj^%$9CSbihnXF9IaVm!OQpjOTu&J3FOb2EKJhz1`PO#nocdI7QwfR2kYm~W5=kw(Ou58;}^s*p=||?-#~pV z1r^N>9XEL3IkN@D=1ePWO=^aYA1v@Sqje}JXIk-?dI%Qy!o4k5RDt~gmaB)Lflofm z<{$W-{lI^%=YfVNqx`+FPpF%9>?nZePFb9w?xlw4xY0oKJEW*0u=|^*sL?uJbfDM; zeao3`h%IN@0blhig8kG@+JE%GrxM+8oLYjIbEX|EYy>)+Ygx`bNsZDzYX^LRJ?nO~ z<;+&<3hk%Xu;AN~KFzSn>y6sBXJGMH!b7|7yIgi<*@?LWbG}I7k7Wt>Qk^#zd-w{E%0AZ$isT=qt>wCG4Mxzv+yA`T>Gd!Ec#OF z134Lr?UMBF3IKe{VUNz(Ls_Tb*mnv;Y7A07*qo IM6N<$f;|XlEdT%j diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_stat_name.png b/android/app/src/main/res/drawable-xxxhdpi/ic_stat_name.png deleted file mode 100644 index 24834c271d84f8d325a9b247db39723bf957abdd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1615 zcmV-V2C(^wP)g1}GXR8zgKp z8Z4-Y$|AD4C>m*=>+4%AAHDPS-n-}Ad#1b3{=xU(x$CU8^qq6}T5In+Xiy{)i9{li zK>#+O=2Ev%FHs*-Z&Qy@XHZ+~XCgVk#15wBP_JWPexa_QMyriTn=qL>Q4dr9BvL&v zFH&2otw;u#z)h*Esb7=1P0bo=l-iB7fJqxl9Z!9p%x7C%s`euTz=X}FUXdhr!cWvV zy-!47k|tC4Alq|3EerL2k@YY+8&Q`~KTFc`WQE?h@xZ^&A47d2$tr@idjCelgiNQN z*DMvtI`yHEFcA}|+o-=JK}EAheQ6X-z$j`l^}QsfES9KGjR0OgID~pn5>hsMt8c-> zpr=t!X(lS*Won4}7c2~RE9xfd&tx7d;dg4f`WPe(bOg1C`X-r!%CHyx0`)UE;LrK} zskf5xSBm{7n5Vu52fR$Y62+7c+Tm|%8A5N$Fu<|YweV+bU9g4S?&@n$z{|uZP+ulv zuau9dqo~2^Z!lPJ)Lzu9iO{RX%FGv0W7OZEV1V0G_o80U`Man|>T6Iiz>TTPP;8kv zD>el&OMMLr1~`P8k8;YyX|dMi0&1xG8x#z12K7QR_DcDUx{?~Jz6J%nOne9Y`C13; zee)o-gZdg23~)4c0rf*7{AziF+E;xI3ixw=9`$}Q_DcDRI)xgp{ssl=Qzq`gR?|xm z`V<>{F?HVhfa}64YFG6&NZSWAAcR~#=nVrs9Q9bqen*`{jZhzh1^!Lv(_|h> z$C@KcsqyMx@W5BKRq_nAoBGul82o*jk&^g;IszFQ;_!0!7SuR|GFHw&*8P{0QuvX& zklIjv=?nOX;=$Ak_*%doSN6WLa zJ(o1oF*oRaY7M-#*eS13`>5^y;I-RQ%TdfIx=m$tPj`&Yj-;NIAkt#No=go>yL|y4 zMYp0~(WB_Q=)G$Ve9!G_31T4rpsuGjS6lr7A4PvRX{Ipd=>2OCogM~%DSs=$^v2qf zwkv&S{d|A$+Nso&Ni&5rSN-T3;3FqiBeDY7YWP^+{nYu$mUt@}+=yyM(Wk0EjR8K2 zzL5G_l2R7-7u=kZc5mQE#k?sADVwG0SMZ>{6HAJ~TB^9Bv!VbtUd+}d@i$U ztWh&teG482Jq6Vc>DnmzapAwrFh1;WVV|F*FJ$h(&5yhwR|A8&5eA+w2jwFcdgS=r<$HPkwX|Q81r>Tudn=qLZk$o`N zR?9QgRwN_v`jPDuYX@sVHg5xWo-l!DYF0CHtJ;jD1o>1?Z7)Li?E9({@V&lyaH`sl zqyU{ietNiFZAVf7Kh-b~R;les3XsnZOk<_mj-&uyCC!6o{?JaRAiq(@!_;;pC1`&S zau2o_Io0ttkv@P|Iy?C*Sfn;18G-+zMms!?rcOfVi6s#}4Uq*qPp;Y1(0Kyy1)PZ1 z`-koH7CU??_L7SL-o&>9J|2bFiR^%;FW^lY`%TFS`k6>15{X3W$iH(CI^lHce-{7% N002ovPDHLkV1l+(4rBlT diff --git a/android/app/src/main/res/drawable/ic.xml b/android/app/src/main/res/drawable/ic.xml new file mode 100644 index 0000000..9746595 --- /dev/null +++ b/android/app/src/main/res/drawable/ic.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_banner.png b/android/app/src/main/res/mipmap-xhdpi/ic_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..65ef38932399e3876335ed619b67ad4270002d5c GIT binary patch literal 4300 zcmdUz`9IX(7subjh^S^z#!^%&QV7|~Rz#YzWeXW=KC)%Y&Xg_dd}{1FeJn#+KXwg? zQMMt*K1K$^OpI-8WBK}g9^XIW`~BrU?s+`!z=W{^)hic^(=viT-!hQ%<9#TvKT|uJIhlicvAi2 zC(9^(F%_IKf^4=?4?sG6+;UpsT;P~)JE$Qv>f*K8-b(O2=zBt2B}$Y(Epog#8q}+- zc(AOArUo?!W@fCEHsK+;AHB-lVWMiLzGlQjQzxfl7CQ4wL8N*X^ZrP>k`7%81r2wK1 zu;DOTPSzf)9vsjBgDSY&gkSHhdYTy7osfjJNOSnd0b74AEa=I*Sx~oks~PJ(En*!0 zF~AnGx|+8NBD0AMxECe~G>RgHC$L}Orv>b^d*C9W{_XzHfyPTn;gSmV@|-|w5_eh7 zE!+0%WCmz13V4m4Dl6706-yBK=6Gx79822ALc)&{sK$l>ajP1Ml4!z98P=uu}&WnEFf&9X&1=jvw!+!C?Q(m#&o{?wq zw|Mre&ml*t!9Z%7MEE_aKRg-dy~$l!ktd zEV80ChtxG%eQ8@zL4M7a{ZEJ~n*kPC0!39g-tX6j2ww@-Pon^nZR`dq7zs%ReZE}9qF6)@ z2;=YpU{afSS(B#sqOFF}XVSY)wW+fRXaofJRp6JqJwGGxZju^MuoUuJG;-!oc{joyM#^Wq2Zk>dqQQw zWk&Q*quQBlzhT(j*Zh-3-s6xk{{{1vBSuAnXKT~1JsSS}HeERIJAZw;2dnt@_R`w* zD~(gh@?ZwJBTUk&^5P})#x!PX>y_T6^F6nbeSLkU{@OombgCeOc4S%7#<0``HnL%hChi3Jw-%D=KQY zK1>wJwcII^8ZRstTz_vn5*?J__U$1gPc4Uw5{zL{@@xl-{&B$ElCSgLBi*In-UIcJPdeFAjHAjF(O%(E^>UuR=K1(N(U6wC!Gkz9iYtQ}MU903I zj7`1hg`J4xoauyFr&SWO#%mJXj4(xK5+fg@EQ|KeRNPuP6@DMQT9Y8eTh};`6H0KCU3+-@RDVE5Mn=3Fht1B8Eb_jT z4#PaadA0*xTBp$5L!^euV=Qy6-N2GCR%>&QUJ;*2jv2+FU)!JH};Teoj!aLv5{0TmC?b}aK zkWb=v<1DAWicq*n(Bp%ZU(xoDaPcxYxsx_?FXcYE7&S-aSbew8iR4chcg5A*b z{n|(oBzI~?3vMief;Zpm#2-vqh5oquk1rZfZcTL5x(QqFdo`|x-Fika^!@hkk*I}r zoYrNW3%d!Hb?0Z&^_*5~i6xe?3J+2QIbbW@>4Wt`f5=8(gu$cXO?7re=&obC)hNO{ z;ZDk&tl4zgFn-}mN55MZc`fMTd_|erXW0cbVn{m937_Tc3gg3`$xBm^+=kl*#A*s- zhA59Iu&ZV928<-4{DKHIuiFn05mXx37=>Sb4rF(&A0m!k#@}@_Z&kBpva+AK65liL zAN+WlImC+b?q?iLR4L;D^5#MI#KZ(o-EzpdBTm8g7ZZr}BlWF3HfR?kohinn`~Rx1 z7kWc=cJbTqQA+&B2mK4v9gmPsHU_C|63j~XKce~vwx8eve#;@kRBeA3V_l24)bmJjqWzi`@ zp54)mBNe;z`UXp&-jN*Dpm^nMav(6jR=1O8rA=i9N}2%PzxKZzylz8A$WWe2m|2H5 zR(U97IU!-2RL6KRQN{4PD18w~TpRe7o~griX}S^m+JRzJVPWww3~?SG7=oR!|6J-J zJWYW=aQ{4rx>2%zL4)FUjigRju}4%e8~}RE3B0oD9cQEVhTzqFdhT_Kz|3;}^vn2W zPb^lAkM9jv9!$`3f-vJeO&a;Ybujk!CJEHSOuWm3V}yJeAp#==0_OQ$ztXTz))$%| z`wBC^_z{yU-841B*EZfixG;zQ6}#?bzhayP+V2a82$J2`cWWfima&LCz4N}YCBQ9_ zgix&?QNmpuVl6>Y+Ld#K+dvSNnO$1p2WP;~%mkA^5%kB>q7!m<=4-gFM`$9tN?q-! zr4)ma+4BAA5kA;OR6@W-xVT(c8Zf1%#9NoQyG8@=hdgb_IoaIx{cQ?&RD4>bU7l4z-Z6mrM?OWi7R@h@$1hKhZW~I;=G)zJz)jSOe%k~bc zmuoZT`?78Qv-A1>lk-TNoLm^{l$Xolv$H$ZJi%~*ZC92)vPvTFH0b` z_(r0z>PuHN2LAC4a}W{=^&VZW7b3fZl4{f`=nMAnNAV$O&%Z5bKZTwA3<)0jTZZ!wVt1BjotxNedD>n=I=#Mjtk+lM!U$W1WRCkP>zB%F)_hm ze<*D)=>eTB_0x~i@Vj#yARky0aQKTT9lvvSqEaB!j;n;)C8!X|kv9O-ITS^0DtZh7ZqUb6w4LCWW!v?}nOhOdTGi|a==VKl0V9M)`rl^OGIhMifNUqTnEJls>Uue~1T(Ke z601+5s!VW$O!P{frMvD}m$EiMjFdon_iPJtOMrL(v>#gOIH&AtPy%r7&U#bo){e#) z$%eC6?r)GnC&wr{FodhC5QR@;aJz|i%%9;P6OovBhK!~u9q^csDEK7y#NMoKRV%il zVhcVCh$F!Csi@<-H31mVbM3Pzi1txl)RC8B`u!6Ir=%qwjj8GNuewu7>e@N6ubR#1 zrPE)YN&y)X$RGXp=U<JigWQTZe)k`D_32u$0a9q zU|EdYi*j|lX-hY{juR9-AlfOX&?)tmVPCDA&nmvqn~_|pU!<+R79o==Wdd|IZ%~9b zuJIyv+IC$6fk-U_;jp1?KX3gH%3zR>4$zDt@g|!Fx4n;fzy-qz<70=qynkhRY@+p# zpQb~8gfZ&<$z8RCvTAefqu9lChkbLSkcpdR^;awf_S&t#81JsnJJop~PS3$783D6% zRd3ClKJ3D?(-tCrWJW7o{W4Pd!=#0&+mB@~|3B5eoF$wh zy*zSM9$YHB-4okeJ%tX%EM5&jdE!@yRg*pn9@A5wnY`pJI!PpxhW4e|g{eXhC=PV4 z%iiEy?CTLRhZDeCF=NpFZpk_APc550o@FNR8%JGi?U#)~N%YZykMnHLZu`=v?~oY) z*s_F0yk-CXM_OzTp(XYJ=K^p<53sHNp;^XVZI#qJUSKiVFvZq>(+6}`ORq3T7)wT}9_&bLa`gg$C=b@t0iHMMaz*B4KYm8Z_Xg02V}b7$hb zA%Vf|dyD#zx4sjwy??A0Tw+ROdQg}*ae~9)bb=T^l+hcjO|aYjX{Nys)rd}fSe$XF zi{@5UtWi+dSdxL-fLCwFkiI!C2q5k}=n;%>1i-8yl_ydGj0VS-W9(W`j>|0kCXK~} zcNTTse)WPIEUStv{PM5f3;b=h_>VzjZJ0M`K0b;Adl9EM8h@ILv|V?RJsMWbrkE-S zZm_OO(^z*hJIBM;X+>!eO{7MZ8Zxk>i9C3<1;PP733|*^ZFM>%S5D*N!%vWzhV?1> z(a@6;hkGXf+8GCK+TXnx+u1h@A>bGW$kjcL|58t~nj#QZqlUDjHm#62H%2IZxE5eP z0m#|<&Lj6SJ+S1{8xvb3a2Z+bOEGFFBxXU7N8B3GDSv<+18%6ka@UqtFUpuO=Re0G z!IIX|#OhWRw6{6a`D3i^+cGW`cpC{68BCrxWT)O4j-u9;Y>kmd<-Kw25&tI|`@fBV bBamm?bmt?UDB5wC0?>V+r&WI6=Iws~mNh!X literal 0 HcmV?d00001 diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index 6c317e4..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,33 +0,0 @@ -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 -} - diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..1bfd741 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,22 @@ + +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("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/core/build.gradle.kts b/android/core/build.gradle.kts index 6c1404e..1a18c91 100644 --- a/android/core/build.gradle.kts +++ b/android/core/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.argumentsWithVarargAsSingleArray + plugins { id("com.android.library") id("org.jetbrains.kotlin.android") @@ -10,11 +12,11 @@ android { defaultConfig { minSdk = 21 - consumerProguardFiles("consumer-rules.pro") } buildTypes { release { + isJniDebuggable = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" diff --git a/android/core/consumer-rules.pro b/android/core/consumer-rules.pro deleted file mode 100644 index e69de29..0000000 diff --git a/android/core/src/main/cpp/CMakeLists.txt b/android/core/src/main/cpp/CMakeLists.txt index 7c29f76..cdd7623 100644 --- a/android/core/src/main/cpp/CMakeLists.txt +++ b/android/core/src/main/cpp/CMakeLists.txt @@ -4,6 +4,8 @@ project("core") message("CMAKE_SOURCE_DIR ${CMAKE_SOURCE_DIR}") +message("CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE}") + if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") add_compile_options(-O3) diff --git a/android/core/src/main/cpp/core.cpp b/android/core/src/main/cpp/core.cpp index 5392300..19804e8 100644 --- a/android/core/src/main/cpp/core.cpp +++ b/android/core/src/main/cpp/core.cpp @@ -1,21 +1,18 @@ -#include - #ifdef LIBCLASH #include -#include #include "jni_helper.h" #include "libclash.h" extern "C" JNIEXPORT void JNICALL -Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb) { - auto interface = new_global(cb); +Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject, const jint fd, jobject cb) { + const auto interface = new_global(cb); startTUN(fd, interface); } extern "C" JNIEXPORT void JNICALL -Java_com_follow_clash_core_Core_stopTun(JNIEnv *env, jobject thiz) { +Java_com_follow_clash_core_Core_stopTun(JNIEnv *) { stopTun(); } @@ -26,50 +23,50 @@ static jmethodID m_tun_interface_resolve_process; static void release_jni_object_impl(void *obj) { ATTACH_JNI(); - del_global((jobject) obj); + del_global(static_cast(obj)); } -static void call_tun_interface_protect_impl(void *tun_interface, int fd) { +static void call_tun_interface_protect_impl(void *tun_interface, const int fd) { ATTACH_JNI(); - env->CallVoidMethod((jobject) tun_interface, - (jmethodID) m_tun_interface_protect, - (jint) fd); + env->CallVoidMethod(static_cast(tun_interface), + m_tun_interface_protect, + fd); } -static const char* +static const char * call_tun_interface_resolve_process_impl(void *tun_interface, int protocol, - const char *source, - const char *target, - int uid) { + const char *source, + const char *target, + const int uid) { ATTACH_JNI(); - jstring packageName = (jstring)env->CallObjectMethod((jobject) tun_interface, - (jmethodID) m_tun_interface_resolve_process, - (jint) protocol, - (jstring) new_string(source), - (jstring) new_string(target), - (jint) uid); + const auto packageName = reinterpret_cast(env->CallObjectMethod(static_cast(tun_interface), + m_tun_interface_resolve_process, + protocol, + new_string(source), + new_string(target), + uid)); return get_string(packageName); } extern "C" JNIEXPORT jint JNICALL -JNI_OnLoad(JavaVM *vm, void *reserved) { +JNI_OnLoad(JavaVM *vm, void *) { JNIEnv *env = nullptr; - if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } initialize_jni(vm, env); - jclass c_tun_interface = find_class("com/follow/clash/core/TunInterface"); + const auto c_tun_interface = find_class("com/follow/clash/core/TunInterface"); m_tun_interface_protect = find_method(c_tun_interface, "protect", "(I)V"); m_tun_interface_resolve_process = find_method(c_tun_interface, "resolverProcess", - "(ILjava/lang/String;Ljava/lang/String;I)Ljava/lang/String;"); + "(ILjava/lang/String;Ljava/lang/String;I)Ljava/lang/String;"); registerCallbacks(&call_tun_interface_protect_impl, &call_tun_interface_resolve_process_impl, &release_jni_object_impl); return JNI_VERSION_1_6; } -#endif \ No newline at end of file +#endif diff --git a/android/core/src/main/cpp/jni_helper.cpp b/android/core/src/main/cpp/jni_helper.cpp index cb0f853..e840fe3 100644 --- a/android/core/src/main/cpp/jni_helper.cpp +++ b/android/core/src/main/cpp/jni_helper.cpp @@ -1,5 +1,6 @@ #include "jni_helper.h" +#include #include #include @@ -12,7 +13,7 @@ static jmethodID m_get_bytes; void initialize_jni(JavaVM *vm, JNIEnv *env) { global_vm = vm; - c_string = (jclass) new_global(find_class("java/lang/String")); + c_string = reinterpret_cast(new_global(find_class("java/lang/String"))); m_new_string = find_method(c_string, "", "([B)V"); m_get_bytes = find_method(c_string, "getBytes", "()[B"); } @@ -22,23 +23,23 @@ JavaVM *global_java_vm() { } char *jni_get_string(JNIEnv *env, jstring str) { - auto array = (jbyteArray) env->CallObjectMethod(str, m_get_bytes); - int length = env->GetArrayLength(array); - char *content = (char *) malloc(length + 1); - env->GetByteArrayRegion(array, 0, length, (jbyte *) content); + const auto array = reinterpret_cast(env->CallObjectMethod(str, m_get_bytes)); + const int length = env->GetArrayLength(array); + const auto content = static_cast(malloc(length + 1)); + env->GetByteArrayRegion(array, 0, length, reinterpret_cast(content)); content[length] = 0; return content; } jstring jni_new_string(JNIEnv *env, const char *str) { - auto length = (int) strlen(str); - jbyteArray array = env->NewByteArray(length); - env->SetByteArrayRegion(array, 0, length, (const jbyte *) str); - return (jstring) env->NewObject(c_string, m_new_string, array); + const auto length = static_cast(strlen(str)); + const auto array = env->NewByteArray(length); + env->SetByteArrayRegion(array, 0, length, reinterpret_cast(str)); + return reinterpret_cast(env->NewObject(c_string, m_new_string, array)); } int jni_catch_exception(JNIEnv *env) { - int result = env->ExceptionCheck(); + const int result = env->ExceptionCheck(); if (result) { env->ExceptionDescribe(); env->ExceptionClear(); @@ -46,9 +47,9 @@ int jni_catch_exception(JNIEnv *env) { return result; } -void jni_attach_thread(struct scoped_jni *jni) { +void jni_attach_thread(scoped_jni *jni) { JavaVM *vm = global_java_vm(); - if (vm->GetEnv((void **) &jni->env, JNI_VERSION_1_6) == JNI_OK) { + if (vm->GetEnv(reinterpret_cast(&jni->env), JNI_VERSION_1_6) == JNI_OK) { jni->require_release = 0; return; } @@ -58,9 +59,9 @@ void jni_attach_thread(struct scoped_jni *jni) { jni->require_release = 1; } -void jni_detach_thread(struct scoped_jni *jni) { +void jni_detach_thread(const scoped_jni *env) { JavaVM *vm = global_java_vm(); - if (jni->require_release) { + if (env->require_release) { vm->DetachCurrentThread(); } } diff --git a/android/core/src/main/cpp/jni_helper.h b/android/core/src/main/cpp/jni_helper.h index a952865..57fbd55 100644 --- a/android/core/src/main/cpp/jni_helper.h +++ b/android/core/src/main/cpp/jni_helper.h @@ -1,9 +1,6 @@ #pragma once #include -#include -#include -#include struct scoped_jni { JNIEnv *env; @@ -18,14 +15,14 @@ extern char *jni_get_string(JNIEnv *env, jstring str); extern int jni_catch_exception(JNIEnv *env); -extern void jni_attach_thread(struct scoped_jni *jni); +extern void jni_attach_thread(scoped_jni *jni); -extern void jni_detach_thread(struct scoped_jni *env); +extern void jni_detach_thread(const scoped_jni *env); extern void release_string(char **str); #define ATTACH_JNI() __attribute__((unused, cleanup(jni_detach_thread))) \ - struct scoped_jni _jni; \ + scoped_jni _jni{}; \ jni_attach_thread(&_jni); \ JNIEnv *env = _jni.env @@ -36,4 +33,4 @@ extern void release_string(char **str); #define new_global(obj) env->NewGlobalRef(obj) #define del_global(obj) env->DeleteGlobalRef(obj) #define get_string(jstr) jni_get_string(env, jstr) -#define new_string(cstr) jni_new_string(env, cstr) \ No newline at end of file +#define new_string(cstr) jni_new_string(env, cstr) diff --git a/android/gradle.properties b/android/gradle.properties index f1c93ba..9b8b8c5 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true kotlin_version=1.9.22 -agp_version=8.9.1 +agp_version=8.9.2 diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index 074e057..0000000 --- a/android/settings.gradle +++ /dev/null @@ -1,27 +0,0 @@ -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' diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..8daaa66 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,29 @@ +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") diff --git a/arb/intl_en.arb b/arb/intl_en.arb index bbf21f1..87009b8 100644 --- a/arb/intl_en.arb +++ b/arb/intl_en.arb @@ -13,7 +13,6 @@ "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", @@ -22,7 +21,6 @@ "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", @@ -149,8 +147,6 @@ "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", @@ -185,8 +181,6 @@ "expirationTime": "Expiration time", "connections": "Connections", "connectionsDesc": "View current connections data", - "nullRequestsDesc": "No requests", - "nullConnectionsDesc": "No connections", "intranetIP": "Intranet IP", "view": "View", "cut": "Cut", @@ -219,7 +213,6 @@ "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", @@ -250,7 +243,6 @@ "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", @@ -337,15 +329,12 @@ "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", @@ -353,28 +342,21 @@ "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", @@ -400,5 +382,29 @@ "recoveryStrategy": "Recovery strategy", "recoveryStrategy_override": "Override", "recoveryStrategy_compatible": "Compatible", - "logsTest": "Logs test" + "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", + "import": "Import", + "importFile": "Import from file", + "importUrl": "Import from URL", + "autoSetSystemDns": "Auto set system DNS" } \ No newline at end of file diff --git a/arb/intl_ja.arb b/arb/intl_ja.arb index c3093a9..5ee7bce 100644 --- a/arb/intl_ja.arb +++ b/arb/intl_ja.arb @@ -13,7 +13,6 @@ "resourcesDesc": "外部リソース関連情報", "trafficUsage": "トラフィック使用量", "coreInfo": "コア情報", - "nullCoreInfoDesc": "コア情報を取得できません", "networkSpeed": "ネットワーク速度", "outboundMode": "アウトバウンドモード", "networkDetection": "ネットワーク検出", @@ -22,7 +21,6 @@ "noProxy": "プロキシなし", "noProxyDesc": "プロファイルを作成するか、有効なプロファイルを追加してください", "nullProfileDesc": "プロファイルがありません。追加してください", - "nullLogsDesc": "ログがありません", "settings": "設定", "language": "言語", "defaultText": "デフォルト", @@ -149,8 +147,6 @@ "addressHelp": "WebDAVサーバーアドレス", "addressTip": "有効なWebDAVアドレスを入力", "password": "パスワード", - "passwordTip": "パスワードは必須です", - "accountTip": "アカウントは必須です", "checkUpdate": "更新を確認", "discoverNewVersion": "新バージョンを発見", "checkUpdateError": "アプリは最新版です", @@ -185,8 +181,6 @@ "expirationTime": "有効期限", "connections": "接続", "connectionsDesc": "現在の接続データを表示", - "nullRequestsDesc": "リクエストなし", - "nullConnectionsDesc": "接続なし", "intranetIP": "イントラネットIP", "view": "表示", "cut": "切り取り", @@ -219,7 +213,6 @@ "autoCloseConnectionsDesc": "ノード変更後に接続を自動閉じる", "onlyStatisticsProxy": "プロキシのみ統計", "onlyStatisticsProxyDesc": "有効化するとプロキシトラフィックのみ統計", - "deleteProfileTip": "現在のプロファイルを削除しますか?", "pureBlackMode": "純黒モード", "keepAliveIntervalDesc": "TCPキープアライブ間隔", "entries": " エントリ", @@ -250,7 +243,6 @@ "dnsDesc": "DNS関連設定の更新", "key": "キー", "value": "値", - "notEmpty": "空欄不可", "hostsDesc": "ホストを追加", "vpnTip": "変更はVPN再起動後に有効", "vpnEnableDesc": "VpnService経由で全システムトラフィックをルーティング", @@ -337,15 +329,12 @@ "fileIsUpdate": "ファイルが変更されました。保存しますか?", "profileHasUpdate": "プロファイルが変更されました。自動更新を無効化しますか?", "hasCacheChange": "変更をキャッシュしますか?", - "nullProxies": "プロキシなし", "copySuccess": "コピー成功", "copyLink": "リンクをコピー", "exportFile": "ファイルをエクスポート", "cacheCorrupt": "キャッシュが破損しています。クリアしますか?", "detectionTip": "サードパーティAPIに依存(参考値)", "listen": "リスン", - "keyExists": "現在のキーは既に存在します", - "valueExists": "現在の値は既に存在します", "undo": "元に戻す", "redo": "やり直す", "none": "なし", @@ -353,28 +342,21 @@ "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": "トーンスポット", @@ -401,5 +383,29 @@ "recoveryStrategy": "リカバリー戦略", "recoveryStrategy_override": "オーバーライド", "recoveryStrategy_compatible": "互換性", - "logsTest": "ログテスト" + "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": "別のポートを入力してください", + "import": "インポート", + "importFile": "ファイルからインポート", + "importUrl": "URLからインポート", + "autoSetSystemDns": "オートセットシステムDNS" } \ No newline at end of file diff --git a/arb/intl_ru.arb b/arb/intl_ru.arb index 31ad5a2..6067193 100644 --- a/arb/intl_ru.arb +++ b/arb/intl_ru.arb @@ -13,7 +13,6 @@ "resourcesDesc": "Информация, связанная с внешними ресурсами", "trafficUsage": "Использование трафика", "coreInfo": "Информация о ядре", - "nullCoreInfoDesc": "Не удалось получить информацию о ядре", "networkSpeed": "Скорость сети", "outboundMode": "Режим исходящего трафика", "networkDetection": "Обнаружение сети", @@ -22,7 +21,6 @@ "noProxy": "Нет прокси", "noProxyDesc": "Пожалуйста, создайте профиль или добавьте действительный профиль", "nullProfileDesc": "Нет профиля, пожалуйста, добавьте профиль", - "nullLogsDesc": "Нет логов", "settings": "Настройки", "language": "Язык", "defaultText": "По умолчанию", @@ -149,8 +147,6 @@ "addressHelp": "Адрес сервера WebDAV", "addressTip": "Пожалуйста, введите действительный адрес WebDAV", "password": "Пароль", - "passwordTip": "Пароль не может быть пустым", - "accountTip": "Аккаунт не может быть пустым", "checkUpdate": "Проверить обновления", "discoverNewVersion": "Обнаружена новая версия", "checkUpdateError": "Текущее приложение уже является последней версией", @@ -185,8 +181,6 @@ "expirationTime": "Время истечения", "connections": "Соединения", "connectionsDesc": "Просмотр текущих данных о соединениях", - "nullRequestsDesc": "Нет запросов", - "nullConnectionsDesc": "Нет соединений", "intranetIP": "Внутренний IP", "view": "Просмотр", "cut": "Вырезать", @@ -219,7 +213,6 @@ "autoCloseConnectionsDesc": "Автоматически закрывать соединения после смены узла", "onlyStatisticsProxy": "Только статистика прокси", "onlyStatisticsProxyDesc": "При включении будет учитываться только трафик прокси", - "deleteProfileTip": "Вы уверены, что хотите удалить текущий профиль?", "pureBlackMode": "Чисто черный режим", "keepAliveIntervalDesc": "Интервал поддержания TCP-соединения", "entries": " записей", @@ -250,7 +243,6 @@ "dnsDesc": "Обновление настроек, связанных с DNS", "key": "Ключ", "value": "Значение", - "notEmpty": "Не может быть пустым", "hostsDesc": "Добавить Hosts", "vpnTip": "Изменения вступят в силу после перезапуска VPN", "vpnEnableDesc": "Автоматически направляет весь системный трафик через VpnService", @@ -337,15 +329,12 @@ "fileIsUpdate": "Файл был изменен. Хотите сохранить изменения?", "profileHasUpdate": "Профиль был изменен. Хотите отключить автообновление?", "hasCacheChange": "Хотите сохранить изменения в кэше?", - "nullProxies": "Нет прокси", "copySuccess": "Копирование успешно", "copyLink": "Копировать ссылку", "exportFile": "Экспорт файла", "cacheCorrupt": "Кэш поврежден. Хотите очистить его?", "detectionTip": "Опирается на сторонний API, только для справки", "listen": "Слушать", - "keyExists": "Текущий ключ уже существует", - "valueExists": "Текущее значение уже существует", "undo": "Отменить", "redo": "Повторить", "none": "Нет", @@ -353,28 +342,21 @@ "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": "Тональный акцент", @@ -401,5 +383,29 @@ "recoveryStrategy": "Стратегия восстановления", "recoveryStrategy_override": "Переопределение", "recoveryStrategy_compatible": "Совместимый", - "logsTest": "Тест журналов" + "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": "Введите другой порт", + "import": "Импорт", + "importFile": "Импорт из файла", + "importUrl": "Импорт по URL", + "autoSetSystemDns": "Автоматическая настройка системного DNS" } \ No newline at end of file diff --git a/arb/intl_zh_CN.arb b/arb/intl_zh_CN.arb index 3b7e279..5baf1cd 100644 --- a/arb/intl_zh_CN.arb +++ b/arb/intl_zh_CN.arb @@ -13,7 +13,6 @@ "resourcesDesc": "外部资源相关信息", "trafficUsage": "流量统计", "coreInfo": "内核信息", - "nullCoreInfoDesc": "无法获取内核信息", "networkSpeed": "网络速度", "outboundMode": "出站模式", "networkDetection": "网络检测", @@ -22,7 +21,6 @@ "noProxy": "暂无代理", "noProxyDesc": "请创建配置文件或者添加有效配置文件", "nullProfileDesc": "没有配置文件,请先添加配置文件", - "nullLogsDesc": "暂无日志", "settings": "设置", "language": "语言", "defaultText": "默认", @@ -149,8 +147,6 @@ "addressHelp": "WebDAV服务器地址", "addressTip": "请输入有效的WebDAV地址", "password": "密码", - "passwordTip": "密码不能为空", - "accountTip": "账号不能为空", "checkUpdate": "检查更新", "discoverNewVersion": "发现新版本", "checkUpdateError": "当前应用已经是最新版了", @@ -185,8 +181,6 @@ "expirationTime": "到期时间", "connections": "连接", "connectionsDesc": "查看当前连接数据", - "nullRequestsDesc": "暂无请求", - "nullConnectionsDesc": "暂无连接", "intranetIP": "内网 IP", "view": "查看", "cut": "剪切", @@ -219,7 +213,6 @@ "autoCloseConnectionsDesc": "切换节点后自动关闭连接", "onlyStatisticsProxy": "仅统计代理", "onlyStatisticsProxyDesc": "开启后,将只统计代理流量", - "deleteProfileTip": "确定要删除当前配置吗?", "pureBlackMode": "纯黑模式", "keepAliveIntervalDesc": "TCP保持活动间隔", "entries": "个条目", @@ -250,7 +243,6 @@ "dnsDesc": "更新DNS相关设置", "key": "键", "value": "值", - "notEmpty": "不能为空", "hostsDesc": "追加Hosts", "vpnTip": "重启VPN后改变生效", "vpnEnableDesc": "通过VpnService自动路由系统所有流量", @@ -337,15 +329,12 @@ "fileIsUpdate": "文件有修改,是否保存修改", "profileHasUpdate": "配置文件已经修改,是否关闭自动更新 ", "hasCacheChange": "是否缓存修改", - "nullProxies": "暂无代理", "copySuccess": "复制成功", "copyLink": "复制链接", "exportFile": "导出文件", "cacheCorrupt": "缓存已损坏,是否清空?", "detectionTip": "依赖第三方api,仅供参考", "listen": "监听", - "keyExists": "当前键已存在", - "valueExists": "当前值已存在", "undo": "撤销", "redo": "重做", "none": "无", @@ -353,28 +342,21 @@ "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": "调性点缀", @@ -401,5 +383,29 @@ "recoveryStrategy": "恢复策略", "recoveryStrategy_override": "覆盖", "recoveryStrategy_compatible": "兼容", - "logsTest": "日志测试" + "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": "请输入不同的端口", + "import": "导入", + "importFile": "通过文件导入", + "importUrl": "通过URL导入", + "autoSetSystemDns": "自动设置系统DNS" } diff --git a/core/Clash.Meta b/core/Clash.Meta index 6eac0e5..413467a 160000 --- a/core/Clash.Meta +++ b/core/Clash.Meta @@ -1 +1 @@ -Subproject commit 6eac0e5d3da6e1c40d123bdab89c13bd6d63214e +Subproject commit 413467ae6b3665fa63d072f18dfccd871c9cda6a diff --git a/core/action.go b/core/action.go index def5cd1..61ba4b6 100644 --- a/core/action.go +++ b/core/action.go @@ -5,16 +5,17 @@ import ( ) type Action struct { - Id string `json:"id"` - Method Method `json:"method"` - Data interface{} `json:"data"` - DefaultValue interface{} `json:"default-value"` + Id string `json:"id"` + Method Method `json:"method"` + Data interface{} `json:"data"` } 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) { @@ -22,99 +23,117 @@ func (result ActionResult) Json() ([]byte, error) { return data, err } -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) success(data interface{}) { + result.Code = 0 + result.Data = data + result.send() } -func handleAction(action *Action, result func(data interface{})) { +func (result ActionResult) error(data interface{}) { + result.Code = -1 + result.Data = data + result.send() +} + +func handleAction(action *Action, result ActionResult) { switch action.Method { case initClashMethod: paramsString := action.Data.(string) - result(handleInitClash(paramsString)) + result.success(handleInitClash(paramsString)) return case getIsInitMethod: - result(handleGetIsInit()) + result.success(handleGetIsInit()) return case forceGcMethod: handleForceGc() - result(true) + result.success(true) return case shutdownMethod: - result(handleShutdown()) + result.success(handleShutdown()) return case validateConfigMethod: data := []byte(action.Data.(string)) - result(handleValidateConfig(data)) + result.success(handleValidateConfig(data)) return case updateConfigMethod: data := []byte(action.Data.(string)) - result(handleUpdateConfig(data)) + result.success(handleUpdateConfig(data)) + return + case setupConfigMethod: + data := []byte(action.Data.(string)) + result.success(handleSetupConfig(data)) return case getProxiesMethod: - result(handleGetProxies()) + result.success(handleGetProxies()) return case changeProxyMethod: data := action.Data.(string) handleChangeProxy(data, func(value string) { - result(value) + result.success(value) }) return case getTrafficMethod: - result(handleGetTraffic()) + result.success(handleGetTraffic()) return case getTotalTrafficMethod: - result(handleGetTotalTraffic()) + result.success(handleGetTotalTraffic()) return case resetTrafficMethod: handleResetTraffic() - result(true) + result.success(true) return case asyncTestDelayMethod: data := action.Data.(string) handleAsyncTestDelay(data, func(value string) { - result(value) + result.success(value) }) return case getConnectionsMethod: - result(handleGetConnections()) + result.success(handleGetConnections()) return case closeConnectionsMethod: - result(handleCloseConnections()) + 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) return case closeConnectionMethod: id := action.Data.(string) - result(handleCloseConnection(id)) + result.success(handleCloseConnection(id)) return case getExternalProvidersMethod: - result(handleGetExternalProviders()) + result.success(handleGetExternalProviders()) return case getExternalProviderMethod: externalProviderName := action.Data.(string) - result(handleGetExternalProvider(externalProviderName)) + result.success(handleGetExternalProvider(externalProviderName)) case updateGeoDataMethod: paramsString := action.Data.(string) var params = map[string]string{} err := json.Unmarshal([]byte(paramsString), ¶ms) if err != nil { - result(err.Error()) + result.success(err.Error()) return } geoType := params["geo-type"] geoName := params["geo-name"] handleUpdateGeoData(geoType, geoName, func(value string) { - result(value) + result.success(value) }) return case updateExternalProviderMethod: providerName := action.Data.(string) handleUpdateExternalProvider(providerName, func(value string) { - result(value) + result.success(value) }) return case sideLoadExternalProviderMethod: @@ -122,59 +141,48 @@ func handleAction(action *Action, result func(data interface{})) { var params = map[string]string{} err := json.Unmarshal([]byte(paramsString), ¶ms) if err != nil { - result(err.Error()) + result.success(err.Error()) return } providerName := params["providerName"] data := params["data"] handleSideLoadExternalProvider(providerName, []byte(data), func(value string) { - result(value) + result.success(value) }) return case startLogMethod: handleStartLog() - result(true) + result.success(true) return case stopLogMethod: handleStopLog() - result(true) + result.success(true) return case startListenerMethod: - result(handleStartListener()) + result.success(handleStartListener()) return case stopListenerMethod: - result(handleStopListener()) + result.success(handleStopListener()) return case getCountryCodeMethod: ip := action.Data.(string) handleGetCountryCode(ip, func(value string) { - result(value) + result.success(value) }) return case getMemoryMethod: handleGetMemory(func(value string) { - result(value) - }) - return - case getProfileMethod: - profileId := action.Data.(string) - handleGetMemory(func(value string) { - result(handleGetProfile(profileId)) + result.success(value) }) return case setStateMethod: data := action.Data.(string) handleSetState(data) - result(true) + result.success(true) case crashMethod: - result(true) + result.success(true) handleCrash() default: - handle := nextHandle(action, result) - if handle { - return - } else { - result(action.DefaultValue) - } + nextHandle(action, result) } } diff --git a/core/android_bride.go b/core/android_bride.go index 640a397..25290bc 100644 --- a/core/android_bride.go +++ b/core/android_bride.go @@ -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 "" } diff --git a/core/common.go b/core/common.go index 506ebb5..10e422b 100644 --- a/core/common.go +++ b/core/common.go @@ -1,9 +1,10 @@ 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" @@ -21,31 +22,16 @@ 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 ( - 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)) + currentConfig *config.Config + version = 0 + isRunning = false + runLock sync.Mutex + mBatch, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50)) ) type ExternalProviders []ExternalProvider @@ -54,54 +40,6 @@ 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() { @@ -166,143 +104,15 @@ func sideUpdateExternalProvider(p cp.Provider, bytes []byte) error { } } -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) { +func updateListeners() { if !isRunning { return } - general := currentConfig.General - listeners := currentConfig.Listeners - if force == true { - stopListeners() + if currentConfig == nil { + return } + listeners := currentConfig.Listeners + general := currentConfig.General listener.PatchInboundListeners(listeners, tunnel.Tunnel, true) listener.SetAllowLan(general.AllowLan) inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes) @@ -326,11 +136,7 @@ func stopListeners() { listener.StopListener() } -func patchSelectGroup() { - mapping := configParams.SelectedMap - if mapping == nil { - return - } +func patchSelectGroup(mapping map[string]string) { for name, proxy := range tunnel.ProxiesWithProviders() { outbound, ok := proxy.(*adapter.Proxy) if !ok { @@ -351,20 +157,102 @@ func patchSelectGroup() { } } -func applyConfig(rawConfig *config.RawConfig) error { +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 { runLock.Lock() defer runLock.Unlock() var err error - currentConfig, err = config.ParseRawConfig(rawConfig) + constant.DefaultTestURL = params.TestURL + currentConfig, err = config.ParseRawConfig(params.Config) if err != nil { currentConfig, _ = config.ParseRawConfig(config.DefaultRawConfig()) } - if configParams.IsPatch { - patchConfig() - } else { - hub.ApplyConfig(currentConfig) - patchSelectGroup() - } - updateListeners(false) + 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) return err } diff --git a/core/constant.go b/core/constant.go index f3a9ab9..47039cf 100644 --- a/core/constant.go +++ b/core/constant.go @@ -3,7 +3,12 @@ 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" ) @@ -12,19 +17,34 @@ type InitParams struct { Version int `json:"version"` } -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 SetupParams struct { + Config *config.RawConfig `json:"config"` + SelectedMap map[string]string `json:"selected-map"` + TestURL string `json:"test-url"` } -type GenerateConfigParams struct { - ProfileId string `json:"profile-id"` - Config config.RawConfig `json:"config" ` - Params ConfigExtendedParams `json:"params"` +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 ChangeProxyParams struct { @@ -64,6 +84,7 @@ const ( asyncTestDelayMethod Method = "asyncTestDelay" getConnectionsMethod Method = "getConnections" closeConnectionsMethod Method = "closeConnections" + resetConnectionsMethod Method = "resetConnectionsMethod" closeConnectionMethod Method = "closeConnection" getExternalProvidersMethod Method = "getExternalProviders" getExternalProviderMethod Method = "getExternalProvider" @@ -81,8 +102,9 @@ 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 diff --git a/core/go.mod b/core/go.mod index d7a5220..ff86ea2 100644 --- a/core/go.mod +++ b/core/go.mod @@ -6,13 +6,12 @@ replace github.com/metacubex/mihomo => ./Clash.Meta require ( github.com/metacubex/mihomo v0.0.0-00010101000000-000000000000 - 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.2.4 // indirect + github.com/RyuaNerin/go-krypto v1.3.0 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect @@ -21,13 +20,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.2 // indirect + github.com/ebitengine/purego v0.8.3 // indirect github.com/enfein/mieru/v3 v3.13.0 // indirect - github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect + github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gaukas/godicttls v0.0.4 // indirect github.com/go-chi/chi/v5 v5.2.1 // indirect github.com/go-chi/render v1.0.3 // indirect @@ -36,7 +35,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.1 // indirect + github.com/gofrs/uuid/v5 v5.3.2 // 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 @@ -51,22 +50,27 @@ require ( github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.4.1 // indirect github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect - github.com/metacubex/bart v0.19.0 // indirect + github.com/metacubex/bart v0.20.5 // 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/quic-go v0.49.1-0.20250212162123-c135a4412996 // indirect + github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect + github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639 // indirect github.com/metacubex/randv2 v0.2.0 // 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/sing v0.5.3 // indirect + github.com/metacubex/sing-mux v0.3.2 // indirect + github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f // indirect + github.com/metacubex/sing-shadowsocks v0.2.10 // indirect + github.com/metacubex/sing-shadowsocks2 v0.2.4 // indirect + github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect + github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c // indirect + github.com/metacubex/sing-vmess v0.2.2 // indirect + github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect + github.com/metacubex/smux v0.0.0-20250503055512-501391591dee // indirect + github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 // indirect + github.com/metacubex/utls v1.7.3 // 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 @@ -78,14 +82,9 @@ 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/samber/lo v1.50.0 // 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 diff --git a/core/go.sum b/core/go.sum index ebec92d..6517b72 100644 --- a/core/go.sum +++ b/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/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/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/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.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= -github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc= +github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/enfein/mieru/v3 v3.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-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8= -github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= +github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= +github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= 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.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +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/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.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0= -github.com/gofrs/uuid/v5 v5.3.1/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +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/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= @@ -97,38 +97,49 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4= github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI= -github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY= -github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI= +github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM= +github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro= github.com/metacubex/chacha v0.1.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/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/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.52.1-0.20250522021943-aef454b9e639 h1:L+1brQNzBhCCxWlicwfK1TlceemCRmrDE4HmcVHc29w= +github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639/go.mod h1:Kc6h++Q/zf3AxcUCevJhJwgrskJumv+pZdR8g/E/10k= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= -github.com/metacubex/sing-quic v0.0.0-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/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= +github.com/metacubex/sing v0.5.3 h1:QWdN16WFKMk06x4nzkc8SvZ7y2x+TLQrpkPoHs+WSVM= +github.com/metacubex/sing v0.5.3/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-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I= +github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM= +github.com/metacubex/sing-shadowsocks v0.2.10 h1:Pr7LDbjMANIQHl07zWgl1vDuhpsfDQUpZ8cX6DPabfg= +github.com/metacubex/sing-shadowsocks v0.2.10/go.mod h1:MtRM0ZZjR0kaDOzy9zWSt6/4/UlrnsNBq+1FNAF4vBk= +github.com/metacubex/sing-shadowsocks2 v0.2.4 h1:Ec0x3hHR7xkld5Z09IGh16wtUUpBb2HgqZ9DExd8Q7s= +github.com/metacubex/sing-shadowsocks2 v0.2.4/go.mod h1:WP8+S0kqtnSbX1vlIpo5i8Irm/ijZITEPBcZ26B5unY= +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.20250524142129-9d110c0af70c h1:Y6jk7AH5BEg9Dsvczrf/KokYsvxeKSZZlCLHg+hC4ro= +github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U= +github.com/metacubex/sing-vmess v0.2.2 h1:nG6GIKF1UOGmlzs+BIetdGHkFZ20YqFVIYp5Htqzp+4= +github.com/metacubex/sing-vmess v0.2.2/go.mod h1:CVDNcdSLVYFgTHQlubr88d8CdqupAUDqLjROos+H9xk= +github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= +github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= +github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo= +github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= +github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc= +github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= +github.com/metacubex/utls v1.7.3 h1:yDcMEWojFh+t8rU9X0HPcZDPAoFze/rIIyssqivzj8A= +github.com/metacubex/utls v1.7.3/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= @@ -159,25 +170,12 @@ 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/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/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY= +github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc= 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= @@ -189,9 +187,16 @@ 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= @@ -251,7 +256,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.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= diff --git a/core/hub.go b/core/hub.go index 06095e9..f8a0462 100644 --- a/core/hub.go +++ b/core/hub.go @@ -10,6 +10,7 @@ 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" @@ -28,10 +29,8 @@ import ( var ( isInit = false - configParams = ConfigExtendedParams{} externalProviders = map[string]cp.Provider{} logSubscriber observable.Subscription[log.Event] - currentConfig *config.Config ) func handleInitClash(paramsString string) bool { @@ -52,7 +51,8 @@ func handleStartListener() bool { runLock.Lock() defer runLock.Unlock() isRunning = true - updateListeners(true) + updateListeners() + resolver.ResetConnection() return true } @@ -91,30 +91,10 @@ func handleValidateConfig(bytes []byte) string { return "" } -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 { +func handleGetProxies() map[string]constant.Proxy { runLock.Lock() defer runLock.Unlock() - data, err := json.Marshal(tunnel.ProxiesWithProviders()) - if err != nil { - return "" - } - return string(data) + return tunnel.ProxiesWithProviders() } func handleChangeProxy(data string, fn func(string string)) { @@ -184,21 +164,12 @@ 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)) { - b.Go(paramsString, func() (bool, error) { + mBatch.Go(paramsString, func() (bool, error) { var params = &TestDelayParams{} err := json.Unmarshal([]byte(paramsString), params) if err != nil { @@ -266,6 +237,11 @@ func handleGetConnections() string { func handleCloseConnections() bool { runLock.Lock() defer runLock.Unlock() + closeConnections() + return true +} + +func closeConnections() { statistic.DefaultManager.Range(func(c statistic.Tracker) bool { err := c.Close() if err != nil { @@ -273,6 +249,12 @@ func handleCloseConnections() bool { } return true }) +} + +func handleResetConnections() bool { + runLock.Lock() + defer runLock.Unlock() + resolver.ResetConnection() return true } @@ -442,10 +424,47 @@ 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{ diff --git a/core/lib.go b/core/lib.go index 9f2c037..b5e6229 100644 --- a/core/lib.go +++ b/core/lib.go @@ -39,6 +39,14 @@ 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) @@ -49,22 +57,38 @@ func invokeAction(paramsChar *C.char, port C.longlong) { bridge.SendToPort(i, err.Error()) return } - go handleAction(action, func(data interface{}) { - bridge.SendToPort(i, string(action.getResult(data))) - }) + result := ActionResult{ + Id: action.Id, + Method: action.Method, + Port: i, + } + go handleAction(action, result) } func sendMessage(message Message) { if messagePort == -1 { return } - res, err := message.Json() - if err != nil { - return - } - bridge.SendToPort(messagePort, string(Action{ + result := ActionResult{ Method: messageMethod, - }.getResult(res))) + 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)) } //export startListener diff --git a/core/lib_android.go b/core/lib_android.go index 9ed8541..00b3b35 100644 --- a/core/lib_android.go +++ b/core/lib_android.go @@ -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 func(data interface{})) bool { +func nextHandle(action *Action, result ActionResult) bool { switch action.Method { case getAndroidVpnOptionsMethod: - result(handleGetAndroidVpnOptions()) + result.success(handleGetAndroidVpnOptions()) return true case updateDnsMethod: data := action.Data.(string) handleUpdateDns(data) - result(true) + result.success(true) return true case getRunTimeMethod: - result(handleGetRunTime()) + result.success(handleGetRunTime()) return true case getCurrentProfileNameMethod: - result(handleGetCurrentProfileName()) + result.success(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, handleUpdateConfig(bytes)) + bridge.SendToPort(i, handleSetupConfig(bytes)) }() } diff --git a/core/server.go b/core/server.go index 719a793..d15d9ed 100644 --- a/core/server.go +++ b/core/server.go @@ -12,14 +12,20 @@ import ( var conn net.Conn -func sendMessage(message Message) { - res, err := message.Json() +func (result ActionResult) send() { + data, err := result.Json() if err != nil { return } - send(Action{ + send(data) +} + +func sendMessage(message Message) { + result := ActionResult{ Method: messageMethod, - }.getResult(res)) + Data: message, + } + result.send() } func send(data []byte) { @@ -61,12 +67,15 @@ func startServer(arg string) { return } - go handleAction(action, func(data interface{}) { - send(action.getResult(data)) - }) + result := ActionResult{ + Id: action.Id, + Method: action.Method, + } + + go handleAction(action, result) } } -func nextHandle(action *Action, result func(data interface{})) bool { +func nextHandle(action *Action, result ActionResult) bool { return false } diff --git a/lib/application.dart b/lib/application.dart index 0f62f2d..3fd92bc 100644 --- a/lib/application.dart +++ b/lib/application.dart @@ -1,5 +1,6 @@ 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'; @@ -100,7 +101,10 @@ class ApplicationState extends ConsumerState { return AppStateManager( child: ClashManager( child: ConnectivityManager( - onConnectivityChanged: () { + onConnectivityChanged: (results) async { + if (!results.contains(ConnectivityResult.vpn)) { + await clashCore.closeConnections(); + } globalState.appController.updateLocalIp(); globalState.appController.addCheckIpNumDebounce(); }, diff --git a/lib/clash/core.dart b/lib/clash/core.dart index 2a880f6..650933f 100644 --- a/lib/clash/core.dart +++ b/lib/clash/core.dart @@ -66,6 +66,11 @@ class ClashCore { Future init() async { await initGeo(); + if (globalState.config.appSetting.openLogs) { + clashCore.startLog(); + } else { + clashCore.stopLog(); + } final homeDirPath = await appPath.homeDirPath; return await clashInterface.init( InitParams( @@ -89,39 +94,39 @@ class ClashCore { return clashInterface.validateConfig(data); } - Future updateConfig(UpdateConfigParams updateConfigParams) async { - return await clashInterface.updateConfig(updateConfigParams); + Future updateConfig(UpdateParams updateParams) async { + return await clashInterface.updateConfig(updateParams); + } + + Future setupConfig(SetupParams setupParams) async { + return await clashInterface.setupConfig(setupParams); } Future> getProxiesGroups() async { - final proxiesRawString = await clashInterface.getProxies(); - return Isolate.run>(() { - 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 + 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) .map( - (e) => Group.fromJson(e), + (name) => proxies[name], ) + .where((proxy) => proxy != null) .toList(); - }); + return group; + }).toList(); + return groupsRaw + .map( + (e) => Group.fromJson(e), + ) + .toList(); } FutureOr changeProxy(ChangeProxyParams changeProxyParams) async { @@ -143,6 +148,10 @@ class ClashCore { clashInterface.closeConnections(); } + resetConnections() { + clashInterface.resetConnections(); + } + Future> getExternalProviders() async { final externalProvidersRawString = await clashInterface.getExternalProviders(); @@ -206,6 +215,16 @@ class ClashCore { return Delay.fromJson(json.decode(data)); } + Future> getConfig(String id) async { + final profilePath = await appPath.getProfilePath(id); + final res = await clashInterface.getConfig(profilePath); + if (res.isSuccess) { + return res.data as Map; + } else { + throw res.message; + } + } + Future getTraffic() async { final trafficString = await clashInterface.getTraffic(); if (trafficString.isEmpty) { @@ -241,14 +260,6 @@ class ClashCore { return int.parse(value); } - Future 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(); } diff --git a/lib/clash/generated/clash_ffi.dart b/lib/clash/generated/clash_ffi.dart index d20959d..c97847f 100644 --- a/lib/clash/generated/clash_ffi.dart +++ b/lib/clash/generated/clash_ffi.dart @@ -154,18 +154,18 @@ class ClashFFI { late final _setrlimit = _setrlimitPtr.asFunction)>(); - int wait1( + int wait$1( ffi.Pointer arg0, ) { - return _wait1( + return _wait$1( arg0, ); } - late final _wait1Ptr = + late final _wait$1Ptr = _lookup)>>('wait'); - late final _wait1 = - _wait1Ptr.asFunction)>(); + late final _wait$1 = + _wait$1Ptr.asFunction)>(); int waitpid( int arg0, @@ -2518,6 +2518,20 @@ class ClashFFI { late final _invokeAction = _invokeActionPtr.asFunction, int)>(); + ffi.Pointer getConfig( + ffi.Pointer s, + ) { + return _getConfig( + s, + ); + } + + late final _getConfigPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function(ffi.Pointer)>>('getConfig'); + late final _getConfig = _getConfigPtr + .asFunction Function(ffi.Pointer)>(); + void startListener() { return _startListener(); } @@ -2639,6 +2653,29 @@ class ClashFFI { _updateDnsPtr.asFunction)>(); } +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 __mbstate8; @@ -2647,6 +2684,46 @@ 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; +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)>> @@ -2731,6 +2808,48 @@ final class _opaque_pthread_t extends ffi.Struct { external ffi.Array __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 p; @@ -2738,10 +2857,6 @@ 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), @@ -2754,10 +2869,15 @@ 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; @@ -2769,9 +2889,6 @@ 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; @@ -2783,9 +2900,6 @@ 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; @@ -2914,6 +3028,9 @@ 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 ss_sp; @@ -2924,8 +3041,7 @@ final class __darwin_sigaltstack extends ffi.Struct { external int ss_flags; } -typedef __darwin_size_t = ffi.UnsignedLong; -typedef Dart__darwin_size_t = int; +typedef stack_t = __darwin_sigaltstack; final class __darwin_ucontext extends ffi.Struct { @ffi.Int() @@ -2944,7 +3060,9 @@ final class __darwin_ucontext extends ffi.Struct { external ffi.Pointer<__darwin_mcontext64> uc_mcontext; } -typedef __darwin_sigset_t = __uint32_t; +typedef ucontext_t = __darwin_ucontext; +typedef sigset_t = __darwin_sigset_t; +typedef uid_t = __darwin_uid_t; final class sigval extends ffi.Union { @ffi.Int() @@ -2968,9 +3086,6 @@ final class sigevent extends ffi.Struct { external ffi.Pointer 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; @@ -3001,12 +3116,7 @@ final class __siginfo extends ffi.Struct { external ffi.Array __pad; } -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; +typedef siginfo_t = __siginfo; final class __sigaction_u extends ffi.Union { external ffi.Pointer> @@ -3020,7 +3130,7 @@ final class __sigaction_u extends ffi.Union { } final class __sigaction extends ffi.Struct { - external __sigaction_u __sigaction_u1; + external __sigaction_u __sigaction_u$1; external ffi.Pointer< ffi.NativeFunction< @@ -3034,11 +3144,8 @@ 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_u1; + external __sigaction_u __sigaction_u$1; @sigset_t() external int sa_mask; @@ -3047,6 +3154,10 @@ 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>; + final class sigvec extends ffi.Struct { external ffi.Pointer> sv_handler; @@ -3065,6 +3176,43 @@ 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; @@ -3073,9 +3221,7 @@ final class timeval extends ffi.Struct { external int tv_usec; } -typedef __darwin_time_t = ffi.Long; -typedef Dart__darwin_time_t = int; -typedef __darwin_suseconds_t = __int32_t; +typedef rlim_t = __uint64_t; final class rusage extends ffi.Struct { external timeval ru_utime; @@ -3125,6 +3271,8 @@ final class rusage extends ffi.Struct { external int ru_nivcsw; } +typedef rusage_info_t = ffi.Pointer; + final class rusage_info_v0 extends ffi.Struct { @ffi.Array.multi([16]) external ffi.Array ri_uuid; @@ -3730,6 +3878,8 @@ final class rusage_info_v6 extends ffi.Struct { external ffi.Array ri_reserved; } +typedef rusage_info_current = rusage_info_v6; + final class rlimit extends ffi.Struct { @rlim_t() external int rlim_cur; @@ -3738,8 +3888,6 @@ 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; @@ -3748,11 +3896,11 @@ final class proc_rlimit_control_wakeupmon extends ffi.Struct { external int wm_rate; } -typedef id_t = __darwin_id_t; -typedef __darwin_id_t = __uint32_t; - final class wait extends ffi.Opaque {} +typedef ct_rune_t = __darwin_ct_rune_t; +typedef rune_t = __darwin_rune_t; + final class div_t extends ffi.Struct { @ffi.Int() external int quot; @@ -3784,18 +3932,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 __darwin_mode_t = __uint16_t; -typedef __uint16_t = ffi.UnsignedShort; -typedef Dart__uint16_t = int; -typedef protect_func = ffi.Pointer>; +typedef release_object_funcFunction = ffi.Void Function( + ffi.Pointer obj); +typedef Dartrelease_object_funcFunction = void Function( + ffi.Pointer obj); +typedef release_object_func + = ffi.Pointer>; typedef protect_funcFunction = ffi.Void Function( ffi.Pointer tun_interface, ffi.Int fd); typedef Dartprotect_funcFunction = void Function( ffi.Pointer tun_interface, int fd); -typedef resolve_process_func - = ffi.Pointer>; +typedef protect_func = ffi.Pointer>; typedef resolve_process_funcFunction = ffi.Pointer Function( ffi.Pointer tun_interface, ffi.Int protocol, @@ -3808,12 +3956,35 @@ typedef Dartresolve_process_funcFunction = ffi.Pointer Function( ffi.Pointer source, ffi.Pointer target, int uid); -typedef release_object_func - = ffi.Pointer>; -typedef release_object_funcFunction = ffi.Void Function( - ffi.Pointer obj); -typedef Dartrelease_object_funcFunction = void Function( - ffi.Pointer obj); +typedef resolve_process_func + = ffi.Pointer>; +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; +typedef GoChan = ffi.Pointer; final class GoInterface extends ffi.Struct { external ffi.Pointer t; @@ -3831,12 +4002,6 @@ 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; diff --git a/lib/clash/interface.dart b/lib/clash/interface.dart index c0ee9de..444cd22 100644 --- a/lib/clash/interface.dart +++ b/lib/clash/interface.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:isolate'; import 'package:fl_clash/clash/message.dart'; import 'package:fl_clash/common/common.dart'; @@ -19,11 +20,15 @@ mixin ClashInterface { FutureOr validateConfig(String data); + FutureOr getConfig(String path); + Future asyncTestDelay(String url, String proxyName); - FutureOr updateConfig(UpdateConfigParams updateConfigParams); + FutureOr updateConfig(UpdateParams updateParams); - FutureOr getProxies(); + FutureOr setupConfig(SetupParams setupParams); + + FutureOr getProxies(); FutureOr changeProxy(ChangeProxyParams changeProxyParams); @@ -66,18 +71,12 @@ mixin ClashInterface { FutureOr closeConnections(); - FutureOr getProfile(String id); + FutureOr resetConnections(); Future setState(CoreState state); } mixin AndroidClashInterface { - Future setFdMap(int fd); - - Future setProcessMap(ProcessMapItem item); - - // Future stopTun(); - Future updateDns(String value); Future getAndroidVpnOptions(); @@ -90,55 +89,23 @@ mixin AndroidClashInterface { abstract class ClashHandlerInterface with ClashInterface { Map callbackCompleterMap = {}; - Future 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 as String); + clashMessage.controller.add(result.data); 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 (_) { - commonPrint.log(result.id); + } catch (e) { + commonPrint.log("${result.id} error $e"); } } @@ -153,18 +120,21 @@ abstract class ClashHandlerInterface with ClashInterface { dynamic data, Duration? timeout, FutureOr Function()? onTimeout, + T? defaultValue, }) async { final id = "${method.name}#${utils.id}"; callbackCompleterMap[id] = Completer(); - dynamic defaultValue; - - if (T == String) { - defaultValue = ""; - } - if (T == bool) { - defaultValue = false; + dynamic mDefaultValue = defaultValue; + if (mDefaultValue == null) { + if (T == String) { + mDefaultValue = ""; + } else if (T == bool) { + mDefaultValue = false; + } else if (T == Map) { + mDefaultValue = {}; + } } sendMessage( @@ -173,7 +143,6 @@ abstract class ClashHandlerInterface with ClashInterface { id: id, method: method, data: data, - defaultValue: defaultValue, ), ), ); @@ -185,7 +154,7 @@ abstract class ClashHandlerInterface with ClashInterface { }, onTimeout: onTimeout ?? () { - return defaultValue; + return mDefaultValue; }, functionName: id, ); @@ -211,6 +180,7 @@ abstract class ClashHandlerInterface with ClashInterface { shutdown() async { return await invoke( method: ActionMethod.shutdown, + timeout: Duration(seconds: 1), ); } @@ -237,10 +207,31 @@ abstract class ClashHandlerInterface with ClashInterface { } @override - Future updateConfig(UpdateConfigParams updateConfigParams) async { + Future updateConfig(UpdateParams updateParams) async { return await invoke( method: ActionMethod.updateConfig, - data: json.encode(updateConfigParams), + data: json.encode(updateParams), + timeout: Duration(minutes: 2), + ); + } + + @override + Future getConfig(String path) async { + final res = await invoke( + method: ActionMethod.getConfig, + data: path, + timeout: Duration(minutes: 2), + defaultValue: Result.success({}), + ); + return res; + } + + @override + Future setupConfig(SetupParams setupParams) async { + final data = await Isolate.run(() => json.encode(setupParams)); + return await invoke( + method: ActionMethod.setupConfig, + data: data, timeout: Duration(minutes: 2), ); } @@ -253,8 +244,8 @@ abstract class ClashHandlerInterface with ClashInterface { } @override - Future getProxies() { - return invoke( + Future getProxies() { + return invoke( method: ActionMethod.getProxies, timeout: Duration(seconds: 5), ); @@ -329,17 +320,16 @@ abstract class ClashHandlerInterface with ClashInterface { } @override - Future closeConnection(String id) { + Future resetConnections() { return invoke( - method: ActionMethod.closeConnection, - data: id, + method: ActionMethod.resetConnections, ); } @override - Future getProfile(String id) { - return invoke( - method: ActionMethod.getProfile, + Future closeConnection(String id) { + return invoke( + method: ActionMethod.closeConnection, data: id, ); } diff --git a/lib/clash/lib.dart b/lib/clash/lib.dart index 9de8a2a..9cebf14 100644 --- a/lib/clash/lib.dart +++ b/lib/clash/lib.dart @@ -6,7 +6,7 @@ import 'dart:isolate'; import 'dart:ui'; import 'package:ffi/ffi.dart'; -import 'package:fl_clash/common/constant.dart'; +import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/plugins/service.dart'; @@ -62,26 +62,6 @@ class ClashLib extends ClashHandlerInterface with AndroidClashInterface { return _instance!; } - @override - Future 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(); @@ -106,22 +86,6 @@ class ClashLib extends ClashHandlerInterface with AndroidClashInterface { sendPort?.send(message); } - @override - Future setFdMap(int fd) { - return invoke( - method: ActionMethod.setFdMap, - data: json.encode(fd), - ); - } - - @override - Future setProcessMap(item) { - return invoke( - method: ActionMethod.setProcessMap, - data: item, - ); - } - // @override // Future stopTun() { // return invoke( @@ -267,9 +231,32 @@ class ClashLibHandler { return true; } + DateTime? getRunTime() { + final runTimeRaw = clashFFI.getRunTime(); + final runTimeString = runTimeRaw.cast().toDartString(); + if (runTimeString.isEmpty) { + return null; + } + return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString)); + } + + Future> getConfig(String id) async { + final path = await appPath.getProfilePath(id); + final pathChar = path.toNativeUtf8().cast(); + final configRaw = clashFFI.getConfig(pathChar); + final configString = configRaw.cast().toDartString(); + if (configString.isEmpty) { + return {}; + } + final config = json.decode(configString); + malloc.free(pathChar); + clashFFI.freeCString(configRaw); + return config; + } + Future quickStart( InitParams initParams, - UpdateConfigParams updateConfigParams, + SetupParams setupParams, CoreState state, ) { final completer = Completer(); @@ -280,7 +267,7 @@ class ClashLibHandler { receiver.close(); } }); - final params = json.encode(updateConfigParams); + final params = json.encode(setupParams); final initValue = json.encode(initParams); final stateParams = json.encode(state); final initParamsChar = initValue.toNativeUtf8().cast(); @@ -297,15 +284,10 @@ class ClashLibHandler { malloc.free(stateParamsChar); return completer.future; } - - DateTime? getRunTime() { - final runTimeRaw = clashFFI.getRunTime(); - final runTimeString = runTimeRaw.cast().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 && globalState.isService ? ClashLibHandler() : null; diff --git a/lib/clash/message.dart b/lib/clash/message.dart index 81b929b..5a81f41 100644 --- a/lib/clash/message.dart +++ b/lib/clash/message.dart @@ -1,20 +1,19 @@ 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(); + final controller = StreamController>(); ClashMessage._() { controller.stream.listen( (message) { - if(message.isEmpty){ + if (message.isEmpty) { return; } - final m = AppMessage.fromJson(json.decode(message)); + final m = AppMessage.fromJson(message); for (final AppMessageListener listener in _listeners) { switch (m.type) { case AppMessageType.log: diff --git a/lib/clash/service.dart b/lib/clash/service.dart index 2c1866c..b104eaf 100644 --- a/lib/clash/service.dart +++ b/lib/clash/service.dart @@ -1,7 +1,6 @@ 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'; @@ -51,23 +50,18 @@ class ClashService extends ClashHandlerInterface { await _destroySocket(); socketCompleter.complete(socket); socket - .transform( - StreamTransformer.fromHandlers( - handleData: (Uint8List data, EventSink sink) { - sink.add(utf8.decode(data, allowMalformed: true)); - }, - ), - ) + .transform(uint8ListToListIntConverter) + .transform(utf8.decoder) .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()); @@ -104,7 +98,13 @@ class ClashService extends ClashHandlerInterface { arg, ], ); - process!.stdout.listen((_) {}); + process?.stdout.listen((_) {}); + process?.stderr.listen((e) { + final error = utf8.decode(e); + if (error.isNotEmpty) { + commonPrint.log(error); + } + }); isStarting = false; } diff --git a/lib/common/common.dart b/lib/common/common.dart index 7aae94d..ab98059 100644 --- a/lib/common/common.dart +++ b/lib/common/common.dart @@ -3,7 +3,9 @@ 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'; @@ -12,28 +14,27 @@ export 'iterable.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'; \ No newline at end of file diff --git a/lib/common/constant.dart b/lib/common/constant.dart index 908cb8a..3a29a22 100644 --- a/lib/common/constant.dart +++ b/lib/common/constant.dart @@ -23,10 +23,12 @@ final baseInfoEdgeInsets = EdgeInsets.symmetric( horizontal: 16.ap, ); -final defaultTextScaleFactor = WidgetsBinding.instance.platformDispatcher.textScaleFactor; +final defaultTextScaleFactor = + WidgetsBinding.instance.platformDispatcher.textScaleFactor; 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"; @@ -76,6 +78,10 @@ const viewModeColumnsMap = { ViewMode.desktop: [4, 3], }; +// const proxiesStoreKey = PageStorageKey('proxies'); +// const toolsStoreKey = PageStorageKey('tools'); +// const profilesStoreKey = PageStorageKey('profiles'); + const defaultPrimaryColor = 0XFFD8C0C3; double getWidgetHeight(num lines) { @@ -97,3 +103,8 @@ const defaultPrimaryColors = [ defaultPrimaryColor, 0XFF665390, ]; + +const scriptTemplate = """ +const main = (config) => { + return config; +}"""; diff --git a/lib/common/converter.dart b/lib/common/converter.dart new file mode 100644 index 0000000..f818aaa --- /dev/null +++ b/lib/common/converter.dart @@ -0,0 +1,32 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +class Uint8ListToListIntConverter extends Converter> { + @override + List convert(Uint8List input) { + return input.toList(); + } + + @override + Sink startChunkedConversion(Sink> sink) { + return _Uint8ListToListIntConverterSink(sink); + } +} + +class _Uint8ListToListIntConverterSink implements Sink { + const _Uint8ListToListIntConverterSink(this._target); + + final Sink> _target; + + @override + void add(Uint8List data) { + _target.add(data.toList()); + } + + @override + void close() { + _target.close(); + } +} + +final uint8ListToListIntConverter = Uint8ListToListIntConverter(); diff --git a/lib/common/function.dart b/lib/common/function.dart index a1e1770..7003c5f 100644 --- a/lib/common/function.dart +++ b/lib/common/function.dart @@ -1,10 +1,12 @@ import 'dart:async'; +import 'package:fl_clash/enum/enum.dart'; + class Debouncer { - final Map _operations = {}; + final Map _operations = {}; call( - dynamic tag, + FunctionTag tag, Function func, { List? args, Duration duration = const Duration(milliseconds: 600), @@ -33,10 +35,10 @@ class Debouncer { } class Throttler { - final Map _operations = {}; + final Map _operations = {}; call( - dynamic tag, + FunctionTag tag, Function func, { List? args, Duration duration = const Duration(milliseconds: 600), diff --git a/lib/common/http.dart b/lib/common/http.dart index 9bd2faa..0fe22a3 100644 --- a/lib/common/http.dart +++ b/lib/common/http.dart @@ -18,6 +18,7 @@ class FlClashHttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext? context) { final client = super.createHttpClient(context); + client.badCertificateCallback = (_, __, ___) => true; client.findProxy = handleFindProxy; return client; } diff --git a/lib/common/navigation.dart b/lib/common/navigation.dart index 5d2ccab..a82bdae 100644 --- a/lib/common/navigation.dart +++ b/lib/common/navigation.dart @@ -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 { @@ -12,16 +12,17 @@ class Navigation { }) { return [ const NavigationItem( + keep: false, icon: Icon(Icons.space_dashboard), label: PageLabel.dashboard, - fragment: DashboardFragment( + view: DashboardView( key: GlobalObjectKey(PageLabel.dashboard), ), ), NavigationItem( icon: const Icon(Icons.article), label: PageLabel.proxies, - fragment: const ProxiesFragment( + view: const ProxiesView( key: GlobalObjectKey( PageLabel.proxies, ), @@ -33,7 +34,7 @@ class Navigation { const NavigationItem( icon: Icon(Icons.folder), label: PageLabel.profiles, - fragment: ProfilesFragment( + view: ProfilesView( key: GlobalObjectKey( PageLabel.profiles, ), @@ -42,7 +43,7 @@ class Navigation { const NavigationItem( icon: Icon(Icons.view_timeline), label: PageLabel.requests, - fragment: RequestsFragment( + view: RequestsView( key: GlobalObjectKey( PageLabel.requests, ), @@ -53,7 +54,7 @@ class Navigation { const NavigationItem( icon: Icon(Icons.ballot), label: PageLabel.connections, - fragment: ConnectionsFragment( + view: ConnectionsView( key: GlobalObjectKey( PageLabel.connections, ), @@ -65,8 +66,7 @@ class Navigation { icon: Icon(Icons.storage), label: PageLabel.resources, description: "resourcesDesc", - keep: false, - fragment: Resources( + view: ResourcesView( key: GlobalObjectKey( PageLabel.resources, ), @@ -76,7 +76,7 @@ class Navigation { NavigationItem( icon: const Icon(Icons.adb), label: PageLabel.logs, - fragment: const LogsFragment( + view: const LogsView( key: GlobalObjectKey( PageLabel.logs, ), @@ -89,7 +89,7 @@ class Navigation { const NavigationItem( icon: Icon(Icons.construction), label: PageLabel.tools, - fragment: ToolsFragment( + view: ToolsView( key: GlobalObjectKey( PageLabel.tools, ), diff --git a/lib/common/navigator.dart b/lib/common/navigator.dart index 60e13e5..b2b3d56 100644 --- a/lib/common/navigator.dart +++ b/lib/common/navigator.dart @@ -2,6 +2,7 @@ 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 { @@ -19,6 +20,21 @@ class BaseNavigator { ), ); } + + static Future modal(BuildContext context, Widget child) async { + if (globalState.appState.viewMode != ViewMode.mobile) { + return await globalState.showCommonDialog( + child: CommonModal( + child: child, + ), + ); + } + return await Navigator.of(context).push( + CommonRoute( + builder: (context) => child, + ), + ); + } } class CommonDesktopRoute extends PageRoute { @@ -218,8 +234,7 @@ class _CommonPageTransitionState extends State { begin: const _CommonEdgeShadowDecoration(), end: _CommonEdgeShadowDecoration( [ - widget.context.colorScheme.inverseSurface - .withValues(alpha: 0.02), + widget.context.colorScheme.inverseSurface.withValues(alpha: 0.02), Colors.transparent, ], ), diff --git a/lib/common/path.dart b/lib/common/path.dart index 7709331..21be3b3 100644 --- a/lib/common/path.dart +++ b/lib/common/path.dart @@ -1,11 +1,10 @@ import 'dart:async'; import 'dart:io'; +import 'package:fl_clash/common/common.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; -import 'constant.dart'; - class AppPath { static AppPath? _instance; Completer dataDir = Completer(); @@ -73,16 +72,33 @@ class AppPath { return join(directory.path, profilesDirectoryName); } - Future getProfilePath(String? id) async { - if (id == null) return null; + Future getProfilePath(String id) async { final directory = await profilesPath; return join(directory, "$id.yaml"); } - Future getProvidersPath(String? id) async { - if (id == null) return null; + Future getProvidersDirPath(String id) async { final directory = await profilesPath; - return join(directory, "providers", id); + return join( + directory, + "providers", + id, + ); + } + + Future getProvidersFilePath( + String id, + String type, + String url, + ) async { + final directory = await profilesPath; + return join( + directory, + "providers", + id, + type, + url.toMd5(), + ); } Future get tempPath async { diff --git a/lib/common/print.dart b/lib/common/print.dart index fb9786b..4e63a2d 100644 --- a/lib/common/print.dart +++ b/lib/common/print.dart @@ -15,7 +15,7 @@ class CommonPrint { log(String? text) { final payload = "[FlClash] $text"; debugPrint(payload); - if (globalState.isService) { + if (!globalState.isInit) { return; } globalState.appController.addLog( diff --git a/lib/common/render.dart b/lib/common/render.dart index 8d9cd42..6c9192c 100644 --- a/lib/common/render.dart +++ b/lib/common/render.dart @@ -23,14 +23,14 @@ class Render { pause() { throttler.call( - DebounceTag.renderPause, + FunctionTag.renderPause, _pause, duration: Duration(seconds: 5), ); } resume() { - throttler.cancel(DebounceTag.renderPause); + throttler.cancel(FunctionTag.renderPause); _resume(); } diff --git a/lib/common/request.dart b/lib/common/request.dart index c06135c..d277ee5 100644 --- a/lib/common/request.dart +++ b/lib/common/request.dart @@ -43,6 +43,16 @@ class Request { return response; } + Future getTextResponseForUrl(String url) async { + final response = await _clashDio.get( + url, + options: Options( + responseType: ResponseType.plain, + ), + ); + return response; + } + Future getImage(String url) async { if (url.isEmpty) return null; final response = await _dio.get( diff --git a/lib/common/string.dart b/lib/common/string.dart index b4ccaeb..4c30097 100644 --- a/lib/common/string.dart +++ b/lib/common/string.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:crypto/crypto.dart'; + import 'print.dart'; extension StringExtension on String { @@ -8,6 +10,13 @@ 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(), @@ -38,6 +47,10 @@ extension StringExtension on String { } } + bool get isSvg { + return endsWith(".svg"); + } + bool get isRegex { try { RegExp(this); @@ -47,6 +60,15 @@ extension StringExtension on String { return false; } } + + String toMd5() { + final bytes = utf8.encode(this); + return md5.convert(bytes).toString(); + } + +// bool containsToLower(String target) { +// return toLowerCase().contains(target); +// } } extension StringExtensionSafe on String? { diff --git a/lib/common/system.dart b/lib/common/system.dart index 20f26da..5dbfc49 100644 --- a/lib/common/system.dart +++ b/lib/common/system.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; class System { static System? _instance; + List? originDns; System._internal(); @@ -56,7 +57,7 @@ class System { Future authorizeCore() async { if (Platform.isAndroid) { - return AuthorizeCode.none; + return AuthorizeCode.error; } final corePath = appPath.corePath.replaceAll(' ', '\\\\ '); final isAdmin = await checkIsAdmin(); @@ -104,6 +105,100 @@ class System { return AuthorizeCode.error; } + Future getMacOSDefaultServiceName() async { + if (!Platform.isMacOS) { + return null; + } + final result = await Process.run('route', ['-n', 'get', 'default']); + final output = result.stdout.toString(); + final deviceLine = output + .split('\n') + .firstWhere((s) => s.contains('interface:'), orElse: () => ""); + final lineSplits = deviceLine.trim().split(' '); + if (lineSplits.length != 2) { + return null; + } + final device = lineSplits[1]; + final serviceResult = await Process.run( + 'networksetup', + ['-listnetworkserviceorder'], + ); + final serviceResultOutput = serviceResult.stdout.toString(); + final currentService = serviceResultOutput.split('\n\n').firstWhere( + (s) => s.contains("Device: $device"), + orElse: () => "", + ); + if (currentService.isEmpty) { + return null; + } + final currentServiceNameLine = currentService.split("\n").firstWhere( + (line) => RegExp(r'^\(\d+\).*').hasMatch(line), + orElse: () => ""); + final currentServiceNameLineSplits = + currentServiceNameLine.trim().split(' '); + if (currentServiceNameLineSplits.length < 2) { + return null; + } + return currentServiceNameLineSplits[1]; + } + + Future?> getMacOSOriginDns() async { + if (!Platform.isMacOS) { + return null; + } + final deviceServiceName = await getMacOSDefaultServiceName(); + if (deviceServiceName == null) { + return null; + } + final result = await Process.run( + 'networksetup', + ['-getdnsservers', deviceServiceName], + ); + final output = result.stdout.toString().trim(); + if (output.startsWith("There aren't any DNS Servers set on")) { + originDns = []; + } else { + originDns = output.split("\n"); + } + return originDns; + } + + setMacOSDns(bool restore) async { + if (!Platform.isMacOS) { + return; + } + final serviceName = await getMacOSDefaultServiceName(); + if (serviceName == null) { + return; + } + List? nextDns; + if (restore) { + nextDns = originDns; + } else { + final originDns = await system.getMacOSOriginDns(); + if (originDns == null) { + return; + } + final needAddDns = "223.5.5.5"; + if (originDns.contains(needAddDns)) { + return; + } + nextDns = List.from(originDns)..add(needAddDns); + } + if (nextDns == null) { + return; + } + await Process.run( + 'networksetup', + [ + '-setdnsservers', + serviceName, + if (nextDns.isNotEmpty) ...nextDns, + if (nextDns.isEmpty) "Empty", + ], + ); + } + back() async { await app?.moveTaskToBack(); await window?.hide(); diff --git a/lib/common/utils.dart b/lib/common/utils.dart index 49a869f..c26bc72 100644 --- a/lib/common/utils.dart +++ b/lib/common/utils.dart @@ -1,10 +1,13 @@ +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'; class Utils { @@ -230,7 +233,7 @@ class Utils { } int getProfilesColumns(double viewWidth) { - return max((viewWidth / 350).floor(), 1); + return max((viewWidth / 320).floor(), 1); } final _indexPrimary = [ @@ -323,6 +326,72 @@ class Utils { } return ""; } + + SingleActivator controlSingleActivator(LogicalKeyboardKey trigger) { + final control = Platform.isMacOS ? false : true; + return SingleActivator( + trigger, + control: control, + meta: !control, + ); + } + + // dynamic convertYamlNode(dynamic node) { + // if (node is YamlMap) { + // final map = {}; + // 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); + // } else if (mergeValue is YamlList) { + // for (final node in mergeValue.nodes) { + // if (node.value is YamlMap) { + // map.addAll(convertYamlNode(node.value) as Map); + // } + // } + // } + // } + // + // 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 = []; + // for (final item in node.nodes) { + // list.add(convertYamlNode(item.value)); + // } + // return list; + // } else if (node is YamlScalar) { + // return node.value; + // } + // return node; + // } + + FutureOr handleWatch(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(); diff --git a/lib/controller.dart b/lib/controller.dart index 5f60b0a..c59a0aa 100644 --- a/lib/controller.dart +++ b/lib/controller.dart @@ -8,6 +8,7 @@ import 'package:archive/archive.dart'; import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/common/archive.dart'; import 'package:fl_clash/enum/enum.dart'; +import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/providers/providers.dart'; import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/dialog.dart'; @@ -17,11 +18,10 @@ 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; int? lastProfileModified; final BuildContext context; @@ -29,19 +29,24 @@ class AppController { AppController(this.context, WidgetRef ref) : _ref = ref; + setupClashConfigDebounce() { + debouncer.call(FunctionTag.setupClashConfig, () async { + await setupClashConfig(); + }); + } + updateClashConfigDebounce() { - debouncer.call(DebounceTag.updateClashConfig, () async { - final isPatch = globalState.appState.needApply ? false : true; - await updateClashConfig(isPatch); + debouncer.call(FunctionTag.updateClashConfig, () async { + await updateClashConfig(); }); } updateGroupsDebounce() { - debouncer.call(DebounceTag.updateGroups, updateGroups); + debouncer.call(FunctionTag.updateGroups, updateGroups); } addCheckIpNumDebounce() { - debouncer.call(DebounceTag.addCheckIpNum, () { + debouncer.call(FunctionTag.addCheckIpNum, () { _ref.read(checkIpNumProvider.notifier).add(); }); } @@ -49,17 +54,17 @@ class AppController { applyProfileDebounce({ bool silence = false, }) { - debouncer.call(DebounceTag.applyProfile, (silence) { + debouncer.call(FunctionTag.applyProfile, (silence) { applyProfile(silence: silence); }, args: [silence]); } savePreferencesDebounce() { - debouncer.call(DebounceTag.savePreferences, savePreferences); + debouncer.call(FunctionTag.savePreferences, savePreferences); } changeProxyDebounce(String groupName, String proxyName) { - debouncer.call(DebounceTag.changeProxy, + debouncer.call(FunctionTag.changeProxy, (String groupName, String proxyName) async { await changeProxy( groupName: groupName, @@ -70,9 +75,9 @@ class AppController { } restartCore() async { + commonPrint.log("restart core"); await clashService?.reStart(); await _initCore(); - if (_ref.read(runTimeProvider.notifier).isStart) { await globalState.handleStart(); } @@ -243,55 +248,82 @@ class AppController { ); } - Future updateClashConfig([bool? isPatch]) async { - commonPrint.log("update clash patch: ${isPatch ?? false}"); + Future updateClashConfig() async { final commonScaffoldState = globalState.homeScaffoldKey.currentState; if (commonScaffoldState?.mounted != true) return; await commonScaffoldState?.loadingRun(() async { - await _updateClashConfig( - isPatch, - ); + await _updateClashConfig(); }); } - Future _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) { + Future _updateClashConfig() async { + final updateParams = _ref.read(updateParamsProvider); + final res = await _requestAdmin(updateParams.tun.enable); + if (res.isError) { + return; + } + final realTunEnable = _ref.read(realTunEnableProvider); + final message = await clashCore.updateConfig( + updateParams.copyWith.tun( + enable: realTunEnable, + ), + ); + if (message.isNotEmpty) throw message; + } + + Future> _requestAdmin(bool enableTun) async { + final realTunEnable = _ref.read(realTunEnableProvider); + if (enableTun != realTunEnable && realTunEnable == false) { final code = await system.authorizeCore(); switch (code) { + case AuthorizeCode.success: + await restartCore(); + return Result.error(""); case AuthorizeCode.none: break; - case AuthorizeCode.success: - lastTunEnable = enableTun; - await restartCore(); - return; case AuthorizeCode.error: enableTun = false; + break; } } - if (appSetting.openLogs) { - clashCore.startLog(); - } else { - clashCore.stopLog(); + _ref.read(realTunEnableProvider.notifier).value = enableTun; + return Result.success(enableTun); + } + + Future 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; } - final res = await clashCore.updateConfig( - globalState.getUpdateConfigParams(isPatch), + final realTunEnable = _ref.read(realTunEnableProvider); + final realPatchConfig = patchConfig.copyWith.tun(enable: realTunEnable); + final params = await globalState.getSetupParams( + pathConfig: realPatchConfig, ); - if (isPatch == false) { - _ref.read(needApplyProvider.notifier).value = false; + final message = await clashCore.setupConfig(params); + lastProfileModified = await _ref.read( + currentProfileProvider.select( + (state) => state?.profileLastModified, + ), + ); + if (message.isNotEmpty) { + throw message; } - if (res.isNotEmpty) throw res; - lastTunEnable = enableTun; - lastProfileModified = await profile?.profileLastModified; } Future _applyProfile() async { await clashCore.requestGc(); - await updateClashConfig(); + await setupClashConfig(); await updateGroups(); await updateProviders(); } @@ -385,6 +417,9 @@ class AppController { } handleBackOrExit() async { + if (_ref.read(backBlockProvider)) { + return; + } if (_ref.read(appSettingProvider).minimizeOnExit) { if (system.isDesktop) { await savePreferencesDebounce(); @@ -395,13 +430,24 @@ class AppController { } } + backBlock() { + _ref.read(backBlockProvider.notifier).value = true; + } + + unBackBlock() { + _ref.read(backBlockProvider.notifier).value = false; + } + handleExit() async { + Future.delayed(commonDuration, () { + system.exit(); + }); try { - await updateStatus(false); + await savePreferences(); + await system.setMacOSDns(true); await proxy?.stopProxy(); await clashCore.shutdown(); await clashService?.destroy(); - await savePreferences(); } finally { system.exit(); } @@ -498,11 +544,12 @@ class AppController { } init() async { - await _handlePreference(); - await _handlerDisclaimer(); + FlutterError.onError = (details) { + commonPrint.log(details.stack.toString()); + }; + updateTray(true); await _initCore(); await _initStatus(); - updateTray(true); autoLaunch?.updateStatus( _ref.read(appSettingProvider).autoLaunch, ); @@ -513,6 +560,8 @@ class AppController { } else { window?.hide(); } + await _handlePreference(); + await _handlerDisclaimer(); _ref.read(initProvider.notifier).value = true; } @@ -690,10 +739,16 @@ class AppController { return List.of(proxies) ..sort( (a, b) { - final aDelay = - _ref.read(getDelayProvider(proxyName: a.name, testUrl: testUrl)); - final bDelay = - _ref.read(getDelayProvider(proxyName: b.name, testUrl: testUrl)); + final aDelay = _ref.read(getDelayProvider( + proxyName: a.name, + testUrl: testUrl, + )); + final bDelay = _ref.read( + getDelayProvider( + proxyName: b.name, + testUrl: testUrl, + ), + ); if (aDelay == null && bDelay == null) { return 0; } @@ -721,21 +776,17 @@ class AppController { clearEffect(String profileId) async { final profilePath = await appPath.getProfilePath(profileId); - final providersPath = await appPath.getProvidersPath(profileId); + final providersDirPath = await appPath.getProvidersDirPath(profileId); return await Isolate.run(() async { - if (profilePath != null) { - final profileFile = File(profilePath); - final isExists = await profileFile.exists(); - if (isExists) { - profileFile.delete(recursive: true); - } + final profileFile = File(profilePath); + final isExists = await profileFile.exists(); + if (isExists) { + profileFile.delete(recursive: true); } - if (providersPath != null) { - final providersFileDir = File(providersPath); - final isExists = await providersFileDir.exists(); - if (isExists) { - providersFileDir.delete(recursive: true); - } + final providersFileDir = File(providersDirPath); + final providersFileIsExists = await providersFileDir.exists(); + if (providersFileIsExists) { + providersFileDir.delete(recursive: true); } }); } @@ -754,6 +805,17 @@ class AppController { ); } + Future> getPackages() async { + if (_ref.read(isMobileViewProvider)) { + await Future.delayed(commonDuration); + } + if (_ref.read(packagesProvider).isEmpty) { + _ref.read(packagesProvider.notifier).value = + await app?.getPackages() ?? []; + } + return _ref.read(packagesProvider); + } + updateStart() { updateStatus(!_ref.read(runTimeProvider.notifier).isStart); } @@ -971,6 +1033,7 @@ class AppController { _ref.read(overrideDnsProvider.notifier).value = config.overrideDns; _ref.read(networkSettingProvider.notifier).value = config.networkProps; _ref.read(hotKeyActionsProvider.notifier).value = config.hotKeyActions; + _ref.read(scriptStateProvider.notifier).value = config.scriptProps; } final currentProfile = _ref.read(currentProfileProvider); if (currentProfile == null) { diff --git a/lib/enum/enum.dart b/lib/enum/enum.dart index d6e33b6..f2751f0 100644 --- a/lib/enum/enum.dart +++ b/lib/enum/enum.dart @@ -2,7 +2,7 @@ import 'dart:io'; -import 'package:fl_clash/fragments/dashboard/widgets/widgets.dart'; +import 'package:fl_clash/views/dashboard/widgets/widgets.dart'; import 'package:fl_clash/widgets/widgets.dart'; import 'package:flutter/services.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -118,7 +118,12 @@ enum AccessSortType { none, name, time } enum ProfileType { file, url } -enum ResultType { success, error } +enum ResultType { + @JsonValue(0) + success, + @JsonValue(-1) + error, +} enum AppMessageType { log, @@ -164,9 +169,13 @@ enum DnsMode { enum ExternalControllerStatus { @JsonValue("") - close, + close(""), @JsonValue("127.0.0.1:9090") - open + open("127.0.0.1:9090"); + + final String value; + + const ExternalControllerStatus(this.value); } enum KeyboardModifier { @@ -248,6 +257,7 @@ enum ActionMethod { shutdown, validateConfig, updateConfig, + getConfig, getProxies, changeProxy, getTraffic, @@ -256,6 +266,7 @@ enum ActionMethod { asyncTestDelay, getConnections, closeConnections, + resetConnections, closeConnection, getExternalProviders, getExternalProvider, @@ -268,12 +279,10 @@ enum ActionMethod { stopListener, getCountryCode, getMemory, - getProfile, crash, + setupConfig, ///Android, - setFdMap, - setProcessMap, setState, startTun, stopTun, @@ -291,8 +300,9 @@ enum WindowsHelperServiceStatus { running, } -enum DebounceTag { +enum FunctionTag { updateClashConfig, + setupClashConfig, updateStatus, updateGroups, addCheckIpNum, @@ -308,6 +318,8 @@ enum DebounceTag { updatePageIndex, pageChange, proxiesTabChange, + logs, + requests, } enum DashboardWidget { @@ -482,3 +494,13 @@ enum CacheTag { rules, requests, } + +enum Language { + yaml, + javaScript, +} + +enum ImportOption { + file, + url, +} diff --git a/lib/fragments/config/general.dart b/lib/fragments/config/general.dart deleted file mode 100644 index 71c96c4..0000000 --- a/lib/fragments/config/general.dart +++ /dev/null @@ -1,430 +0,0 @@ -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.options( - leading: const Icon(Icons.info_outline), - title: Text(appLocalizations.logLevel), - subtitle: Text(logLevel.name), - delegate: OptionsDelegate( - 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.options( - leading: const Icon(Icons.computer_outlined), - title: const Text("UA"), - subtitle: Text(globalUa ?? appLocalizations.defaultText), - delegate: OptionsDelegate( - 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 = [ - LogLevelItem(), - UaItem(), - if (system.isDesktop) KeepAliveIntervalItem(), - TestUrlItem(), - MixedPortItem(), - HostsItem(), - Ipv6Item(), - AllowLanItem(), - UnifiedDelayItem(), - FindProcessItem(), - TcpConcurrentItem(), - GeodataLoaderItem(), - ExternalControllerItem(), -] - .separated( - const Divider( - height: 0, - ), - ) - .toList(); diff --git a/lib/fragments/connection/item.dart b/lib/fragments/connection/item.dart deleted file mode 100644 index 9ce9113..0000000 --- a/lib/fragments/connection/item.dart +++ /dev/null @@ -1,210 +0,0 @@ -import 'dart:io'; - -import 'package:fl_clash/common/common.dart'; -import 'package:fl_clash/enum/enum.dart'; -import 'package:fl_clash/models/models.dart'; -import 'package:fl_clash/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 _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( - 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( - // 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: () {}, - ), - ], - ), - ); - } -} diff --git a/lib/fragments/dashboard/dashboard.dart b/lib/fragments/dashboard/dashboard.dart deleted file mode 100644 index 862e1ff..0000000 --- a/lib/fragments/dashboard/dashboard.dart +++ /dev/null @@ -1,138 +0,0 @@ -import 'dart:math'; - -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/widgets/widgets.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'widgets/start_button.dart'; - -class DashboardFragment extends ConsumerStatefulWidget { - const DashboardFragment({super.key}); - - @override - ConsumerState createState() => _DashboardFragmentState(); -} - -class _DashboardFragmentState extends ConsumerState - with PageMixin { - final key = GlobalKey(); - - @override - initState() { - ref.listenManual( - isCurrentPageProvider(PageLabel.dashboard), - (prev, next) { - if (prev != next && next == true) { - initPageState(); - } - }, - fireImmediately: true, - ); - return super.initState(); - } - - @override - Widget? get floatingActionButton => const StartButton(); - - @override - List get actions => [ - ValueListenableBuilder( - valueListenable: key.currentState!.addedChildrenNotifier, - builder: (_, addedChildren, child) { - return ValueListenableBuilder( - valueListenable: key.currentState!.isEditNotifier, - builder: (_, isEdit, child) { - if (!isEdit || addedChildren.isEmpty) { - return Container(); - } - return child!; - }, - child: child, - ); - }, - child: IconButton( - onPressed: () { - key.currentState!.showAddModal(); - }, - icon: Icon( - Icons.add_circle, - ), - ), - ), - IconButton( - icon: ValueListenableBuilder( - valueListenable: key.currentState!.isEditNotifier, - builder: (_, isEdit, ___) { - return isEdit - ? Icon(Icons.save) - : Icon( - Icons.edit, - ); - }, - ), - onPressed: () { - key.currentState!.isEditNotifier.value = - !key.currentState!.isEditNotifier.value; - }, - ), - ]; - - _handleSave(List girdItems, WidgetRef ref) { - final dashboardWidgets = girdItems - .map( - (item) => DashboardWidget.getDashboardWidget(item), - ) - .toList(); - ref.read(appSettingProvider.notifier).updateState( - (state) => state.copyWith(dashboardWidgets: dashboardWidgets), - ); - } - - @override - Widget build(BuildContext context) { - final dashboardState = ref.watch(dashboardStateProvider); - final columns = max(4 * ((dashboardState.viewWidth / 320).ceil()), 8); - return Align( - alignment: Alignment.topCenter, - child: SingleChildScrollView( - padding: const EdgeInsets.all(16).copyWith( - bottom: 88, - ), - child: SuperGrid( - key: key, - crossAxisCount: columns, - crossAxisSpacing: 16.ap, - mainAxisSpacing: 16.ap, - children: [ - ...dashboardState.dashboardWidgets - .where( - (item) => item.platforms.contains( - SupportPlatform.currentPlatform, - ), - ) - .map( - (item) => item.widget, - ), - ], - onSave: (girdItems) { - _handleSave(girdItems, ref); - }, - addedItemsBuilder: (girdItems) { - return DashboardWidget.values - .where( - (item) => - !girdItems.contains(item.widget) && - item.platforms.contains( - SupportPlatform.currentPlatform, - ), - ) - .map((item) => item.widget) - .toList(); - }, - ), - ), - ); - } -} diff --git a/lib/fragments/dashboard/widgets/start_button.dart b/lib/fragments/dashboard/widgets/start_button.dart deleted file mode 100644 index 66c1491..0000000 --- a/lib/fragments/dashboard/widgets/start_button.dart +++ /dev/null @@ -1,143 +0,0 @@ -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 createState() => _StartButtonState(); -} - -class _StartButtonState extends State - 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( - DebounceTag.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), - ); - }, - ), - ); - } -} diff --git a/lib/fragments/proxies/proxies.dart b/lib/fragments/proxies/proxies.dart deleted file mode 100644 index 0848ffb..0000000 --- a/lib/fragments/proxies/proxies.dart +++ /dev/null @@ -1,179 +0,0 @@ -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/providers/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 ProxiesFragment extends ConsumerStatefulWidget { - const ProxiesFragment({super.key}); - - @override - ConsumerState createState() => _ProxiesFragmentState(); -} - -class _ProxiesFragmentState extends ConsumerState - with PageMixin { - final GlobalKey _proxiesTabKey = GlobalKey(); - bool _hasProviders = false; - bool _isTab = false; - - @override - get actions => [ - if (_hasProviders) - IconButton( - onPressed: () { - showExtend( - context, - builder: (_, type) { - return ProvidersView( - type: type, - ); - }, - ); - }, - icon: const Icon( - Icons.poll_outlined, - ), - ), - _isTab - ? IconButton( - onPressed: () { - _proxiesTabKey.currentState?.scrollToGroupSelected(); - }, - icon: const Icon( - Icons.adjust_outlined, - ), - ) - : IconButton( - onPressed: () { - showExtend( - context, - builder: (_, type) { - return AdaptiveSheetScaffold( - type: type, - body: const _IconConfigView(), - title: appLocalizations.iconConfiguration, - ); - }, - ); - }, - icon: const Icon( - Icons.style_outlined, - ), - ), - IconButton( - onPressed: () { - showSheet( - context: context, - props: SheetProps( - isScrollControlled: true, - ), - builder: (_, type) { - return AdaptiveSheetScaffold( - type: type, - body: const ProxiesSetting(), - title: appLocalizations.settings, - ); - }, - ); - }, - icon: const Icon( - Icons.tune, - ), - ) - ]; - - @override - get floatingActionButton => _isTab - ? DelayTestButton( - onClick: () async { - await delayTest( - currentTabProxies, - currentTabTestUrl, - ); - }, - ) - : null; - - @override - void initState() { - ref.listenManual( - proxiesActionsStateProvider, - fireImmediately: true, - (prev, next) { - if (prev == next) { - return; - } - if (next.pageLabel == PageLabel.proxies) { - _hasProviders = next.hasProviders; - _isTab = next.type == ProxiesType.tab; - initPageState(); - return; - } - }, - ); - super.initState(); - } - - @override - Widget build(BuildContext context) { - final proxiesType = ref.watch( - proxiesStyleSettingProvider.select( - (state) => state.type, - ), - ); - - ref.watch(themeSettingProvider.select((state) => state.textScale)); - return switch (proxiesType) { - ProxiesType.tab => ProxiesTabFragment( - key: _proxiesTabKey, - ), - ProxiesType.list => const ProxiesListFragment(), - }; - } -} - -class _IconConfigView extends ConsumerWidget { - const _IconConfigView(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final iconMap = - ref.watch(proxiesStyleSettingProvider.select((state) => state.iconMap)); - return MapInputPage( - title: appLocalizations.iconConfiguration, - map: iconMap, - keyLabel: appLocalizations.regExp, - valueLabel: appLocalizations.icon, - titleBuilder: (item) => Text(item.key), - leadingBuilder: (item) => Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - ), - clipBehavior: Clip.antiAlias, - child: CommonTargetIcon( - src: item.value, - size: 42, - ), - ), - subtitleBuilder: (item) => Text( - item.value, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - onChange: (value) { - ref.read(proxiesStyleSettingProvider.notifier).updateState( - (state) => state.copyWith( - iconMap: value, - ), - ); - }, - ); - } -} diff --git a/lib/l10n/intl/messages_en.dart b/lib/l10n/intl/messages_en.dart index 72b6634..24d48a9 100644 --- a/lib/l10n/intl/messages_en.dart +++ b/lib/l10n/intl/messages_en.dart @@ -20,7 +20,25 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'en'; - static String m0(count) => "${count} items have been selected"; + 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"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -36,9 +54,6 @@ 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"), @@ -108,6 +123,9 @@ class MessageLookup extends MessageLookupByLibrary { "autoRunDesc": MessageLookupByLibrary.simpleMessage( "Auto run when the application is opened", ), + "autoSetSystemDns": MessageLookupByLibrary.simpleMessage( + "Auto set system DNS", + ), "autoUpdate": MessageLookupByLibrary.simpleMessage("Auto update"), "autoUpdateInterval": MessageLookupByLibrary.simpleMessage( "Auto update interval (minutes)", @@ -149,9 +167,7 @@ class MessageLookup extends MessageLookupByLibrary { "clearData": MessageLookupByLibrary.simpleMessage("Clear Data"), "clipboardExport": MessageLookupByLibrary.simpleMessage("Export clipboard"), "clipboardImport": MessageLookupByLibrary.simpleMessage("Clipboard import"), - "colorExists": MessageLookupByLibrary.simpleMessage( - "Current color already exists", - ), + "color": MessageLookupByLibrary.simpleMessage("Color"), "colorSchemes": MessageLookupByLibrary.simpleMessage("Color schemes"), "columns": MessageLookupByLibrary.simpleMessage("Columns"), "compatible": MessageLookupByLibrary.simpleMessage("Compatibility mode"), @@ -166,9 +182,6 @@ 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( @@ -196,15 +209,8 @@ class MessageLookup extends MessageLookupByLibrary { "delay": MessageLookupByLibrary.simpleMessage("Delay"), "delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"), "delete": MessageLookupByLibrary.simpleMessage("Delete"), - "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?", - ), + "deleteMultipTip": m0, + "deleteTip": m1, "desc": MessageLookupByLibrary.simpleMessage( "A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.", ), @@ -236,6 +242,7 @@ 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"), @@ -243,6 +250,7 @@ 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"), @@ -318,7 +326,10 @@ class MessageLookup extends MessageLookupByLibrary { "Icon configuration", ), "iconStyle": MessageLookupByLibrary.simpleMessage("Icon style"), + "import": MessageLookupByLibrary.simpleMessage("Import"), + "importFile": MessageLookupByLibrary.simpleMessage("Import from file"), "importFromURL": MessageLookupByLibrary.simpleMessage("Import from URL"), + "importUrl": MessageLookupByLibrary.simpleMessage("Import from URL"), "infiniteTime": MessageLookupByLibrary.simpleMessage("Long term effective"), "init": MessageLookupByLibrary.simpleMessage("Init"), "inputCorrectHotkey": MessageLookupByLibrary.simpleMessage( @@ -328,6 +339,7 @@ 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( @@ -342,9 +354,6 @@ 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"), @@ -381,6 +390,7 @@ 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"), @@ -419,22 +429,14 @@ 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", ), - "nullProxies": MessageLookupByLibrary.simpleMessage("No proxies"), - "nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"), + "nullTip": m4, + "numberTip": m5, "oneColumn": MessageLookupByLibrary.simpleMessage("One column"), "onlyIcon": MessageLookupByLibrary.simpleMessage("Icon"), "onlyOtherApps": MessageLookupByLibrary.simpleMessage( @@ -460,18 +462,21 @@ 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", ), @@ -482,6 +487,10 @@ 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", ), @@ -550,6 +559,7 @@ 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"), @@ -560,6 +570,7 @@ 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", @@ -586,14 +597,8 @@ 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?", @@ -601,11 +606,12 @@ 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": m0, + "selectedCountTitle": m7, "settings": MessageLookupByLibrary.simpleMessage("Settings"), "show": MessageLookupByLibrary.simpleMessage("Show"), "shrink": MessageLookupByLibrary.simpleMessage("Shrink"), @@ -614,6 +620,7 @@ 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"), @@ -629,9 +636,6 @@ 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"), @@ -665,6 +669,7 @@ 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( @@ -680,18 +685,17 @@ 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( diff --git a/lib/l10n/intl/messages_ja.dart b/lib/l10n/intl/messages_ja.dart index d7bd7dc..14e7535 100644 --- a/lib/l10n/intl/messages_ja.dart +++ b/lib/l10n/intl/messages_ja.dart @@ -20,7 +20,23 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'ja'; - static String m0(count) => "${count} 項目が選択されています"; + 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である必要があります"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -36,7 +52,6 @@ class MessageLookup extends MessageLookupByLibrary { "選択したアプリをVPNから除外", ), "account": MessageLookupByLibrary.simpleMessage("アカウント"), - "accountTip": MessageLookupByLibrary.simpleMessage("アカウントは必須です"), "action": MessageLookupByLibrary.simpleMessage("アクション"), "action_mode": MessageLookupByLibrary.simpleMessage("モード切替"), "action_proxy": MessageLookupByLibrary.simpleMessage("システムプロキシ"), @@ -78,6 +93,7 @@ class MessageLookup extends MessageLookupByLibrary { "autoLaunchDesc": MessageLookupByLibrary.simpleMessage("システムの自動起動に従う"), "autoRun": MessageLookupByLibrary.simpleMessage("自動実行"), "autoRunDesc": MessageLookupByLibrary.simpleMessage("アプリ起動時に自動実行"), + "autoSetSystemDns": MessageLookupByLibrary.simpleMessage("オートセットシステムDNS"), "autoUpdate": MessageLookupByLibrary.simpleMessage("自動更新"), "autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自動更新間隔(分)"), "backup": MessageLookupByLibrary.simpleMessage("バックアップ"), @@ -107,7 +123,7 @@ class MessageLookup extends MessageLookupByLibrary { "clearData": MessageLookupByLibrary.simpleMessage("データを消去"), "clipboardExport": MessageLookupByLibrary.simpleMessage("クリップボードにエクスポート"), "clipboardImport": MessageLookupByLibrary.simpleMessage("クリップボードからインポート"), - "colorExists": MessageLookupByLibrary.simpleMessage("この色は既に存在します"), + "color": MessageLookupByLibrary.simpleMessage("カラー"), "colorSchemes": MessageLookupByLibrary.simpleMessage("カラースキーム"), "columns": MessageLookupByLibrary.simpleMessage("列"), "compatible": MessageLookupByLibrary.simpleMessage("互換モード"), @@ -120,7 +136,6 @@ 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("環境変数をコピー"), @@ -144,11 +159,8 @@ class MessageLookup extends MessageLookupByLibrary { "delay": MessageLookupByLibrary.simpleMessage("遅延"), "delaySort": MessageLookupByLibrary.simpleMessage("遅延順"), "delete": MessageLookupByLibrary.simpleMessage("削除"), - "deleteColorTip": MessageLookupByLibrary.simpleMessage("現在の色を削除しますか?"), - "deleteProfileTip": MessageLookupByLibrary.simpleMessage( - "現在のプロファイルを削除しますか?", - ), - "deleteRuleTip": MessageLookupByLibrary.simpleMessage("選択したルールを削除しますか?"), + "deleteMultipTip": m0, + "deleteTip": m1, "desc": MessageLookupByLibrary.simpleMessage( "ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。", ), @@ -170,6 +182,7 @@ class MessageLookup extends MessageLookupByLibrary { "domain": MessageLookupByLibrary.simpleMessage("ドメイン"), "download": MessageLookupByLibrary.simpleMessage("ダウンロード"), "edit": MessageLookupByLibrary.simpleMessage("編集"), + "emptyTip": m2, "en": MessageLookupByLibrary.simpleMessage("英語"), "enableOverride": MessageLookupByLibrary.simpleMessage("上書きを有効化"), "entries": MessageLookupByLibrary.simpleMessage(" エントリ"), @@ -177,6 +190,7 @@ class MessageLookup extends MessageLookupByLibrary { "excludeDesc": MessageLookupByLibrary.simpleMessage( "アプリがバックグラウンド時に最近のタスクから非表示", ), + "existsTip": m3, "exit": MessageLookupByLibrary.simpleMessage("終了"), "expand": MessageLookupByLibrary.simpleMessage("標準"), "expirationTime": MessageLookupByLibrary.simpleMessage("有効期限"), @@ -232,12 +246,16 @@ class MessageLookup extends MessageLookupByLibrary { "icon": MessageLookupByLibrary.simpleMessage("アイコン"), "iconConfiguration": MessageLookupByLibrary.simpleMessage("アイコン設定"), "iconStyle": MessageLookupByLibrary.simpleMessage("アイコンスタイル"), + "import": MessageLookupByLibrary.simpleMessage("インポート"), + "importFile": MessageLookupByLibrary.simpleMessage("ファイルからインポート"), "importFromURL": MessageLookupByLibrary.simpleMessage("URLからインポート"), + "importUrl": MessageLookupByLibrary.simpleMessage("URLからインポート"), "infiniteTime": MessageLookupByLibrary.simpleMessage("長期有効"), "init": MessageLookupByLibrary.simpleMessage("初期化"), "inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("正しいホットキーを入力"), "intelligentSelected": MessageLookupByLibrary.simpleMessage("インテリジェント選択"), "internet": MessageLookupByLibrary.simpleMessage("インターネット"), + "interval": MessageLookupByLibrary.simpleMessage("インターバル"), "intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"), "ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"), "ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"), @@ -248,7 +266,6 @@ class MessageLookup extends MessageLookupByLibrary { "TCPキープアライブ間隔", ), "key": MessageLookupByLibrary.simpleMessage("キー"), - "keyExists": MessageLookupByLibrary.simpleMessage("現在のキーは既に存在します"), "language": MessageLookupByLibrary.simpleMessage("言語"), "layout": MessageLookupByLibrary.simpleMessage("レイアウト"), "light": MessageLookupByLibrary.simpleMessage("ライト"), @@ -275,6 +292,7 @@ class MessageLookup extends MessageLookupByLibrary { "システムの終了イベントを変更", ), "minutes": MessageLookupByLibrary.simpleMessage("分"), + "mixedPort": MessageLookupByLibrary.simpleMessage("混合ポート"), "mode": MessageLookupByLibrary.simpleMessage("モード"), "monochromeScheme": MessageLookupByLibrary.simpleMessage("モノクローム"), "months": MessageLookupByLibrary.simpleMessage("月"), @@ -305,18 +323,14 @@ 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( "プロファイルがありません。追加してください", ), - "nullProxies": MessageLookupByLibrary.simpleMessage("プロキシなし"), - "nullRequestsDesc": MessageLookupByLibrary.simpleMessage("リクエストなし"), + "nullTip": m4, + "numberTip": m5, "oneColumn": MessageLookupByLibrary.simpleMessage("1列"), "onlyIcon": MessageLookupByLibrary.simpleMessage("アイコンのみ"), "onlyOtherApps": MessageLookupByLibrary.simpleMessage("サードパーティアプリのみ"), @@ -334,14 +348,19 @@ 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( "管理者パスワードを入力", ), @@ -352,6 +371,8 @@ 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("プレビュー"), @@ -402,6 +423,7 @@ class MessageLookup extends MessageLookupByLibrary { "オーバーライド", ), "recoverySuccess": MessageLookupByLibrary.simpleMessage("復元成功"), + "redirPort": MessageLookupByLibrary.simpleMessage("Redirポート"), "redo": MessageLookupByLibrary.simpleMessage("やり直す"), "regExp": MessageLookupByLibrary.simpleMessage("正規表現"), "remote": MessageLookupByLibrary.simpleMessage("リモート"), @@ -412,6 +434,7 @@ class MessageLookup extends MessageLookupByLibrary { "WebDAVからデータを復元", ), "remove": MessageLookupByLibrary.simpleMessage("削除"), + "rename": MessageLookupByLibrary.simpleMessage("リネーム"), "requests": MessageLookupByLibrary.simpleMessage("リクエスト"), "requestsDesc": MessageLookupByLibrary.simpleMessage("最近のリクエスト記録を表示"), "reset": MessageLookupByLibrary.simpleMessage("リセット"), @@ -432,26 +455,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": m0, + "selectedCountTitle": m7, "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"), @@ -465,7 +486,6 @@ 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("システム"), @@ -493,6 +513,7 @@ 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("管理者モードでのみ有効"), @@ -506,14 +527,15 @@ 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関連設定の変更"), diff --git a/lib/l10n/intl/messages_ru.dart b/lib/l10n/intl/messages_ru.dart index b28ad14..530ebcc 100644 --- a/lib/l10n/intl/messages_ru.dart +++ b/lib/l10n/intl/messages_ru.dart @@ -20,7 +20,24 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'ru'; - static String m0(count) => "Выбрано ${count} элементов"; + 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"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -36,9 +53,6 @@ class MessageLookup extends MessageLookupByLibrary { "Выбранные приложения будут исключены из VPN", ), "account": MessageLookupByLibrary.simpleMessage("Аккаунт"), - "accountTip": MessageLookupByLibrary.simpleMessage( - "Аккаунт не может быть пустым", - ), "action": MessageLookupByLibrary.simpleMessage("Действие"), "action_mode": MessageLookupByLibrary.simpleMessage("Переключить режим"), "action_proxy": MessageLookupByLibrary.simpleMessage("Системный прокси"), @@ -106,6 +120,9 @@ class MessageLookup extends MessageLookupByLibrary { "autoRunDesc": MessageLookupByLibrary.simpleMessage( "Автоматический запуск при открытии приложения", ), + "autoSetSystemDns": MessageLookupByLibrary.simpleMessage( + "Автоматическая настройка системного DNS", + ), "autoUpdate": MessageLookupByLibrary.simpleMessage("Автообновление"), "autoUpdateInterval": MessageLookupByLibrary.simpleMessage( "Интервал автообновления (минуты)", @@ -155,9 +172,7 @@ class MessageLookup extends MessageLookupByLibrary { "clipboardImport": MessageLookupByLibrary.simpleMessage( "Импорт из буфера обмена", ), - "colorExists": MessageLookupByLibrary.simpleMessage( - "Этот цвет уже существует", - ), + "color": MessageLookupByLibrary.simpleMessage("Цвет"), "colorSchemes": MessageLookupByLibrary.simpleMessage("Цветовые схемы"), "columns": MessageLookupByLibrary.simpleMessage("Столбцы"), "compatible": MessageLookupByLibrary.simpleMessage("Режим совместимости"), @@ -172,9 +187,6 @@ 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( @@ -204,15 +216,8 @@ class MessageLookup extends MessageLookupByLibrary { "delay": MessageLookupByLibrary.simpleMessage("Задержка"), "delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"), "delete": MessageLookupByLibrary.simpleMessage("Удалить"), - "deleteColorTip": MessageLookupByLibrary.simpleMessage( - "Удалить текущий цвет?", - ), - "deleteProfileTip": MessageLookupByLibrary.simpleMessage( - "Вы уверены, что хотите удалить текущий профиль?", - ), - "deleteRuleTip": MessageLookupByLibrary.simpleMessage( - "Вы уверены, что хотите удалить выбранное правило?", - ), + "deleteMultipTip": m0, + "deleteTip": m1, "desc": MessageLookupByLibrary.simpleMessage( "Многоплатформенный прокси-клиент на основе ClashMeta, простой и удобный в использовании, с открытым исходным кодом и без рекламы.", ), @@ -246,6 +251,7 @@ class MessageLookup extends MessageLookupByLibrary { "domain": MessageLookupByLibrary.simpleMessage("Домен"), "download": MessageLookupByLibrary.simpleMessage("Скачивание"), "edit": MessageLookupByLibrary.simpleMessage("Редактировать"), + "emptyTip": m2, "en": MessageLookupByLibrary.simpleMessage("Английский"), "enableOverride": MessageLookupByLibrary.simpleMessage( "Включить переопределение", @@ -257,6 +263,7 @@ class MessageLookup extends MessageLookupByLibrary { "excludeDesc": MessageLookupByLibrary.simpleMessage( "Когда приложение находится в фоновом режиме, оно скрыто из последних задач", ), + "existsTip": m3, "exit": MessageLookupByLibrary.simpleMessage("Выход"), "expand": MessageLookupByLibrary.simpleMessage("Стандартный"), "expirationTime": MessageLookupByLibrary.simpleMessage("Время истечения"), @@ -338,7 +345,10 @@ class MessageLookup extends MessageLookupByLibrary { "Конфигурация иконки", ), "iconStyle": MessageLookupByLibrary.simpleMessage("Стиль иконки"), + "import": MessageLookupByLibrary.simpleMessage("Импорт"), + "importFile": MessageLookupByLibrary.simpleMessage("Импорт из файла"), "importFromURL": MessageLookupByLibrary.simpleMessage("Импорт из URL"), + "importUrl": MessageLookupByLibrary.simpleMessage("Импорт по URL"), "infiniteTime": MessageLookupByLibrary.simpleMessage( "Долгосрочное действие", ), @@ -350,6 +360,7 @@ class MessageLookup extends MessageLookupByLibrary { "Интеллектуальный выбор", ), "internet": MessageLookupByLibrary.simpleMessage("Интернет"), + "interval": MessageLookupByLibrary.simpleMessage("Интервал"), "intranetIP": MessageLookupByLibrary.simpleMessage("Внутренний IP"), "ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"), "ipv6Desc": MessageLookupByLibrary.simpleMessage( @@ -364,9 +375,6 @@ class MessageLookup extends MessageLookupByLibrary { "Интервал поддержания TCP-соединения", ), "key": MessageLookupByLibrary.simpleMessage("Ключ"), - "keyExists": MessageLookupByLibrary.simpleMessage( - "Текущий ключ уже существует", - ), "language": MessageLookupByLibrary.simpleMessage("Язык"), "layout": MessageLookupByLibrary.simpleMessage("Макет"), "light": MessageLookupByLibrary.simpleMessage("Светлый"), @@ -407,6 +415,7 @@ class MessageLookup extends MessageLookupByLibrary { "Изменить стандартное событие выхода из системы", ), "minutes": MessageLookupByLibrary.simpleMessage("Минут"), + "mixedPort": MessageLookupByLibrary.simpleMessage("Смешанный порт"), "mode": MessageLookupByLibrary.simpleMessage("Режим"), "monochromeScheme": MessageLookupByLibrary.simpleMessage("Монохром"), "months": MessageLookupByLibrary.simpleMessage("Месяцев"), @@ -447,22 +456,14 @@ 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( "Нет профиля, пожалуйста, добавьте профиль", ), - "nullProxies": MessageLookupByLibrary.simpleMessage("Нет прокси"), - "nullRequestsDesc": MessageLookupByLibrary.simpleMessage("Нет запросов"), + "nullTip": m4, + "numberTip": m5, "oneColumn": MessageLookupByLibrary.simpleMessage("Один столбец"), "onlyIcon": MessageLookupByLibrary.simpleMessage("Только иконка"), "onlyOtherApps": MessageLookupByLibrary.simpleMessage( @@ -490,18 +491,21 @@ 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( "Пожалуйста, введите пароль администратора", ), @@ -512,6 +516,10 @@ class MessageLookup extends MessageLookupByLibrary { "Пожалуйста, загрузите действительный QR-код", ), "port": MessageLookupByLibrary.simpleMessage("Порт"), + "portConflictTip": MessageLookupByLibrary.simpleMessage( + "Введите другой порт", + ), + "portTip": m6, "preferH3Desc": MessageLookupByLibrary.simpleMessage( "Приоритетное использование HTTP/3 для DOH", ), @@ -586,6 +594,7 @@ class MessageLookup extends MessageLookupByLibrary { "recoverySuccess": MessageLookupByLibrary.simpleMessage( "Восстановление успешно", ), + "redirPort": MessageLookupByLibrary.simpleMessage("Redir-порт"), "redo": MessageLookupByLibrary.simpleMessage("Повторить"), "regExp": MessageLookupByLibrary.simpleMessage("Регулярное выражение"), "remote": MessageLookupByLibrary.simpleMessage("Удаленный"), @@ -596,6 +605,7 @@ class MessageLookup extends MessageLookupByLibrary { "Восстановление данных с WebDAV", ), "remove": MessageLookupByLibrary.simpleMessage("Удалить"), + "rename": MessageLookupByLibrary.simpleMessage("Переименовать"), "requests": MessageLookupByLibrary.simpleMessage("Запросы"), "requestsDesc": MessageLookupByLibrary.simpleMessage( "Просмотр последних записей запросов", @@ -626,24 +636,19 @@ 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": m0, + "selectedCountTitle": m7, "settings": MessageLookupByLibrary.simpleMessage("Настройки"), "show": MessageLookupByLibrary.simpleMessage("Показать"), "shrink": MessageLookupByLibrary.simpleMessage("Сжать"), @@ -652,6 +657,7 @@ class MessageLookup extends MessageLookupByLibrary { "Запуск в фоновом режиме", ), "size": MessageLookupByLibrary.simpleMessage("Размер"), + "socksPort": MessageLookupByLibrary.simpleMessage("Socks-порт"), "sort": MessageLookupByLibrary.simpleMessage("Сортировка"), "source": MessageLookupByLibrary.simpleMessage("Источник"), "sourceIp": MessageLookupByLibrary.simpleMessage("Исходный IP"), @@ -667,9 +673,6 @@ 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("Система"), @@ -703,6 +706,7 @@ class MessageLookup extends MessageLookupByLibrary { "toggle": MessageLookupByLibrary.simpleMessage("Переключить"), "tonalSpotScheme": MessageLookupByLibrary.simpleMessage("Тональный акцент"), "tools": MessageLookupByLibrary.simpleMessage("Инструменты"), + "tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxy-порт"), "trafficUsage": MessageLookupByLibrary.simpleMessage( "Использование трафика", ), @@ -722,20 +726,19 @@ 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( diff --git a/lib/l10n/intl/messages_zh_CN.dart b/lib/l10n/intl/messages_zh_CN.dart index 3a8d965..f7c7b71 100644 --- a/lib/l10n/intl/messages_zh_CN.dart +++ b/lib/l10n/intl/messages_zh_CN.dart @@ -20,7 +20,23 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'zh_CN'; - static String m0(count) => "已选择 ${count} 项"; + 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"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -34,7 +50,6 @@ class MessageLookup extends MessageLookupByLibrary { "选中应用将会被排除在VPN之外", ), "account": MessageLookupByLibrary.simpleMessage("账号"), - "accountTip": MessageLookupByLibrary.simpleMessage("账号不能为空"), "action": MessageLookupByLibrary.simpleMessage("操作"), "action_mode": MessageLookupByLibrary.simpleMessage("切换模式"), "action_proxy": MessageLookupByLibrary.simpleMessage("系统代理"), @@ -72,6 +87,7 @@ class MessageLookup extends MessageLookupByLibrary { "autoLaunchDesc": MessageLookupByLibrary.simpleMessage("跟随系统自启动"), "autoRun": MessageLookupByLibrary.simpleMessage("自动运行"), "autoRunDesc": MessageLookupByLibrary.simpleMessage("应用打开时自动运行"), + "autoSetSystemDns": MessageLookupByLibrary.simpleMessage("自动设置系统DNS"), "autoUpdate": MessageLookupByLibrary.simpleMessage("自动更新"), "autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自动更新间隔(分钟)"), "backup": MessageLookupByLibrary.simpleMessage("备份"), @@ -97,7 +113,7 @@ class MessageLookup extends MessageLookupByLibrary { "clearData": MessageLookupByLibrary.simpleMessage("清除数据"), "clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"), "clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"), - "colorExists": MessageLookupByLibrary.simpleMessage("该颜色已存在"), + "color": MessageLookupByLibrary.simpleMessage("颜色"), "colorSchemes": MessageLookupByLibrary.simpleMessage("配色方案"), "columns": MessageLookupByLibrary.simpleMessage("列数"), "compatible": MessageLookupByLibrary.simpleMessage("兼容模式"), @@ -110,7 +126,6 @@ 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("复制环境变量"), @@ -132,9 +147,8 @@ class MessageLookup extends MessageLookupByLibrary { "delay": MessageLookupByLibrary.simpleMessage("延迟"), "delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"), "delete": MessageLookupByLibrary.simpleMessage("删除"), - "deleteColorTip": MessageLookupByLibrary.simpleMessage("确定删除当前颜色吗?"), - "deleteProfileTip": MessageLookupByLibrary.simpleMessage("确定要删除当前配置吗?"), - "deleteRuleTip": MessageLookupByLibrary.simpleMessage("确定要删除选中的规则吗?"), + "deleteMultipTip": m0, + "deleteTip": m1, "desc": MessageLookupByLibrary.simpleMessage( "基于ClashMeta的多平台代理客户端,简单易用,开源无广告。", ), @@ -154,11 +168,13 @@ 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("到期时间"), @@ -206,12 +222,16 @@ class MessageLookup extends MessageLookupByLibrary { "icon": MessageLookupByLibrary.simpleMessage("图片"), "iconConfiguration": MessageLookupByLibrary.simpleMessage("图片配置"), "iconStyle": MessageLookupByLibrary.simpleMessage("图标样式"), + "import": MessageLookupByLibrary.simpleMessage("导入"), + "importFile": MessageLookupByLibrary.simpleMessage("通过文件导入"), "importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"), + "importUrl": MessageLookupByLibrary.simpleMessage("通过URL导入"), "infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"), "init": MessageLookupByLibrary.simpleMessage("初始化"), "inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("请输入正确的快捷键"), "intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"), "internet": MessageLookupByLibrary.simpleMessage("互联网"), + "interval": MessageLookupByLibrary.simpleMessage("间隔"), "intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"), "ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"), "ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"), @@ -220,7 +240,6 @@ 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("浅色"), @@ -245,6 +264,7 @@ 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("月"), @@ -271,14 +291,10 @@ 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("没有配置文件,请先添加配置文件"), - "nullProxies": MessageLookupByLibrary.simpleMessage("暂无代理"), - "nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"), + "nullTip": m4, + "numberTip": m5, "oneColumn": MessageLookupByLibrary.simpleMessage("一列"), "onlyIcon": MessageLookupByLibrary.simpleMessage("仅图标"), "onlyOtherApps": MessageLookupByLibrary.simpleMessage("仅第三方应用"), @@ -294,12 +310,13 @@ 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( "请输入管理员密码", ), @@ -308,6 +325,8 @@ class MessageLookup extends MessageLookupByLibrary { "请上传有效的二维码", ), "port": MessageLookupByLibrary.simpleMessage("端口"), + "portConflictTip": MessageLookupByLibrary.simpleMessage("请输入不同的端口"), + "portTip": m6, "preferH3Desc": MessageLookupByLibrary.simpleMessage("优先使用DOH的http/3"), "pressKeyboard": MessageLookupByLibrary.simpleMessage("请按下按键"), "preview": MessageLookupByLibrary.simpleMessage("预览"), @@ -352,12 +371,14 @@ 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("重置"), @@ -376,24 +397,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": m0, + "selectedCountTitle": m7, "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"), @@ -407,7 +428,6 @@ 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("系统"), @@ -433,6 +453,7 @@ 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("仅在管理员模式生效"), @@ -444,14 +465,15 @@ 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相关设置"), diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 645a56d..d8aeb56 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -140,16 +140,6 @@ 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( @@ -215,11 +205,6 @@ 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: []); @@ -1160,26 +1145,6 @@ 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( @@ -1465,26 +1430,6 @@ 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: []); @@ -1700,16 +1645,6 @@ 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( @@ -1930,16 +1865,6 @@ 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: []); @@ -2615,11 +2540,6 @@ class AppLocalizations { ); } - /// `No proxies` - String get nullProxies { - return Intl.message('No proxies', name: 'nullProxies', desc: '', args: []); - } - /// `Copy success` String get copySuccess { return Intl.message( @@ -2665,26 +2585,6 @@ 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: []); @@ -2735,16 +2635,6 @@ 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: []); @@ -2755,46 +2645,16 @@ 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: []); @@ -2845,16 +2705,6 @@ 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( @@ -2905,26 +2755,6 @@ 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( @@ -3119,6 +2949,196 @@ 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: [], + ); + } + + /// `Import` + String get import { + return Intl.message('Import', name: 'import', desc: '', args: []); + } + + /// `Import from file` + String get importFile { + return Intl.message( + 'Import from file', + name: 'importFile', + desc: '', + args: [], + ); + } + + /// `Import from URL` + String get importUrl { + return Intl.message( + 'Import from URL', + name: 'importUrl', + desc: '', + args: [], + ); + } + + /// `Auto set system DNS` + String get autoSetSystemDns { + return Intl.message( + 'Auto set system DNS', + name: 'autoSetSystemDns', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/main.dart b/lib/main.dart index cbccead..02dff41 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,7 +5,6 @@ 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'; @@ -17,13 +16,11 @@ import 'application.dart'; import 'clash/core.dart'; import 'clash/lib.dart'; import 'common/common.dart'; +import 'models/models.dart'; Future main() async { globalState.isService = false; WidgetsFlutterBinding.ensureInitialized(); - FlutterError.onError = (details) { - commonPrint.log(details.stack.toString()); - }; final version = await system.version; await clashCore.preload(); await globalState.initApp(version); @@ -77,27 +74,35 @@ Future _service(List flags) async { app?.tip(appLocalizations.startVpn); final homeDirPath = await appPath.homeDirPath; final version = await system.version; - 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(); - }, + final clashConfig = globalState.config.patchClashConfig.copyWith.tun( + enable: false, ); + 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(); + }); } } diff --git a/lib/manager/app_state_manager.dart b/lib/manager/app_state_manager.dart index a9e7d99..fa8664b 100644 --- a/lib/manager/app_state_manager.dart +++ b/lib/manager/app_state_manager.dart @@ -32,10 +32,43 @@ class _AppStateManagerState extends ConsumerState } }); }); + ref.listenManual( + checkIpProvider, + (prev, next) { + if (prev != next && next.b) { + detectionState.startCheck(); + } + }, + fireImmediately: true, + ); + ref.listenManual(configStateProvider, (prev, next) { + if (prev != next) { + globalState.appController.savePreferencesDebounce(); + } + }); + ref.listenManual( + autoSetSystemDnsStateProvider, + (prev, next) async { + if (prev == next) { + return; + } + if (next.a == true && next.b == true) { + system.setMacOSDns(false); + } else { + system.setMacOSDns(true); + } + }, + ); } @override - void dispose() { + reassemble() { + super.reassemble(); + } + + @override + void dispose() async { + await system.setMacOSDns(true); WidgetsBinding.instance.removeObserver(this); super.dispose(); } diff --git a/lib/manager/clash_manager.dart b/lib/manager/clash_manager.dart index d45d62a..10561e8 100644 --- a/lib/manager/clash_manager.dart +++ b/lib/manager/clash_manager.dart @@ -32,7 +32,7 @@ class _ClashContainerState extends ConsumerState void initState() { super.initState(); clashMessage.addListener(this); - ref.listenManual(currentProfileIdProvider, (prev, next) { + ref.listenManual(needSetupProvider, (prev, next) { if (prev != next) { globalState.appController.handleChangeProfile(); } @@ -42,11 +42,22 @@ class _ClashContainerState extends ConsumerState await clashCore.setState(next); } }); - ref.listenManual(clashConfigStateProvider, (prev, next) { + ref.listenManual(updateParamsProvider, (prev, next) { if (prev != next) { globalState.appController.updateClashConfigDebounce(); } }); + + ref.listenManual( + appSettingProvider.select((state) => state.openLogs), + (prev, next) { + if (next) { + clashCore.startLog(); + } else { + clashCore.stopLog(); + } + }, + ); } @override @@ -61,7 +72,7 @@ class _ClashContainerState extends ConsumerState final appController = globalState.appController; appController.setDelay(delay); debouncer.call( - DebounceTag.updateDelay, + FunctionTag.updateDelay, () async { await appController.updateGroupsDebounce(); }, diff --git a/lib/manager/connectivity_manager.dart b/lib/manager/connectivity_manager.dart index ee012aa..732de80 100644 --- a/lib/manager/connectivity_manager.dart +++ b/lib/manager/connectivity_manager.dart @@ -4,7 +4,7 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; class ConnectivityManager extends StatefulWidget { - final VoidCallback? onConnectivityChanged; + final Function(List results)? onConnectivityChanged; final Widget child; const ConnectivityManager({ @@ -23,9 +23,9 @@ class _ConnectivityManagerState extends State { @override void initState() { super.initState(); - subscription = Connectivity().onConnectivityChanged.listen((_) async { + subscription = Connectivity().onConnectivityChanged.listen((results) async { if (widget.onConnectivityChanged != null) { - widget.onConnectivityChanged!(); + widget.onConnectivityChanged!(results); } }); } diff --git a/lib/manager/hotkey_manager.dart b/lib/manager/hotkey_manager.dart index 1f6d540..b44c389 100644 --- a/lib/manager/hotkey_manager.dart +++ b/lib/manager/hotkey_manager.dart @@ -8,7 +8,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; -class HotKeyManager extends StatelessWidget { +class HotKeyManager extends ConsumerStatefulWidget { final Widget child; const HotKeyManager({ @@ -16,6 +16,25 @@ class HotKeyManager extends StatelessWidget { required this.child, }); + @override + ConsumerState createState() => _HotKeyManagerState(); +} + +class _HotKeyManagerState extends ConsumerState { + @override + void initState() { + super.initState(); + ref.listenManual( + hotKeyActionsProvider, + (prev, next) { + if (!hotKeyActionListEquality.equals(prev, next)) { + _updateHotKeys(hotKeyActions: next); + } + }, + fireImmediately: true, + ); + } + _handleHotKeyAction(HotAction action) async { switch (action) { case HotAction.mode: @@ -59,22 +78,30 @@ class HotKeyManager extends StatelessWidget { await Future.wait(hotkeyActionHandles); } + _buildShortcuts(Widget child) { + return Shortcuts( + shortcuts: { + utils.controlSingleActivator(LogicalKeyboardKey.keyW): + CloseWindowIntent(), + }, + child: Actions( + actions: { + CloseWindowIntent: CallbackAction( + onInvoke: (_) => globalState.appController.handleBackOrExit(), + ), + DoNothingIntent: CallbackAction( + onInvoke: (_) => null, + ), + }, + child: child, + ), + ); + } + @override Widget build(BuildContext context) { - return Consumer( - builder: (_, ref, child) { - ref.listenManual( - hotKeyActionsProvider, - (prev, next) { - if (!hotKeyActionListEquality.equals(prev, next)) { - _updateHotKeys(hotKeyActions: next); - } - }, - fireImmediately: true, - ); - return child!; - }, - child: child, + return _buildShortcuts( + widget.child, ); } } diff --git a/lib/manager/message_manager.dart b/lib/manager/message_manager.dart index 64cd9f0..6c67c5b 100644 --- a/lib/manager/message_manager.dart +++ b/lib/manager/message_manager.dart @@ -39,6 +39,7 @@ class MessageManagerState extends State { id: utils.uuidV4, text: text, ); + commonPrint.log(text); _bufferMessages.add(commonMessage); await _showMessage(); } @@ -87,32 +88,32 @@ class MessageManagerState extends State { child: messages.isEmpty ? SizedBox() : LayoutBuilder( - key: Key(messages.last.id), - builder: (_, constraints) { - return Card( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(12.0), - ), + 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, + ), + ), + ); + }, ), - 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, - ), - ), - ); - }, - ), ); }, ), diff --git a/lib/manager/theme_manager.dart b/lib/manager/theme_manager.dart index 415364a..c6a8982 100644 --- a/lib/manager/theme_manager.dart +++ b/lib/manager/theme_manager.dart @@ -38,7 +38,7 @@ class ThemeManager extends ConsumerWidget { textScaleFactor, ), padding: padding.copyWith( - top: padding.top > height * 0.3 ? 0.0 : padding.top, + top: padding.top > height * 0.3 ? 20.0 : padding.top, ), ), child: LayoutBuilder( diff --git a/lib/manager/vpn_manager.dart b/lib/manager/vpn_manager.dart index 5ad197c..de56a9f 100644 --- a/lib/manager/vpn_manager.dart +++ b/lib/manager/vpn_manager.dart @@ -29,7 +29,7 @@ class _VpnContainerState extends ConsumerState { showTip() { debouncer.call( - DebounceTag.vpnTip, + FunctionTag.vpnTip, () { if (ref.read(runTimeProvider.notifier).isStart) { globalState.showNotifier( diff --git a/lib/manager/window_manager.dart b/lib/manager/window_manager.dart index 821f49a..3731346 100644 --- a/lib/manager/window_manager.dart +++ b/lib/manager/window_manager.dart @@ -38,7 +38,7 @@ class _WindowContainerState extends ConsumerState (prev, next) { if (prev != next) { debouncer.call( - DebounceTag.autoLaunch, + FunctionTag.autoLaunch, () { autoLaunch?.updateStatus(next); }, @@ -102,9 +102,10 @@ class _WindowContainerState extends ConsumerState } @override - Future onTaskbarCreated() async { - globalState.appController.updateTray(true); - super.onTaskbarCreated(); + void onWindowRestore() { + commonPrint.log("restore"); + render?.resume(); + super.onWindowRestore(); } @override diff --git a/lib/models/app.dart b/lib/models/app.dart index 5fccfbc..0ff39ac 100644 --- a/lib/models/app.dart +++ b/lib/models/app.dart @@ -14,6 +14,7 @@ typedef DelayMap = Map>; class AppState with _$AppState { const factory AppState({ @Default(false) bool isInit, + @Default(false) bool backBlock, @Default(PageLabel.dashboard) PageLabel pageLabel, @Default([]) List packages, @Default(0) int sortNum, @@ -30,7 +31,8 @@ class AppState with _$AppState { required FixedList logs, required FixedList traffics, required Traffic totalTraffic, - @Default(false) bool needApply, + @Default("") String proxiesQuery, + @Default(false) bool realTunEnable, }) = _AppState; } diff --git a/lib/models/clash_config.dart b/lib/models/clash_config.dart index 086e21e..f72700b 100644 --- a/lib/models/clash_config.dart +++ b/lib/models/clash_config.dart @@ -1,12 +1,10 @@ // ignore_for_file: invalid_annotation_target import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/enum/enum.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import '../enum/enum.dart'; - part 'generated/clash_config.freezed.dart'; - part 'generated/clash_config.g.dart'; typedef HostsMap = Map; @@ -142,6 +140,41 @@ class RuleProvider with _$RuleProvider { _$RuleProviderFromJson(json); } +@freezed +class Sniffer with _$Sniffer { + const factory Sniffer({ + @Default(false) bool enable, + @Default(true) @JsonKey(name: "override-destination") bool overrideDest, + @Default([]) List sniffing, + @Default([]) @JsonKey(name: "force-domain") List forceDomain, + @Default([]) @JsonKey(name: "skip-src-address") List skipSrcAddress, + @Default([]) @JsonKey(name: "skip-dst-address") List skipDstAddress, + @Default([]) @JsonKey(name: "skip-domain") List skipDomain, + @Default([]) @JsonKey(name: "port-whitelist") List port, + @Default(true) @JsonKey(name: "force-dns-mapping") bool forceDnsMapping, + @Default(true) @JsonKey(name: "parse-pure-ip") bool parsePureIp, + @Default({}) Map sniff, + }) = _Sniffer; + + factory Sniffer.fromJson(Map json) => + _$SnifferFromJson(json); +} + +List _formJsonPorts(List? ports) { + return ports?.map((item) => item.toString()).toList() ?? []; +} + +@freezed +class SnifferConfig with _$SnifferConfig { + const factory SnifferConfig({ + @Default([]) @JsonKey(fromJson: _formJsonPorts) List ports, + @JsonKey(name: "override-destination") bool? overrideDest, + }) = _SnifferConfig; + + factory SnifferConfig.fromJson(Map json) => + _$SnifferConfigFromJson(json); +} + @freezed class Tun with _$Tun { const factory Tun({ @@ -408,7 +441,7 @@ List _genSubRules(Map json) { class ClashConfigSnippet with _$ClashConfigSnippet { const factory ClashConfigSnippet({ @Default([]) @JsonKey(name: "proxy-groups") List proxyGroups, - @JsonKey(fromJson: _genRule) @Default([]) List rule, + @JsonKey(fromJson: _genRule, name: "rules") @Default([]) List rule, @JsonKey(name: "rule-providers", fromJson: _genRuleProviders) @Default([]) List ruleProvider, @@ -425,6 +458,10 @@ class ClashConfigSnippet with _$ClashConfigSnippet { class ClashConfig with _$ClashConfig { const factory ClashConfig({ @Default(defaultMixedPort) @JsonKey(name: "mixed-port") int mixedPort, + @Default(0) @JsonKey(name: "socks-port") int socksPort, + @Default(0) @JsonKey(name: "port") int port, + @Default(0) @JsonKey(name: "redir-port") int redirPort, + @Default(0) @JsonKey(name: "tproxy-port") int tproxyPort, @Default(Mode.rule) Mode mode, @Default(false) @JsonKey(name: "allow-lan") bool allowLan, @Default(LogLevel.error) @JsonKey(name: "log-level") LogLevel logLevel, diff --git a/lib/models/common.dart b/lib/models/common.dart index eb796ae..39b8c9e 100644 --- a/lib/models/common.dart +++ b/lib/models/common.dart @@ -8,7 +8,6 @@ import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'generated/common.freezed.dart'; - part 'generated/common.g.dart'; @freezed @@ -17,7 +16,7 @@ class NavigationItem with _$NavigationItem { required Icon icon, required PageLabel label, final String? description, - required Widget fragment, + required Widget view, @Default(true) bool keep, String? path, @Default([NavigationItemMode.mobile, NavigationItemMode.desktop]) @@ -129,10 +128,10 @@ extension LogsStateExt on LogsState { final lowQuery = query.toLowerCase(); return logs.where( (log) { - final payload = log.payload.toLowerCase(); final logLevelName = log.logLevel.name; return {logLevelName}.containsAll(keywords) && - ((payload.contains(lowQuery)) || logLevelName.contains(lowQuery)); + ((log.payload.toLowerCase().contains(lowQuery)) || + logLevelName.contains(lowQuery)); }, ).toList(); } @@ -504,15 +503,11 @@ class PopupMenuItemData { this.icon, required this.label, required this.onPressed, - this.type, - this.iconSize, }); - final double? iconSize; final String label; final VoidCallback? onPressed; final IconData? icon; - final PopupMenuItemType? type; } @freezed @@ -528,3 +523,56 @@ class TextPainterParams with _$TextPainterParams { factory TextPainterParams.fromJson(Map json) => _$TextPainterParamsFromJson(json); } + +class CloseWindowIntent extends Intent { + const CloseWindowIntent(); +} + +@freezed +class Result with _$Result { + const factory Result({ + required T? data, + required ResultType type, + required String message, + }) = _Result; + + factory Result.success(T data) => Result( + data: data, + type: ResultType.success, + message: "", + ); + + factory Result.error(String message) => Result( + data: null, + type: ResultType.error, + message: message, + ); +} + +extension ResultExt on Result { + bool get isError => type == ResultType.error; + + bool get isSuccess => type == ResultType.success; +} + +@freezed +class Script with _$Script { + const factory Script({ + required String id, + required String label, + required String content, + }) = _Script; + + factory Script.create({ + required String label, + required String content, + }) { + return Script( + id: utils.uuidV4, + label: label, + content: content, + ); + } + + factory Script.fromJson(Map json) => _$ScriptFromJson(json); +} diff --git a/lib/models/config.dart b/lib/models/config.dart index f541db2..42352c5 100644 --- a/lib/models/config.dart +++ b/lib/models/config.dart @@ -8,7 +8,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'models.dart'; part 'generated/config.freezed.dart'; - part 'generated/config.g.dart'; const defaultBypassDomain = [ @@ -153,7 +152,8 @@ class NetworkProps with _$NetworkProps { const factory NetworkProps({ @Default(true) bool systemProxy, @Default(defaultBypassDomain) List bypassDomain, - @Default(RouteMode.bypassPrivate) RouteMode routeMode, + @Default(RouteMode.config) RouteMode routeMode, + @Default(true) bool autoSetSystemDns, }) = _NetworkProps; factory NetworkProps.fromJson(Map? json) => @@ -212,6 +212,35 @@ class ThemeProps with _$ThemeProps { } } +@freezed +class ScriptProps with _$ScriptProps { + const factory ScriptProps({ + String? currentId, + @Default([]) List