Compare commits

..

1 Commits

Author SHA1 Message Date
chen08209
9271d59f72 Fix windows tun issues
Fix windows backup recovery issues

Optimize overwrite handle

Optimize access control page

Optimize some details
2025-11-28 17:35:27 +08:00
243 changed files with 12387 additions and 19471 deletions

View File

@@ -22,7 +22,7 @@ jobs:
os: ubuntu-22.04 os: ubuntu-22.04
arch: amd64 arch: amd64
- platform: macos - platform: macos
os: macos-15-intel os: macos-13
arch: amd64 arch: amd64
- platform: macos - platform: macos
os: macos-latest os: macos-latest
@@ -177,23 +177,21 @@ jobs:
- name: Generate release.md - name: Generate release.md
run: | run: |
tags=($(git tag --merged HEAD --sort=-creatordate)) tags=($(git tag --merged HEAD --sort=-creatordate))
preTag=$(curl -s "https://api.github.com/repos/chen08209/FlClash/releases/latest" | \ preTag=$(curl -s "https://api.github.com/repos/chen08209/FlClash/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")' || echo "")
sed -nE 's/.*"tag_name": "([^"]+)".*/\1/p')
[ -z "$preTag" ] && preTag=""
out="release.md" > "$out" out="release.md" > "$out"
for i in "${!tags[@]}"; do for i in "${!tags[@]}"; do
curr="${tags[i]}" curr="${tags[i]}"
[[ "$curr" == "$preTag" ]] && break [[ "$curr" == "$preTag" ]] && break
prev="${tags[i+1]}" prev="${tags[i+1]}"
range="${prev:+$prev..}$curr" range="${prev:+$prev..}$curr"
echo -e "## $curr\n" >> "$out"
git log --no-merges --pretty=format:"%B" "$range" | \ git log --no-merges --pretty=format:"%B" "$range" | \
awk '!/Update changelog/ && NF {print "- " $0 "\n"}' >> "$out" awk '!/Update changelog/ && NF {print "- " $0 "\n"}' >> "$out"
done done
- name: Push to telegram - name: Push to telegram
env: env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}

11
.gitignore vendored
View File

@@ -21,7 +21,7 @@ migrate_working_dir/
# The .vscode folder contains launch configuration and tasks you configure in # The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line # VS Code which you may wish to be included in version control, so this line
# is commented out by default. # is commented out by default.
.vscode/ #.vscode/
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
**/doc/api/ **/doc/api/
@@ -41,11 +41,6 @@ app.*.symbols
# Obfuscation related # Obfuscation related
app.*.map.json app.*.map.json
#AI generated
CLAUDE.md
/.claude
# Android Studio will place build artifacts here # Android Studio will place build artifacts here
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
@@ -58,6 +53,7 @@ CLAUDE.md
/android/core/**/cmake-build-*/ /android/core/**/cmake-build-*/
/android/core/**/jniLibs/ /android/core/**/jniLibs/
#FlClash #FlClash
/libclash/ /libclash/
/android/app/src/main/jniLibs/ /android/app/src/main/jniLibs/
@@ -65,6 +61,3 @@ CLAUDE.md
/macos/**/Package.resolved /macos/**/Package.resolved
devtools_options.yaml devtools_options.yaml
# FVM Version Cache
.fvm/
.fvmrc

4
.gitmodules vendored
View File

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

View File

@@ -1,11 +1,11 @@
# This file tracks properties of this Flutter project. # This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc. # Used by Flutter tool to assess capabilities and perform upgrades etc.
# #
# This file should be version controlled and should not be manually edited. # This file should be version controlled.
version: version:
revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2" revision: 796c8ef79279f9c774545b3771238c3098dbefab
channel: "stable" channel: stable
project_type: app project_type: app
@@ -13,11 +13,26 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: android
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: ios
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: linux
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: macos
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: web
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: windows - platform: windows
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
# User provided section # User provided section

View File

@@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="additionalArgs" value="--dart-define-from-file env.json" />
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
<method v="2" />
</configuration>
</component>

View File

@@ -1,974 +0,0 @@
## v0.8.92
- Add sqlite store
- Optimize android quick action
- Optimize backup and restore
- Optimize more details
## v0.8.91
- Fix windows some issues
- Optimize overwrite handle
- Optimize access control page
- Optimize some details
## v0.8.90
- Fix android tile service
- Support append system DNS
- Fix some issues
- Update changelog
## v0.8.89
- Fix some issues
- Optimize Windows service mode
- Update core
- Update changelog
## v0.8.88
- Add android separates the core process
- Support core status check and force restart
- Optimize proxies page and access page
- Update flutter and pub dependencies
- Update go version
- Optimize more details
- Update changelog
## v0.8.87
- Optimize desktop view
- Optimize logs, requests, connection pages
- Optimize windows tray auto hide
- Optimize some details
- Update core
- Update changelog
## v0.8.86
- Fix windows tun issues
- Optimize android get system dns
- Optimize more details
- Update changelog
## v0.8.85
- Support override script
- Support proxies search
- Support svg display
- Optimize config persistence
- Add some scenes auto close connections
- Update core
- Optimize more details
## v0.8.84
- Fix windows service verify issues
- Update changelog
## v0.8.83
- Add windows server mode start process verify
- Add linux deb dependencies
- Add backup recovery strategy select
- Support custom text scaling
- Optimize the display of different text scale
- Optimize windows setup experience
- Optimize startTun performance
- Optimize android tv experience
- Optimize default option
- Optimize computed text size
- Optimize hyperOS freeform window
- Add developer mode
- Update core
- Optimize more details
- Add issues template
- Update changelog
## v0.8.82
- Optimize android vpn performance
- Add custom primary color and color scheme
- Add linux nad windows arm release
- Optimize requests and logs page
- Fix map input page delete issues
- Update changelog
## v0.8.81
- Add rule override
- Update core
- Optimize more details
- Update changelog
## v0.8.80
- Optimize dashboard performance
- Fix some issues
- Fix unselected proxy group delay issues
- Fix asn url issues
- Update changelog
## v0.8.79
- Fix tab delay view issues
- Fix tray action issues
- Fix get profile redirect client ua issues
- Fix proxy card delay view issues
- Add Russian, Japanese adaptation
- Fix some issues
- Update changelog
## v0.8.78
- Fix list form input view issues
- Fix traffic view issues
- Update changelog
## v0.8.77
- Optimize performance
- Update core
- Optimize core stability
- Fix linux tun authority check error
- Fix some issues
- Fix scroll physics error
- Update changelog
## v0.8.75
- Add windows storage corruption detection
- Fix core crash caused by windows resource manager restart
- Optimize logs, requests, access to pages
- Fix macos bypass domain issues
- Update changelog
## v0.8.74
- Fix some issues
- Update changelog
## v0.8.73
- Update popup menu
- Add file editor
- Fix android service issues
- Optimize desktop background performance
- Optimize android main process performance
- Optimize delay test
- Optimize vpn protect
- Update changelog
## v0.8.72
- Update core
- Fix some issues
- Update changelog
## v0.8.71
- Remake dashboard
- Optimize theme
- Optimize more details
- Update flutter version
- Update changelog
## v0.8.70
- Support better window position memory
- Add windows arm64 and linux arm64 build script
- Optimize some details
## v0.8.69
- Remake desktop
- Optimize change proxy
- Optimize network check
- Fix fallback issues
- Optimize lots of details
- Update change.yaml
- Fix android tile issues
- Fix windows tray issues
- Support setting bypassDomain
- Update flutter version
- Fix android service issues
- Fix macos dock exit button issues
- Add route address setting
- Optimize provider view
- Update changelog
- Update CHANGELOG.md
## v0.8.67
- Add android shortcuts
- Fix init params issues
- Fix dynamic color issues
- Optimize navigator animate
- Optimize window init
- Optimize fab
- Optimize save
## v0.8.66
- Fix the collapse issues
- Add fontFamily options
## v0.8.65
- Update core version
- Update flutter version
- Optimize ip check
- Optimize url-test
## v0.8.64
- Update release message
- Init auto gen changelog
- Fix windows tray issues
- Fix urltest issues
- Add auto changelog
- Fix windows admin auto launch issues
- Add android vpn options
- Support proxies icon configuration
- Optimize android immersion display
- Fix some issues
- Optimize ip detection
- Support android vpn ipv6 inbound switch
- Support log export
- Optimize more details
- Fix android system dns issues
- Optimize dns default option
- Fix some issues
- Update readme
## v0.8.60
- Fix build error2
- Fix build error
- Support desktop hotkey
- Support android ipv6 inbound
- Support android system dns
- fix some bugs
## v0.8.59
- Fix delete profile error
## v0.8.58
- Fix submit error 2
- Fix submit error
- Optimize DNS strategy
- Fix the problem that the tray is not displayed in some cases
- Optimize tray
- Update core
- Fix some error
## v0.8.57
- Fix tun update issues
- Add DNS override
- Fixed some bugs
- Optimize more detail
- Add Hosts override
## v0.8.56
- fix android tip error
- fix windows auto launch error
## v0.8.55
- Fix windows tray issues
- Optimize windows logic
- Optimize app logic
- Support windows administrator auto launch
- Support android close vpn
## v0.8.53
- Change flutter version
- Support profiles sort
- Support windows country flags display
- Optimize proxies page and profiles page columns
## v0.8.52
- Update flutter version
- Update version
- Update timeout time
- Update access control page
- Fix bug
## v0.8.51
- Optimize provider page
- Optimize delay test
- Support local backup and recovery
- Fix android tile service issues
## v0.8.49
- Fix linux core build error
- Add proxy-only traffic statistics
- Update core
- Optimize more details
- Merge pull request #140 from txyyh/main
- 添加自建 F-Droid 仓库相关 workflow
- Rename readme fingerprint
- Rename workflow deploy repo name
- Add download guide to README
- Add push release files to fdroid-repo
## v0.8.48
- Optimize proxies page
- Fix ua issues
- Optimize more details
## v0.8.47
- Fix windows build error
## v0.8.46
- Update app icon
- Fix desktop backup error
- Optimize request ua
- Change android icon
- Optimize dashboard
## v0.8.44
- Remove request validate certificate
- Sync core
## v0.8.43
- Fix windows error
## v0.8.42
- Fix setup.dart error
- Fix android system proxy not effective
- Add macos arm64
## v0.8.41
- Optimize proxies page
- Support mouse drag scroll
- Adjust desktop ui
- Revert "Fix android vpn issues"
- This reverts commit 891977408e6938e2acd74e9b9adb959c48c79988.
## v0.8.40
- Fix android vpn issues
- Fix android vpn issues
- Rollback partial modification
## v0.8.39
- Fix the problem that ui can't be synchronized when android vpn is occupied by an external
- Override default socksPort,port
## v0.8.38
- Fix fab issues
## v0.8.37
- Update version
- Fix the problem that vpn cannot be started in some cases
- Fix the problem that geodata url does not take effect
## v0.8.36
- Update ua
- Fix change outbound mode without check ip issues
- Separate android ui and vpn
- Fix url validate issues 2
- Add android hidden from the recent task
- Add geoip file
- Support modify geoData URL
## v0.8.35
- Fix url validate issues
- Fix check ip performance problem
- Optimize resources page
## v0.8.34
- Add ua selector
- Support modify test url
- Optimize android proxy
- Fix the error that async proxy provider could not selected the proxy
## v0.8.33
- Fix android proxy error
- Fix submit error
- Add windows tun
- Optimize android proxy
- Optimize change profile
- Update application ua
- Optimize delay test
## v0.8.32
- Fix android repeated request notification issues
## v0.8.31
- Fix memory overflow issues
## v0.8.30
- Optimize proxies expansion panel 2
- Fix android scan qrcode error
## v0.8.29
- Optimize proxies expansion panel
- Fix text error
## v0.8.28
- Optimize proxy
- Optimize delayed sorting performance
- Add expansion panel proxies page
- Support to adjust the proxy card size
- Support to adjust proxies columns number
- Fix autoRun show issues
- Fix Android 10 issues
- Optimize ip show
## v0.8.26
- Add intranet IP display
- Add connections page
- Add search in connections, requests
- Add keyword search in connections, requests, logs
- Add basic viewing editing capabilities
- Optimize update profile
## v0.8.25
- Update version
- Fix the problem of excessive memory usage in traffic usage.
- Add lightBlue theme color
- Fix start unable to update profile issues
- Fix flashback caused by process
## v0.8.23
- Add build version
- Optimize quick start
- Update system default option
## v0.8.22
- Update build.yml
- Fix android vpn close issues
- Add requests page
- Fix checkUpdate dark mode style error
- Fix quickStart error open app
- Add memory proxies tab index
- Support hidden group
- Optimize logs
- Fix externalController hot load error
## v0.8.21
- Add tcp concurrent switch
- Add system proxy switch
- Add geodata loader switch
- Add external controller switch
- Add auto gc on trim memory
- Fix android notification error
## v0.8.20
- Fix ipv6 error
- Fix android udp direct error
- Add ipv6 switch
- Add access all selected button
- Remove android low version splash
## v0.8.19
- Update version
- Add allowBypass
- Fix Android only pick .text file issues
## v0.8.18
- Fix search issues
## v0.8.17
- Fix LoadBalance, Relay load error
- Fix build.yml4
- Fix build.yml3
- Fix build.yml2
- Fix build.yml
- Add search function at access control
- Fix the issues with the profile add button to cover the edit button
- Adapt LoadBalance and Relay
- Add arm
- Fix android notification icon error
## v0.8.16
- Add one-click update all profiles
- Add expire show
## v0.8.15
- Temp remove tun mode
- Remove macos in workflow
- Change go version
## v0.8.14
- Update Version
- Fix tun unable to open
## v0.8.13
- Optimize delay test2
- Optimize delay test
- Add check ip
- add check ip request
## v0.8.12
- Fix the problem that the download of remote resources failed after GeodataMode was turned on, which caused the
application to flash back.
- Fix edit profile error
- Fix quickStart change proxy error
- Fix core version
## v0.8.10
- Fix core version
## v0.8.9
- Update file_picker
- Add resources page
- Optimize more detail
- Add access selected sorted
- Fix notification duplicate creation issue
- Fix AccessControl click issue
## v0.8.7
- Fix Workflow
- Fix Linux unable to open
- Update README.md 3
- Create LICENSE
- Update README.md 2
- Update README.md
- Optimize workFlow
## v0.8.6
- optimize checkUpdate
## v0.8.5
- Fix submit error
## v0.8.4
- add WebDAV
- add Auto check updates
- Optimize more details
- optimize delayTest
## v0.8.2
- upgrade flutter version
## v0.8.1
- Update kernel
- Add import profile via QR code image
## v0.8.0
- Add compatibility mode and adapt clash scheme.
## v0.7.14
- update Version
- Reconstruction application proxy logic
## v0.7.13
- Fix Tab destroy error
## v0.7.12
- Optimize repeat healthcheck
## v0.7.11
- Optimize Direct mode ui
## v0.7.10
- Optimize Healthcheck
- Remove proxies position animation, improve performance
- Add Telegram Link
- Update healthcheck policy
- New Check URLTest
- Fix the problem of invalid auto-selection
## v0.7.8
- New Async UpdateConfig
- add changeProfileDebounce
- Update Workflow
- Fix ChangeProfile block
- Fix Release Message Error
## v0.7.7
- Update Selector 2
## v0.7.6
- Update Version
- Fix Proxies Select Error
## v0.7.5
- Fix the problem that the proxy group is empty in global mode.
- Fix the problem that the proxy group is empty in global mode.
## v0.7.4
- Add ProxyProvider2
## v0.7.3
- Add ProxyProvider
- Update Version
- Update ProxyGroup Sort
- Fix Android quickStart VpnService some problems
## v0.7.1
- Update version
- Set Android notification low importance
- Fix the issue that VpnService can't be closed correctly in special cases
- Fix the problem that TileService is not destroyed correctly in some cases
- Adjust tab animation defaults
- Add Telegram in README_zh_CN.md
- Add Telegram
## v0.7.0
- update mobile_scanner
- Initial commit

10
Makefile Normal file
View File

@@ -0,0 +1,10 @@
android_arm64:
dart ./setup.dart android --arch arm64
macos_arm64:
dart ./setup.dart macos --arch arm64
android_app:
dart ./setup.dart android
android_arm64_core:
dart ./setup.dart android --arch arm64 --out core
macos_arm64_core:
dart ./setup.dart macos --arch arm64 --out core

View File

@@ -64,17 +64,16 @@ android {
buildTypes { buildTypes {
debug { debug {
isMinifyEnabled = false isMinifyEnabled = false
applicationIdSuffix = ".dev" applicationIdSuffix = ".debug"
} }
release { release {
isMinifyEnabled = true isMinifyEnabled = true
isShrinkResources = true isShrinkResources = true
if (isRelease) { signingConfig = if (isRelease) {
signingConfig = signingConfigs.getByName("release") signingConfigs.getByName("release")
} else { } else {
signingConfig = signingConfigs.getByName("debug") signingConfigs.getByName("debug")
applicationIdSuffix = ".dev"
} }
proguardFiles( proguardFiles(

View File

@@ -41,25 +41,6 @@
"other_platform_oauth_client": [] "other_platform_oauth_client": []
} }
} }
},
{
"client_info": {
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
"android_client_info": {
"package_name": "com.follow.clash.dev"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "0"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
} }
] ]
} }

View File

@@ -1,18 +1,13 @@
package com.follow.clash package com.follow.clash
import android.app.Application
import android.content.Context.MODE_PRIVATE
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.widget.Toast
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import com.follow.clash.common.GlobalState import com.follow.clash.common.GlobalState
import com.follow.clash.models.SharedState
import com.google.gson.Gson
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
@@ -26,30 +21,6 @@ import kotlin.coroutines.resume
private const val ICON_TTL_DAYS = 1L private const val ICON_TTL_DAYS = 1L
val Application.sharedState: SharedState
get() {
try {
val sp = getSharedPreferences("FlutterSharedPreferences", MODE_PRIVATE)
val res = sp.getString("flutter.sharedState", "")
return Gson().fromJson(res, SharedState::class.java)
} catch (_: Exception) {
return SharedState()
}
}
private var lastToast: Toast? = null
fun Application.showToast(text: String?) {
Handler(Looper.getMainLooper()).post {
lastToast?.cancel()
lastToast = Toast.makeText(this, text, Toast.LENGTH_LONG).apply {
show()
}
}
}
suspend fun PackageManager.getPackageIconPath(packageName: String): String = suspend fun PackageManager.getPackageIconPath(packageName: String): String =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val cacheDir = GlobalState.application.cacheDir val cacheDir = GlobalState.application.cacheDir
@@ -147,4 +118,4 @@ fun <T> MethodChannel.invokeMethodOnMainThread(
} }
}) })
} }
} }

View File

@@ -1,6 +1,7 @@
package com.follow.clash package com.follow.clash
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import com.follow.clash.common.GlobalState import com.follow.clash.common.GlobalState
import com.follow.clash.plugins.AppPlugin import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ServicePlugin import com.follow.clash.plugins.ServicePlugin
@@ -17,6 +18,9 @@ class MainActivity : FlutterActivity(),
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
lifecycleScope.launch {
State.destroyServiceEngine()
}
} }
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {

View File

@@ -1,6 +1,5 @@
package com.follow.clash package com.follow.clash
import com.follow.clash.common.GlobalState
import com.follow.clash.common.ServiceDelegate import com.follow.clash.common.ServiceDelegate
import com.follow.clash.common.formatString import com.follow.clash.common.formatString
import com.follow.clash.common.intent import com.follow.clash.common.intent
@@ -9,7 +8,6 @@ import com.follow.clash.service.ICallbackInterface
import com.follow.clash.service.IEventInterface import com.follow.clash.service.IEventInterface
import com.follow.clash.service.IRemoteInterface import com.follow.clash.service.IRemoteInterface
import com.follow.clash.service.IResultInterface import com.follow.clash.service.IResultInterface
import com.follow.clash.service.IVoidInterface
import com.follow.clash.service.RemoteService import com.follow.clash.service.RemoteService
import com.follow.clash.service.models.NotificationParams import com.follow.clash.service.models.NotificationParams
import com.follow.clash.service.models.VpnOptions import com.follow.clash.service.models.VpnOptions
@@ -42,7 +40,7 @@ object Service {
delegate.unbind() delegate.unbind()
} }
suspend fun invokeAction(data: String, cb: ((result: String) -> Unit)?): Result<Unit> { suspend fun invokeAction(data: String, cb: (result: String) -> Unit): Result<Unit> {
val res = mutableListOf<ByteArray>() val res = mutableListOf<ByteArray>()
return delegate.useService { return delegate.useService {
it.invokeAction( it.invokeAction(
@@ -53,50 +51,13 @@ object Service {
res.add(result ?: byteArrayOf()) res.add(result ?: byteArrayOf())
ack?.onAck() ack?.onAck()
if (isSuccess) { if (isSuccess) {
cb?.let { cb -> cb(res.formatString())
cb(res.formatString())
}
} }
} }
}) })
} }
} }
suspend fun quickSetup(
initParamsString: String,
setupParamsString: String,
onStarted: (() -> Unit)?,
onResult: ((result: String) -> Unit)?,
): Result<Unit> {
val res = mutableListOf<ByteArray>()
return delegate.useService {
it.quickSetup(
initParamsString,
setupParamsString,
object : ICallbackInterface.Stub() {
override fun onResult(
result: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
) {
res.add(result ?: byteArrayOf())
ack?.onAck()
if (isSuccess) {
onResult?.let { cb ->
cb(res.formatString())
}
}
}
},
object : IVoidInterface.Stub() {
override fun invoke() {
onStarted?.let { onStarted ->
onStarted()
}
}
}
)
}
}
suspend fun setEventListener( suspend fun setEventListener(
cb: ((result: String?) -> Unit)? cb: ((result: String?) -> Unit)?
): Result<Unit> { ): Result<Unit> {
@@ -104,24 +65,24 @@ object Service {
return delegate.useService { return delegate.useService {
it.setEventListener( it.setEventListener(
when (cb != null) { when (cb != null) {
true -> object : IEventInterface.Stub() { true -> object : IEventInterface.Stub() {
override fun onEvent( override fun onEvent(
id: String, data: ByteArray?, isSuccess: Boolean, ack: IAckInterface? id: String, data: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
) { ) {
if (results[id] == null) { if (results[id] == null) {
results[id] = mutableListOf() results[id] = mutableListOf()
} }
results[id]?.add(data ?: byteArrayOf()) results[id]?.add(data ?: byteArrayOf())
ack?.onAck() ack?.onAck()
if (isSuccess) { if (isSuccess) {
cb(results[id]?.formatString()) cb(results[id]?.formatString())
results.remove(id) results.remove(id)
}
} }
} }
}
false -> null false -> null
}) })
} }
} }
@@ -155,7 +116,6 @@ object Service {
try { try {
block(callback) block(callback)
} catch (e: Exception) { } catch (e: Exception) {
GlobalState.log("awaitIResultInterface $e")
if (continuation.isActive) { if (continuation.isActive) {
continuation.resumeWithException(e) continuation.resumeWithException(e)
} }

View File

@@ -1,17 +1,18 @@
package com.follow.clash package com.follow.clash
import android.net.VpnService
import com.follow.clash.common.GlobalState import com.follow.clash.common.GlobalState
import com.follow.clash.models.SharedState
import com.follow.clash.plugins.AppPlugin import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ServicePlugin
import com.follow.clash.plugins.TilePlugin import com.follow.clash.plugins.TilePlugin
import com.follow.clash.service.models.NotificationParams import io.flutter.FlutterInjector
import com.google.gson.Gson
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
enum class RunState { enum class RunState {
START, PENDING, STOP START, PENDING, STOP
@@ -24,17 +25,20 @@ object State {
var runTime: Long = 0 var runTime: Long = 0
var sharedState: SharedState = SharedState()
val runStateFlow: MutableStateFlow<RunState> = MutableStateFlow(RunState.STOP) val runStateFlow: MutableStateFlow<RunState> = MutableStateFlow(RunState.STOP)
var flutterEngine: FlutterEngine? = null var flutterEngine: FlutterEngine? = null
var serviceFlutterEngine: FlutterEngine? = null
val appPlugin: AppPlugin? val appPlugin: AppPlugin?
get() = flutterEngine?.plugin<AppPlugin>() get() = flutterEngine?.plugin<AppPlugin>() ?: serviceFlutterEngine?.plugin<AppPlugin>()
val servicePlugin: ServicePlugin?
get() = flutterEngine?.plugin<ServicePlugin>()
?: serviceFlutterEngine?.plugin<ServicePlugin>()
val tilePlugin: TilePlugin? val tilePlugin: TilePlugin?
get() = flutterEngine?.plugin<TilePlugin>() get() = flutterEngine?.plugin<TilePlugin>() ?: serviceFlutterEngine?.plugin<TilePlugin>()
suspend fun handleToggleAction() { suspend fun handleToggleAction() {
var action: (suspend () -> Unit)? var action: (suspend () -> Unit)?
@@ -50,17 +54,13 @@ object State {
suspend fun handleSyncState() { suspend fun handleSyncState() {
runLock.withLock { runLock.withLock {
try { Service.bind()
Service.bind() runTime = Service.getRunTime()
runTime = Service.getRunTime() val runState = when (runTime == 0L) {
val runState = when (runTime == 0L) { true -> RunState.STOP
true -> RunState.STOP false -> RunState.START
false -> RunState.START
}
runStateFlow.tryEmit(runState)
} catch (_: Exception) {
runStateFlow.tryEmit(RunState.STOP)
} }
runStateFlow.tryEmit(runState)
} }
} }
@@ -73,7 +73,7 @@ object State {
if (flutterEngine != null) { if (flutterEngine != null) {
return return
} }
startServiceWithPref() startServiceWithEngine()
} }
} }
@@ -84,10 +84,9 @@ object State {
return return
} }
tilePlugin?.handleStop() tilePlugin?.handleStop()
if (flutterEngine != null) { if (flutterEngine != null || serviceFlutterEngine != null) {
return return
} }
GlobalState.application.showToast(sharedState.stopTip)
handleStopService() handleStopService()
} }
} }
@@ -103,101 +102,75 @@ object State {
startService() startService()
} }
private fun startServiceWithPref() { fun handleStopService() {
GlobalState.launch {
runLock.withLock {
if (runStateFlow.value != RunState.START) {
return@launch
}
runStateFlow.tryEmit(RunState.PENDING)
runTime = Service.stopService()
runStateFlow.tryEmit(RunState.STOP)
}
destroyServiceEngine()
}
}
suspend fun destroyServiceEngine() {
runLock.withLock {
GlobalState.log("Destroy service engine")
withContext(Dispatchers.Main) {
runCatching {
serviceFlutterEngine?.destroy()
serviceFlutterEngine = null
}
}
}
}
private fun startServiceWithEngine() {
GlobalState.launch { GlobalState.launch {
runLock.withLock { runLock.withLock {
if (runStateFlow.value != RunState.STOP) { if (runStateFlow.value != RunState.STOP) {
return@launch return@launch
} }
sharedState = GlobalState.application.sharedState GlobalState.log("Create service engine")
setupAndStart() withContext(Dispatchers.Main) {
serviceFlutterEngine?.destroy()
serviceFlutterEngine = FlutterEngine(GlobalState.application)
serviceFlutterEngine?.plugins?.add(ServicePlugin())
serviceFlutterEngine?.plugins?.add(AppPlugin())
serviceFlutterEngine?.plugins?.add(TilePlugin())
val dartEntrypoint = DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(), "_service"
)
serviceFlutterEngine?.dartExecutor?.executeDartEntrypoint(dartEntrypoint)
}
} }
} }
} }
suspend fun syncState() {
GlobalState.setCrashlytics(sharedState.crashlytics)
Service.updateNotificationParams(
NotificationParams(
title = sharedState.currentProfileName,
stopText = sharedState.stopText,
onlyStatisticsProxy = sharedState.onlyStatisticsProxy
)
)
Service.setCrashlytics(sharedState.crashlytics)
}
private suspend fun setupAndStart() {
Service.bind()
syncState()
GlobalState.application.showToast(sharedState.startTip)
val initParams = mutableMapOf<String, Any>()
initParams["home-dir"] = GlobalState.application.filesDir.path
initParams["version"] = android.os.Build.VERSION.SDK_INT
val initParamsString = Gson().toJson(initParams)
val setupParamsString = Gson().toJson(sharedState.setupParams)
Service.quickSetup(
initParamsString,
setupParamsString,
onStarted = {
startService()
},
onResult = {
if (it.isNotEmpty()) {
GlobalState.application.showToast(it)
}
},
)
}
private fun startService() { private fun startService() {
GlobalState.launch { GlobalState.launch {
runLock.withLock { runLock.withLock {
if (runStateFlow.value != RunState.STOP) { if (runStateFlow.value != RunState.STOP) {
return@launch return@launch
} }
try { runStateFlow.tryEmit(RunState.PENDING)
runStateFlow.tryEmit(RunState.PENDING) if (servicePlugin == null) {
val options = sharedState.vpnOptions ?: return@launch
appPlugin?.let {
it.prepare(options.enable) {
runTime = Service.startService(options, runTime)
runStateFlow.tryEmit(RunState.START)
}
} ?: run {
val intent = VpnService.prepare(GlobalState.application)
if (intent != null) {
return@launch
}
runTime = Service.startService(options, runTime)
runStateFlow.tryEmit(RunState.START)
}
} finally {
if (runStateFlow.value == RunState.PENDING) {
runStateFlow.tryEmit(RunState.STOP)
}
}
}
}
}
fun handleStopService() {
GlobalState.launch {
runLock.withLock {
if (runStateFlow.value != RunState.START) {
return@launch return@launch
} }
try { val options = servicePlugin?.handleGetVpnOptions()
runStateFlow.tryEmit(RunState.PENDING) if (options == null) {
runTime = Service.stopService() return@launch
runStateFlow.tryEmit(RunState.STOP) }
} finally { appPlugin?.prepare(options.enable) {
if (runStateFlow.value == RunState.PENDING) { runTime = Service.startService(options, runTime)
runStateFlow.tryEmit(RunState.START) runStateFlow.tryEmit(RunState.START)
}
} }
} }
} }
} }
} }

View File

@@ -1,22 +1,9 @@
package com.follow.clash.models package com.follow.clash.models
import com.follow.clash.service.models.VpnOptions
import com.google.gson.annotations.SerializedName
data class SharedState( data class AppState(
val startTip: String = "Starting VPN...",
val stopTip: String = "Stopping VPN...",
val crashlytics: Boolean = true, val crashlytics: Boolean = true,
val currentProfileName: String = "FlClash", val currentProfileName: String = "FlClash",
val stopText: String = "Stop", val stopText: String = "Stop",
val onlyStatisticsProxy: Boolean = false, val onlyStatisticsProxy: Boolean = false,
val vpnOptions: VpnOptions? = null,
val setupParams: SetupParams? = null,
)
data class SetupParams(
@SerializedName("test-url")
val testUrl: String,
@SerializedName("selected-map")
val selectedMap: Map<String, String>,
) )

View File

@@ -9,6 +9,7 @@ import android.content.pm.ComponentInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.VpnService import android.net.VpnService
import android.os.Build import android.os.Build
import android.widget.Toast
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
@@ -23,7 +24,6 @@ import com.follow.clash.common.QuickAction
import com.follow.clash.common.quickIntent import com.follow.clash.common.quickIntent
import com.follow.clash.getPackageIconPath import com.follow.clash.getPackageIconPath
import com.follow.clash.models.Package import com.follow.clash.models.Package
import com.follow.clash.showToast
import com.google.gson.Gson import com.google.gson.Gson
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
@@ -193,7 +193,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
} }
private fun tip(message: String?) { private fun tip(message: String?) {
GlobalState.application.showToast(message) Toast.makeText(GlobalState.application, message, Toast.LENGTH_LONG).show()
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")

View File

@@ -3,9 +3,13 @@ package com.follow.clash.plugins
import com.follow.clash.RunState import com.follow.clash.RunState
import com.follow.clash.Service import com.follow.clash.Service
import com.follow.clash.State import com.follow.clash.State
import com.follow.clash.awaitResult
import com.follow.clash.common.Components import com.follow.clash.common.Components
import com.follow.clash.common.GlobalState
import com.follow.clash.invokeMethodOnMainThread import com.follow.clash.invokeMethodOnMainThread
import com.follow.clash.models.SharedState import com.follow.clash.models.AppState
import com.follow.clash.service.models.NotificationParams
import com.follow.clash.service.models.VpnOptions
import com.google.gson.Gson import com.google.gson.Gson
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
@@ -34,7 +38,7 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) {
"init" -> { "init" -> {
handleInit(result) handleInit(call, result)
} }
"shutdown" -> { "shutdown" -> {
@@ -90,6 +94,11 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
result.success(true) result.success(true)
} }
suspend fun handleGetVpnOptions(): VpnOptions? {
val res = flutterMethodChannel.awaitResult<String>("getVpnOptions", null)
return Gson().fromJson(res, VpnOptions::class.java)
}
val semaphore = Semaphore(10) val semaphore = Semaphore(10)
fun handleSendEvent(value: String?) { fun handleSendEvent(value: String?) {
@@ -107,19 +116,31 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
private fun handleSyncState(call: MethodCall, result: MethodChannel.Result) { private fun handleSyncState(call: MethodCall, result: MethodChannel.Result) {
val data = call.arguments<String>()!! val data = call.arguments<String>()!!
State.sharedState = Gson().fromJson(data, SharedState::class.java) val params = Gson().fromJson(data, AppState::class.java)
GlobalState.setCrashlytics(params.crashlytics)
launch { launch {
State.syncState() Service.updateNotificationParams(
NotificationParams(
title = params.currentProfileName,
stopText = params.stopText,
onlyStatisticsProxy = params.onlyStatisticsProxy
)
)
Service.setCrashlytics(params.crashlytics)
result.success("") result.success("")
} }
} }
fun handleInit(call: MethodCall, result: MethodChannel.Result) {
fun handleInit(result: MethodChannel.Result) {
Service.bind() Service.bind()
launch { launch {
Service.setEventListener { val needSetEventListener = call.arguments<Boolean>() ?: false
handleSendEvent(it) when (needSetEventListener) {
true -> Service.setEventListener {
handleSendEvent(it)
}
false -> Service.setEventListener(null)
}.onSuccess { }.onSuccess {
result.success("") result.success("")
}.onFailure { }.onFailure {

View File

@@ -1,25 +1,25 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp" android:width="108dp"
android:height="108dp" android:height="108dp"
android:viewportWidth="240" android:viewportWidth="240"
android:viewportHeight="240"> android:viewportHeight="240">
<group android:scaleX="0.924" <group android:scaleX="0.924"
android:scaleY="0.924" android:scaleY="0.924"
android:translateX="9.12" android:translateX="9.12"
android:translateY="9.12"> android:translateY="9.12">
<group android:scaleX="0.63461536" <group android:scaleX="0.63461536"
android:scaleY="0.63461536" android:scaleY="0.63461536"
android:translateX="45.96154" android:translateX="45.96154"
android:translateY="43.846153"> android:translateY="43.846153">
<path <path
android:pathData="M60.65,89.6L154.18,35.6A18,18 107.59,0 1,178.77 42.19L178.77,42.19A18,18 107.59,0 1,172.18 66.78L78.65,120.78A18,18 106.67,0 1,54.06 114.19L54.06,114.19A18,18 106.67,0 1,60.65 89.6z" android:pathData="M60.65,89.6L154.18,35.6A18,18 107.59,0 1,178.77 42.19L178.77,42.19A18,18 107.59,0 1,172.18 66.78L78.65,120.78A18,18 106.67,0 1,54.06 114.19L54.06,114.19A18,18 106.67,0 1,60.65 89.6z"
android:fillColor="#6666FB"/> android:fillColor="#6666FB"/>
<path <path
android:pathData="M84.65,131.17L131.42,104.17A18,18 107.83,0 1,156 110.76L156,110.76A18,18 107.83,0 1,149.42 135.35L102.65,162.35A18,18 106.67,0 1,78.06 155.76L78.06,155.76A18,18 106.67,0 1,84.65 131.17z" android:pathData="M84.65,131.17L131.42,104.17A18,18 107.83,0 1,156 110.76L156,110.76A18,18 107.83,0 1,149.42 135.35L102.65,162.35A18,18 106.67,0 1,78.06 155.76L78.06,155.76A18,18 106.67,0 1,84.65 131.17z"
android:fillColor="#336AB6"/> android:fillColor="#336AB6"/>
<path <path
android:pathData="M108.65,172.74L108.65,172.74A18,18 116.03,0 1,133.24 179.33L133.24,179.33A18,18 116.03,0 1,126.65 203.92L126.65,203.92A18,18 116.03,0 1,102.06 197.33L102.06,197.33A18,18 116.03,0 1,108.65 172.74z" android:pathData="M108.65,172.74L108.65,172.74A18,18 116.03,0 1,133.24 179.33L133.24,179.33A18,18 116.03,0 1,126.65 203.92L126.65,203.92A18,18 116.03,0 1,102.06 197.33L102.06,197.33A18,18 116.03,0 1,108.65 172.74z"
android:fillColor="#5CA8E9"/> android:fillColor="#5CA8E9"/>
</group>
</group> </group>
</group>
</vector> </vector>

View File

@@ -1,10 +1,19 @@
buildscript {
dependencies {
classpath(libs.build.kotlin)
}
}
plugins {
id("com.android.library") apply false
}
allprojects { allprojects {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
} }
val newBuildDir: Directory = val newBuildDir: Directory =
rootProject.layout.buildDirectory rootProject.layout.buildDirectory
.dir("../../build") .dir("../../build")
@@ -22,3 +31,4 @@ subprojects {
tasks.register<Delete>("clean") { tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory) delete(rootProject.layout.buildDirectory)
} }

View File

@@ -70,14 +70,6 @@ Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean su
suspend(suspended); suspend(suspended);
} }
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_quickSetup(JNIEnv *env, jobject thiz, jstring init_params_string,
jstring setup_params_string, jobject cb) {
const auto interface = new_global(cb);
quickSetup(interface, get_string(init_params_string), get_string(setup_params_string));
}
static jmethodID m_tun_interface_protect; static jmethodID m_tun_interface_protect;
static jmethodID m_tun_interface_resolve_process; static jmethodID m_tun_interface_resolve_process;
@@ -107,12 +99,12 @@ call_tun_interface_resolve_process_impl(void *tun_interface, const int protocol,
const int uid) { const int uid) {
ATTACH_JNI(); ATTACH_JNI();
const auto packageName = reinterpret_cast<jstring>(env->CallObjectMethod( const auto packageName = reinterpret_cast<jstring>(env->CallObjectMethod(
static_cast<jobject>(tun_interface), static_cast<jobject>(tun_interface),
m_tun_interface_resolve_process, m_tun_interface_resolve_process,
protocol, protocol,
new_string(source), new_string(source),
new_string(target), new_string(target),
uid)); uid));
return get_string(packageName); return get_string(packageName);
} }
@@ -199,10 +191,4 @@ extern "C"
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean suspended) { Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean suspended) {
} }
#endif
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_quickSetup(JNIEnv *env, jobject thiz, jstring init_params_string,
jstring setup_params_string, jobject cb) {
}
#endif

View File

@@ -102,28 +102,6 @@ data object Core {
} }
} }
fun quickSetup(
initParamsString: String,
setupParamsString: String,
cb: (result: String?) -> Unit,
) {
quickSetup(
initParamsString,
setupParamsString,
object : InvokeInterface {
override fun onResult(result: String?) {
cb(result)
}
},
)
}
private external fun quickSetup(
initParamsString: String,
setupParamsString: String,
cb: InvokeInterface
)
external fun stopTun() external fun stopTun()
external fun getTraffic(onlyStatisticsProxy: Boolean): String external fun getTraffic(onlyStatisticsProxy: Boolean): String

View File

@@ -4,13 +4,11 @@ package com.follow.clash.service;
import com.follow.clash.service.ICallbackInterface; import com.follow.clash.service.ICallbackInterface;
import com.follow.clash.service.IEventInterface; import com.follow.clash.service.IEventInterface;
import com.follow.clash.service.IResultInterface; import com.follow.clash.service.IResultInterface;
import com.follow.clash.service.IVoidInterface;
import com.follow.clash.service.models.VpnOptions; import com.follow.clash.service.models.VpnOptions;
import com.follow.clash.service.models.NotificationParams; import com.follow.clash.service.models.NotificationParams;
interface IRemoteInterface { interface IRemoteInterface {
void invokeAction(in String data, in ICallbackInterface callback); void invokeAction(in String data, in ICallbackInterface callback);
void quickSetup(in String initParamsString, in String setupParamsString, in ICallbackInterface callback, in IVoidInterface onStarted);
void updateNotificationParams(in NotificationParams params); void updateNotificationParams(in NotificationParams params);
void startService(in VpnOptions options, in long runTime, in IResultInterface result); void startService(in VpnOptions options, in long runTime, in IResultInterface result);
void stopService(in IResultInterface result); void stopService(in IResultInterface result);

View File

@@ -1,6 +0,0 @@
// IVoidInterface.aidl
package com.follow.clash.service;
interface IVoidInterface {
oneway void invoke();
}

View File

@@ -50,11 +50,7 @@ class CommonService : Service(), IBaseService,
} }
override fun start() { override fun start() {
try { loader.load()
loader.load()
} catch (_: Exception) {
stop()
}
} }
override fun stop() { override fun stop() {

View File

@@ -98,35 +98,6 @@ class RemoteService : Service(),
} }
} }
override fun quickSetup(
initParamsString: String,
setupParamsString: String,
callback: ICallbackInterface,
onStarted: IVoidInterface
) {
Core.quickSetup(initParamsString, setupParamsString) {
launch {
runCatching {
val chunks = it?.chunkedForAidl() ?: listOf()
for ((index, chunk) in chunks.withIndex()) {
suspendCancellableCoroutine { cont ->
callback.onResult(
chunk,
index == chunks.lastIndex,
object : IAckInterface.Stub() {
override fun onAck() {
cont.resume(Unit)
}
},
)
}
}
}
}
}
onStarted()
}
override fun updateNotificationParams(params: NotificationParams?) { override fun updateNotificationParams(params: NotificationParams?) {
State.notificationParamsFlow.tryEmit(params) State.notificationParamsFlow.tryEmit(params)
} }
@@ -137,7 +108,6 @@ class RemoteService : Service(),
runtime: Long, runtime: Long,
result: IResultInterface, result: IResultInterface,
) { ) {
GlobalState.log("remote startService")
State.options = options State.options = options
handleStartService(runtime, result) handleStartService(runtime, result)
} }

View File

@@ -187,7 +187,7 @@ class VpnService : SystemVpnService(), IBaseService,
addDnsServer(DNS6) addDnsServer(DNS6)
} }
setMtu(9000) setMtu(9000)
options.accessControlProps.let { accessControl -> options.accessControl.let { accessControl ->
if (accessControl.enable) { if (accessControl.enable) {
when (accessControl.mode) { when (accessControl.mode) {
AccessControlMode.ACCEPT_SELECTED -> { AccessControlMode.ACCEPT_SELECTED -> {
@@ -234,13 +234,9 @@ class VpnService : SystemVpnService(), IBaseService,
} }
override fun start() { override fun start() {
try { loader.load()
loader.load() State.options?.let {
State.options?.let { handleStart(it)
handleStart(it)
}
} catch (_: Exception) {
stop()
} }
} }

View File

@@ -6,7 +6,7 @@ import kotlinx.parcelize.Parcelize
import java.net.InetAddress import java.net.InetAddress
@Parcelize @Parcelize
data class AccessControlProps( data class AccessControl(
val enable: Boolean, val enable: Boolean,
val mode: AccessControlMode, val mode: AccessControlMode,
val acceptList: List<String>, val acceptList: List<String>,
@@ -19,7 +19,7 @@ data class VpnOptions(
val port: Int, val port: Int,
val ipv6: Boolean, val ipv6: Boolean,
val dnsHijacking: Boolean, val dnsHijacking: Boolean,
val accessControlProps: AccessControlProps, val accessControl: AccessControl,
val allowBypass: Boolean, val allowBypass: Boolean,
val systemProxy: Boolean, val systemProxy: Boolean,
val bypassDomain: List<String>, val bypassDomain: List<String>,

View File

@@ -1,12 +1,11 @@
pluginManagement { pluginManagement {
val flutterSdkPath = val flutterSdkPath = run {
run { val properties = java.util.Properties()
val properties = java.util.Properties() file("local.properties").inputStream().use { properties.load(it) }
file("local.properties").inputStream().use { properties.load(it) } val flutterSdkPath = properties.getProperty("flutter.sdk")
val flutterSdkPath = properties.getProperty("flutter.sdk") require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } flutterSdkPath
flutterSdkPath }
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")

View File

@@ -129,8 +129,14 @@
"compatibleDesc": "Opening it will lose part of its application ability and gain the support of full amount of Clash.", "compatibleDesc": "Opening it will lose part of its application ability and gain the support of full amount of Clash.",
"notSelectedTip": "The current proxy group cannot be selected.", "notSelectedTip": "The current proxy group cannot be selected.",
"tip": "tip", "tip": "tip",
"backupAndRecovery": "Backup and Recovery",
"backupAndRecoveryDesc": "Sync data via WebDAV or file",
"account": "Account", "account": "Account",
"backup": "Backup", "backup": "Backup",
"recovery": "Recovery",
"recoveryProfiles": "Only recovery profiles",
"recoveryAll": "Recovery all data",
"recoverySuccess": "Recovery success",
"backupSuccess": "Backup success", "backupSuccess": "Backup success",
"noInfo": "No info", "noInfo": "No info",
"pleaseBindWebDAV": "Please bind WebDAV", "pleaseBindWebDAV": "Please bind WebDAV",
@@ -213,7 +219,9 @@
"local": "Local", "local": "Local",
"remote": "Remote", "remote": "Remote",
"remoteBackupDesc": "Backup local data to WebDAV", "remoteBackupDesc": "Backup local data to WebDAV",
"remoteRecoveryDesc": "Recovery data from WebDAV",
"localBackupDesc": "Backup local data to local", "localBackupDesc": "Backup local data to local",
"localRecoveryDesc": "Recovery data from file",
"mode": "Mode", "mode": "Mode",
"time": "Time", "time": "Time",
"source": "Source", "source": "Source",
@@ -373,9 +381,9 @@
"systemApp": "System APP", "systemApp": "System APP",
"noNetworkApp": "No network APP", "noNetworkApp": "No network APP",
"contactMe": "Contact me", "contactMe": "Contact me",
"restoreStrategy": "Restore strategy", "recoveryStrategy": "Recovery strategy",
"restoreStrategy_override": "Override", "recoveryStrategy_override": "Override",
"restoreStrategy_compatible": "Compatible", "recoveryStrategy_compatible": "Compatible",
"logsTest": "Logs test", "logsTest": "Logs test",
"emptyTip": "{label} cannot be empty", "emptyTip": "{label} cannot be empty",
"urlTip": "{label} must be a url", "urlTip": "{label} must be a url",
@@ -441,7 +449,6 @@
"externalFetch": "External fetch", "externalFetch": "External fetch",
"confirmForceCrashCore": "Are you sure you want to force crash the core?", "confirmForceCrashCore": "Are you sure you want to force crash the core?",
"confirmClearAllData": "Are you sure you want to clear all data?", "confirmClearAllData": "Are you sure you want to clear all data?",
"loading": "Loading...",
"loadTest": "Load test", "loadTest": "Load test",
"yearsAgo": "{count, plural, =1{1 year ago} other{{count} years ago}}", "yearsAgo": "{count, plural, =1{1 year ago} other{{count} years ago}}",
"monthsAgo": "{count, plural, =1{1 month ago} other{{count} months ago}}", "monthsAgo": "{count, plural, =1{1 month ago} other{{count} months ago}}",
@@ -456,25 +463,5 @@
"coreConfigChangeDetected": "Core configuration change detected", "coreConfigChangeDetected": "Core configuration change detected",
"reload": "Reload", "reload": "Reload",
"vpnConfigChangeDetected": "VPN configuration change detected", "vpnConfigChangeDetected": "VPN configuration change detected",
"restart": "Restart", "restart": "Restart"
"speedStatistics": "Speed statistics",
"resetPageChangesTip": "The current page has changes. Are you sure you want to reset?",
"overwriteTypeCustom": "Custom",
"overwriteTypeCustomDesc": "Custom mode, fully customize proxy groups and rules",
"unknownNetworkError": "Unknown network error",
"networkRequestException": "Network request exception, please try again later.",
"restoreException": "Recovery exception",
"networkException": "Network exception, please check your connection and try again",
"invalidBackupFile": "Invalid backup file",
"pruneCache": "Prune cache",
"backupAndRestore": "Backup and Restore",
"backupAndRestoreDesc": "Sync data via WebDAV or files",
"restore": "Restore",
"restoreSuccess": "Restore success",
"restoreFromWebDAVDesc": "Restore data via WebDAV",
"restoreFromFileDesc": "Restore data via file",
"restoreOnlyConfig": "Restore configuration files only",
"restoreAllData": "Restore all data",
"addProfile": "Add Profile",
"delayTest": "Delay Test"
} }

View File

@@ -129,8 +129,14 @@
"compatibleDesc": "有効化すると一部機能を失いますが、Clashの完全サポートを獲得", "compatibleDesc": "有効化すると一部機能を失いますが、Clashの完全サポートを獲得",
"notSelectedTip": "現在のプロキシグループは選択できません", "notSelectedTip": "現在のプロキシグループは選択できません",
"tip": "ヒント", "tip": "ヒント",
"backupAndRecovery": "バックアップと復元",
"backupAndRecoveryDesc": "WebDAVまたはファイルでデータを同期",
"account": "アカウント", "account": "アカウント",
"backup": "バックアップ", "backup": "バックアップ",
"recovery": "復元",
"recoveryProfiles": "プロファイルのみ復元",
"recoveryAll": "全データ復元",
"recoverySuccess": "復元成功",
"backupSuccess": "バックアップ成功", "backupSuccess": "バックアップ成功",
"noInfo": "情報なし", "noInfo": "情報なし",
"pleaseBindWebDAV": "WebDAVをバインドしてください", "pleaseBindWebDAV": "WebDAVをバインドしてください",
@@ -213,7 +219,9 @@
"local": "ローカル", "local": "ローカル",
"remote": "リモート", "remote": "リモート",
"remoteBackupDesc": "WebDAVにデータをバックアップ", "remoteBackupDesc": "WebDAVにデータをバックアップ",
"remoteRecoveryDesc": "WebDAVからデータを復元",
"localBackupDesc": "ローカルにデータをバックアップ", "localBackupDesc": "ローカルにデータをバックアップ",
"localRecoveryDesc": "ファイルからデータを復元",
"mode": "モード", "mode": "モード",
"time": "時間", "time": "時間",
"source": "ソース", "source": "ソース",
@@ -374,9 +382,9 @@
"systemApp": "システムアプリ", "systemApp": "システムアプリ",
"noNetworkApp": "ネットワークなしアプリ", "noNetworkApp": "ネットワークなしアプリ",
"contactMe": "連絡する", "contactMe": "連絡する",
"restoreStrategy": "復元ストラテジー", "recoveryStrategy": "リカバリー戦略",
"restoreStrategy_override": "上書き", "recoveryStrategy_override": "オーバーライド",
"restoreStrategy_compatible": "互換", "recoveryStrategy_compatible": "互換",
"logsTest": "ログテスト", "logsTest": "ログテスト",
"emptyTip": "{label}は空欄にできません", "emptyTip": "{label}は空欄にできません",
"urlTip": "{label}はURLである必要があります", "urlTip": "{label}はURLである必要があります",
@@ -442,7 +450,6 @@
"externalFetch": "外部取得", "externalFetch": "外部取得",
"confirmForceCrashCore": "コアを強制的にクラッシュさせてもよろしいですか?", "confirmForceCrashCore": "コアを強制的にクラッシュさせてもよろしいですか?",
"confirmClearAllData": "すべてのデータをクリアしてもよろしいですか?", "confirmClearAllData": "すべてのデータをクリアしてもよろしいですか?",
"loading": "読み込み中...",
"loadTest": "読み込みテスト", "loadTest": "読み込みテスト",
"yearsAgo": "{count}年前", "yearsAgo": "{count}年前",
"monthsAgo": "{count}ヶ月前", "monthsAgo": "{count}ヶ月前",
@@ -457,25 +464,5 @@
"coreConfigChangeDetected": "コア設定の変更が検出されました", "coreConfigChangeDetected": "コア設定の変更が検出されました",
"reload": "リロード", "reload": "リロード",
"vpnConfigChangeDetected": "VPN設定の変更が検出されました", "vpnConfigChangeDetected": "VPN設定の変更が検出されました",
"restart": "再起動", "restart": "再起動"
"speedStatistics": "速度統計",
"resetPageChangesTip": "現在のページに変更があります。リセットしてもよろしいですか?",
"overwriteTypeCustom": "カスタム",
"overwriteTypeCustomDesc": "カスタムモード、プロキシグループとルールを完全にカスタマイズ可能",
"unknownNetworkError": "不明なネットワークエラー",
"networkRequestException": "ネットワーク要求例外、後でもう一度試してください。",
"restoreException": "復元例外",
"networkException": "ネットワーク例外、接続を確認してもう一度お試しください",
"invalidBackupFile": "無効なバックアップファイル",
"pruneCache": "キャッシュの削除",
"backupAndRestore": "バックアップと復元",
"backupAndRestoreDesc": "WebDAVまたはファイルを介してデータを同期する",
"restore": "復元",
"restoreSuccess": "復元に成功しました",
"restoreFromWebDAVDesc": "WebDAVを介してデータを復元する",
"restoreFromFileDesc": "ファイルを介してデータを復元する",
"restoreOnlyConfig": "設定ファイルのみを復元する",
"restoreAllData": "すべてのデータを復元する",
"addProfile": "プロファイルを追加",
"delayTest": "遅延テスト"
} }

View File

@@ -382,9 +382,9 @@
"systemApp": "Системное приложение", "systemApp": "Системное приложение",
"noNetworkApp": "Приложение без сети", "noNetworkApp": "Приложение без сети",
"contactMe": "Свяжитесь со мной", "contactMe": "Свяжитесь со мной",
"restoreStrategy": "Стратегия восстановления", "recoveryStrategy": "Стратегия восстановления",
"restoreStrategy_override": "Перезаписать", "recoveryStrategy_override": "Переопределение",
"restoreStrategy_compatible": "Совместимый", "recoveryStrategy_compatible": "Совместимый",
"logsTest": "Тест журналов", "logsTest": "Тест журналов",
"emptyTip": "{label} не может быть пустым", "emptyTip": "{label} не может быть пустым",
"urlTip": "{label} должен быть URL", "urlTip": "{label} должен быть URL",
@@ -450,7 +450,6 @@
"externalFetch": "Внешнее получение", "externalFetch": "Внешнее получение",
"confirmForceCrashCore": "Вы уверены, что хотите принудительно аварийно завершить работу ядра?", "confirmForceCrashCore": "Вы уверены, что хотите принудительно аварийно завершить работу ядра?",
"confirmClearAllData": "Вы уверены, что хотите очистить все данные?", "confirmClearAllData": "Вы уверены, что хотите очистить все данные?",
"loading": "Загрузка...",
"loadTest": "Тест загрузки", "loadTest": "Тест загрузки",
"yearsAgo": "{count, plural, one{{count} год назад} few{{count} года назад} many{{count} лет назад} other{{count} года назад}}", "yearsAgo": "{count, plural, one{{count} год назад} few{{count} года назад} many{{count} лет назад} other{{count} года назад}}",
"monthsAgo": "{count, plural, one{{count} месяц назад} few{{count} месяца назад} many{{count} месяцев назад} other{{count} месяца назад}}", "monthsAgo": "{count, plural, one{{count} месяц назад} few{{count} месяца назад} many{{count} месяцев назад} other{{count} месяца назад}}",
@@ -465,25 +464,5 @@
"coreConfigChangeDetected": "Обнаружено изменение конфигурации ядра", "coreConfigChangeDetected": "Обнаружено изменение конфигурации ядра",
"reload": "Перезагрузить", "reload": "Перезагрузить",
"vpnConfigChangeDetected": "Обнаружено изменение конфигурации VPN", "vpnConfigChangeDetected": "Обнаружено изменение конфигурации VPN",
"restart": "Перезапустить", "restart": "Перезапустить"
"speedStatistics": "Статистика скорости",
"resetPageChangesTip": "На текущей странице есть изменения. Вы уверены, что хотите сбросить?",
"overwriteTypeCustom": "Пользовательский",
"overwriteTypeCustomDesc": "Пользовательский режим, полная настройка групп прокси и правил",
"unknownNetworkError": "Неизвестная сетевая ошибка",
"networkRequestException": "Исключение сетевого запроса, пожалуйста, попробуйте позже.",
"restoreException": "Ошибка восстановления",
"networkException": "Ошибка сети, проверьте соединение и попробуйте еще раз",
"invalidBackupFile": "Неверный файл резервной копии",
"pruneCache": "Очистить кэш",
"backupAndRestore": "Резервное копирование и восстановление",
"backupAndRestoreDesc": "Синхронизация данных через WebDAV или файлы",
"restore": "Восстановить",
"restoreSuccess": "Восстановление успешно",
"restoreFromWebDAVDesc": "Восстановить данные через WebDAV",
"restoreFromFileDesc": "Восстановить данные из файла",
"restoreOnlyConfig": "Восстановить только файлы конфигурации",
"restoreAllData": "Восстановить все данные",
"addProfile": "Добавить профиль",
"delayTest": "Тест задержки"
} }

View File

@@ -129,8 +129,14 @@
"compatibleDesc": "开启将失去部分应用能力获得全量的Clash的支持", "compatibleDesc": "开启将失去部分应用能力获得全量的Clash的支持",
"notSelectedTip": "当前代理组无法选中", "notSelectedTip": "当前代理组无法选中",
"tip": "提示", "tip": "提示",
"backupAndRecovery": "备份与恢复",
"backupAndRecoveryDesc": "通过WebDAV或者文件同步数据",
"account": "账号", "account": "账号",
"backup": "备份", "backup": "备份",
"recovery": "恢复",
"recoveryProfiles": "仅恢复配置文件",
"recoveryAll": "恢复所有数据",
"recoverySuccess": "恢复成功",
"backupSuccess": "备份成功", "backupSuccess": "备份成功",
"noInfo": "暂无信息", "noInfo": "暂无信息",
"pleaseBindWebDAV": "请绑定WebDAV", "pleaseBindWebDAV": "请绑定WebDAV",
@@ -213,7 +219,9 @@
"local": "本地", "local": "本地",
"remote": "远程", "remote": "远程",
"remoteBackupDesc": "备份数据到WebDAV", "remoteBackupDesc": "备份数据到WebDAV",
"remoteRecoveryDesc": "通过WebDAV恢复数据",
"localBackupDesc": "备份数据到本地", "localBackupDesc": "备份数据到本地",
"localRecoveryDesc": "通过文件恢复数据",
"mode": "模式", "mode": "模式",
"time": "时间", "time": "时间",
"source": "来源", "source": "来源",
@@ -374,9 +382,9 @@
"systemApp": "系统应用", "systemApp": "系统应用",
"noNetworkApp": "无网络应用", "noNetworkApp": "无网络应用",
"contactMe": "联系我", "contactMe": "联系我",
"restoreStrategy": "恢复策略", "recoveryStrategy": "恢复策略",
"restoreStrategy_override": "覆盖", "recoveryStrategy_override": "覆盖",
"restoreStrategy_compatible": "兼容", "recoveryStrategy_compatible": "兼容",
"logsTest": "日志测试", "logsTest": "日志测试",
"emptyTip": "{label}不能为空", "emptyTip": "{label}不能为空",
"urlTip": "{label}必须为URL", "urlTip": "{label}必须为URL",
@@ -442,7 +450,6 @@
"externalFetch": "外部获取", "externalFetch": "外部获取",
"confirmForceCrashCore": "确定要强制崩溃核心?", "confirmForceCrashCore": "确定要强制崩溃核心?",
"confirmClearAllData": "确定要清除所有数据?", "confirmClearAllData": "确定要清除所有数据?",
"loading": "加载中...",
"loadTest": "加载测试", "loadTest": "加载测试",
"yearsAgo": "{count} 年前", "yearsAgo": "{count} 年前",
"monthsAgo": "{count} 个月前", "monthsAgo": "{count} 个月前",
@@ -457,25 +464,5 @@
"coreConfigChangeDetected": "检测到核心配置更改", "coreConfigChangeDetected": "检测到核心配置更改",
"reload": "重载", "reload": "重载",
"vpnConfigChangeDetected": "检测到VPN相关配置改动", "vpnConfigChangeDetected": "检测到VPN相关配置改动",
"restart": "重启", "restart": "重启"
"speedStatistics": "网速统计",
"resetPageChangesTip": "当前页面存在更改,确定重置吗?",
"overwriteTypeCustom": "自定义",
"overwriteTypeCustomDesc": "自定义模式,支持完全自定义修改代理组以及规则",
"unknownNetworkError": "未知网络错误",
"networkRequestException": "网络请求异常,请稍后再试。",
"restoreException": "恢复异常",
"networkException": "网络异常,请检查连接后重试",
"invalidBackupFile": "无效备份文件",
"pruneCache": "修剪缓存",
"backupAndRestore": "备份与恢复",
"backupAndRestoreDesc": "通过WebDAV或者文件同步数据",
"restore": "恢复",
"restoreSuccess": "恢复成功",
"restoreFromWebDAVDesc": "通过WebDAV恢复数据",
"restoreFromFileDesc": "通过文件恢复数据",
"restoreOnlyConfig": "仅恢复配置文件",
"restoreAllData": "恢复所有数据",
"addProfile": "添加配置",
"delayTest": "延迟测试"
} }

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -6,7 +6,6 @@ targets:
build_extensions: build_extensions:
'^lib/models/{{}}.dart': 'lib/models/generated/{{}}.g.dart' '^lib/models/{{}}.dart': 'lib/models/generated/{{}}.g.dart'
'^lib/providers/{{}}.dart': 'lib/providers/generated/{{}}.g.dart' '^lib/providers/{{}}.dart': 'lib/providers/generated/{{}}.g.dart'
'^lib/database/{{}}.dart': 'lib/database/generated/{{}}.g.dart'
freezed: freezed:
options: options:
build_extensions: build_extensions:

View File

@@ -37,6 +37,12 @@ var (
mBatch, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50)) mBatch, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
) )
type ExternalProviders []ExternalProvider
func (a ExternalProviders) Len() int { return len(a) }
func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name }
func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func getExternalProvidersRaw() map[string]cp.Provider { func getExternalProvidersRaw() map[string]cp.Provider {
eps := make(map[string]cp.Provider) eps := make(map[string]cp.Provider)
for n, p := range tunnel.Providers() { for n, p := range tunnel.Providers() {
@@ -235,8 +241,25 @@ func updateConfig(params *UpdateParams) {
updateListeners() updateListeners()
} }
func applyConfig(params *SetupParams) error { func parseWithPath(path string) (*config.Config, error) {
runtime.GC() buf, err := readFile(path)
if err != nil {
return nil, err
}
rawConfig := config.DefaultRawConfig()
err = UnmarshalJson(buf, rawConfig)
if err != nil {
return nil, err
}
parseRawConfig, err := config.ParseRawConfig(rawConfig)
if err != nil {
return nil, err
}
return parseRawConfig, nil
}
func setupConfig(params *SetupParams) error {
runLock.Lock() runLock.Lock()
defer runLock.Unlock() defer runLock.Unlock()
var err error var err error
@@ -248,6 +271,7 @@ func applyConfig(params *SetupParams) error {
hub.ApplyConfig(currentConfig) hub.ApplyConfig(currentConfig)
patchSelectGroup(params.SelectedMap) patchSelectGroup(params.SelectedMap)
updateListeners() updateListeners()
runtime.GC()
return err return err
} }

View File

@@ -66,11 +66,6 @@ type ExternalProvider struct {
SubscriptionInfo *provider.SubscriptionInfo `json:"subscription-info"` SubscriptionInfo *provider.SubscriptionInfo `json:"subscription-info"`
} }
type ProxiesData struct {
Proxies map[string]constant.Proxy `json:"proxies"`
All []string `json:"all"`
}
const ( const (
messageMethod Method = "message" messageMethod Method = "message"
initClashMethod Method = "initClash" initClashMethod Method = "initClash"

View File

@@ -10,7 +10,6 @@ require (
) )
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/RyuaNerin/go-krypto v1.3.0 // indirect github.com/RyuaNerin/go-krypto v1.3.0 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/ajg/form v1.5.1 // indirect github.com/ajg/form v1.5.1 // indirect
@@ -19,14 +18,18 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect
github.com/coreos/go-iptables v0.8.0 // indirect github.com/coreos/go-iptables v0.8.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/enfein/mieru/v3 v3.26.2 // indirect github.com/ebitengine/purego v0.9.1 // indirect
github.com/enfein/mieru/v3 v3.22.1 // indirect
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-chi/chi/v5 v5.2.3 // indirect
github.com/go-chi/render v1.0.3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect github.com/gobwas/ws v1.4.0 // indirect
@@ -34,7 +37,7 @@ require (
github.com/golang/snappy v1.0.0 // indirect github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/compress v1.17.9 // indirect
@@ -49,42 +52,37 @@ require (
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b // indirect github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b // indirect
github.com/metacubex/blake3 v0.1.0 // indirect github.com/metacubex/blake3 v0.1.0 // indirect
github.com/metacubex/chacha v0.1.5 // indirect github.com/metacubex/chacha v0.1.5 // indirect
github.com/metacubex/chi v0.1.0 // indirect
github.com/metacubex/cpu v0.1.0 // indirect
github.com/metacubex/fswatch v0.1.1 // indirect github.com/metacubex/fswatch v0.1.1 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 // indirect github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect
github.com/metacubex/hkdf v0.1.0 // indirect github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be // indirect
github.com/metacubex/hpke v0.1.0 // indirect
github.com/metacubex/http v0.1.0 // indirect
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 // indirect
github.com/metacubex/mlkem v0.1.0 // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
github.com/metacubex/qpack v0.6.0 // indirect github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 // indirect
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001 // indirect
github.com/metacubex/randv2 v0.2.0 // indirect github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/restls-client-go v0.1.7 // indirect github.com/metacubex/restls-client-go v0.1.7 // indirect
github.com/metacubex/sing v0.5.6 // indirect github.com/metacubex/sing v0.5.6 // indirect
github.com/metacubex/sing-mux v0.3.4 // indirect github.com/metacubex/sing-mux v0.3.4 // indirect
github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e // indirect github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb // indirect
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
github.com/metacubex/sing-tun v0.4.11 // indirect github.com/metacubex/sing-tun v0.4.9 // indirect
github.com/metacubex/sing-vmess v0.2.4 // indirect github.com/metacubex/sing-vmess v0.2.4 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 // indirect github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 // indirect
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // indirect github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 // indirect
github.com/metacubex/tls v0.1.1 // indirect github.com/metacubex/utls v1.8.3 // indirect
github.com/metacubex/utls v1.8.4 // indirect
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
github.com/miekg/dns v1.1.63 // indirect github.com/miekg/dns v1.1.63 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect github.com/mroth/weightedrand/v2 v2.1.0 // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/openacid/low v0.1.21 // indirect github.com/openacid/low v0.1.21 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/sagernet/cors v1.2.1 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/samber/lo v1.52.0 // indirect github.com/samber/lo v1.52.0 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
@@ -105,7 +103,7 @@ require (
golang.org/x/net v0.35.0 // indirect golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.10.0 // indirect golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.24.0 // indirect golang.org/x/tools v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@@ -1,5 +1,3 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg= github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg=
github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM= github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss= github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss=
@@ -14,6 +12,9 @@ github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xW
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc= github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -21,8 +22,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/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 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/enfein/mieru/v3 v3.26.2 h1:U/2XJc+3vrJD9r815FoFdwToQFEcqSOzzzWIPPhjfEU= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/enfein/mieru/v3 v3.26.2/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/enfein/mieru/v3 v3.22.1 h1:/XGYYXpEhEJlxosmtbpEJkhtRLHB8IToG7LB8kU2ZDY=
github.com/enfein/mieru/v3 v3.22.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/aegis v0.0.0-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 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
@@ -36,8 +39,15 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/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 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
@@ -47,15 +57,17 @@ github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakr
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I= github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
@@ -86,62 +98,47 @@ github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cq
github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk= github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk=
github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M= github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M=
github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
github.com/metacubex/chi v0.1.0 h1:rjNDyDj50nRpicG43CNkIw4ssiCbmDL8d7wJXKlUCsg=
github.com/metacubex/chi v0.1.0/go.mod h1:zM5u5oMQt8b2DjvDHvzadKrP6B2ztmasL1YHRMbVV+g=
github.com/metacubex/cpu v0.1.0 h1:8PeTdV9j6UKbN1K5Jvtbi/Jock7dknvzyYuLb8Conmk=
github.com/metacubex/cpu v0.1.0/go.mod h1:09VEt4dSRLR+bOA8l4w4NDuzGZ8n5dkMv7e8axgEeTU=
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU= github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI= 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 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 h1:hUL81H0Ic/XIDkvtn9M1pmfDdfid7JzYQToY4Ps1TvQ= github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ=
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU= github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA= github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be h1:Y7SigZIqfv/+RIA/D7R6EbB9p+brPRoGOM6zobSmRIM=
github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4= github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I=
github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
github.com/metacubex/qpack v0.6.0 h1:YqClGIMOpiRYLjV1qOs483Od08MdPgRnHjt90FuaAKw= github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 h1:I1uvJl206/HbkzEAZpLgGkZgUveOZb+P+6oTUj7dN+o=
github.com/metacubex/qpack v0.6.0/go.mod h1:lKGSi7Xk94IMvHGOmxS9eIei3bvIqpOAImEBsaOwTkA= github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c=
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001 h1:RlT3bFCIDM/NR9GWaDbFCrweOwpHRfgaT9c0zuRlPhY=
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001/go.mod h1:oNzMrmylS897M3zSMuapIdwSwfq6F2qW01Z3NhVRJhk=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k= github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g= github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.6 h1:mEPDCadsCj3DB8gn+t/EtposlYuALEkExa/LUguw6/c= github.com/metacubex/sing v0.5.6 h1:mEPDCadsCj3DB8gn+t/EtposlYuALEkExa/LUguw6/c=
github.com/metacubex/sing v0.5.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= github.com/metacubex/sing v0.5.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0= github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0=
github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4= github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4=
github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e h1:MLxp42z9Jd6LtY2suyawnl24oNzIsFxWc15bNeDIGxA= github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb h1:gxrJmnxuEAel+kh3V7ntqkHjURif0xKDu76nzr/BF5Y=
github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e/go.mod h1:+lgKTd52xAarGtqugALISShyw4KxnoEpYe2u0zJh26w= github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb/go.mod h1:JK4+PYUKps6pnlicKjsSUAjAcvIUjhorIjdNZGg930M=
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE= github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU= github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A= github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE= github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 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-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.11 h1:NG5zpvYPbBXf+9GSUmDaGCDwl3hZXV677tbRAw0QtCM= github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU=
github.com/metacubex/sing-tun v0.4.11/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I= github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I=
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2BhiAPgbJygiWhesPlfGmF+9Vw6ARdk= github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM=
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg= github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o= github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU=
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/tls v0.1.1 h1:BEcZrsPTTfNf4sKZ02EbZodv4UIj7fgHWa1Eqo12Bc0= github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4=
github.com/metacubex/tls v0.1.1/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM= github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk= github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4= github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E= github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E=
@@ -152,6 +149,9 @@ github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0= github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo= github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0= github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
@@ -164,6 +164,10 @@ github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
@@ -177,9 +181,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 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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.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.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.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.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
@@ -223,20 +234,22 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=

View File

@@ -1,7 +1,6 @@
package main package main
import ( import (
"cmp"
"context" "context"
"encoding/json" "encoding/json"
"github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter"
@@ -20,11 +19,11 @@ import (
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel" "github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic" "github.com/metacubex/mihomo/tunnel/statistic"
"golang.org/x/exp/slices"
"net" "net"
"os" "os"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"sort"
"strconv" "strconv"
"time" "time"
) )
@@ -44,8 +43,10 @@ func handleInitClash(paramsString string) bool {
return false return false
} }
version = params.Version version = params.Version
constant.SetHomeDir(params.HomeDir) if !isInit {
isInit = true constant.SetHomeDir(params.HomeDir)
isInit = true
}
return isInit return isInit
} }
@@ -96,52 +97,10 @@ func handleValidateConfig(path string) string {
return "" return ""
} }
func handleGetProxies() ProxiesData { func handleGetProxies() map[string]constant.Proxy {
runLock.Lock() runLock.Lock()
defer runLock.Unlock() defer runLock.Unlock()
return tunnel.ProxiesWithProviders()
nameList := config.GetProxyNameList()
proxies := make(map[string]constant.Proxy)
for name, proxy := range tunnel.Proxies() {
proxies[name] = proxy
}
for _, p := range tunnel.Providers() {
for _, proxy := range p.Proxies() {
proxies[proxy.Name()] = proxy
}
}
hasGlobal := false
allNames := make([]string, 0, len(nameList)+1)
for _, name := range nameList {
if name == "GLOBAL" {
hasGlobal = true
}
p, ok := proxies[name]
if !ok || p == nil {
continue
}
switch p.Type() {
case constant.Selector, constant.URLTest, constant.Fallback, constant.Relay, constant.LoadBalance:
allNames = append(allNames, name)
default:
}
}
if !hasGlobal {
if p, ok := proxies["GLOBAL"]; ok && p != nil {
allNames = append([]string{"GLOBAL"}, allNames...)
}
}
return ProxiesData{
All: allNames,
Proxies: proxies,
}
} }
func handleChangeProxy(data string, fn func(string string)) { func handleChangeProxy(data string, fn func(string string)) {
@@ -184,7 +143,7 @@ func handleChangeProxy(data string, fn func(string string)) {
} }
func handleGetTraffic(onlyStatisticsProxy bool) string { func handleGetTraffic(onlyStatisticsProxy bool) string {
up, down := statistic.DefaultManager.NowTraffic(onlyStatisticsProxy) up, down := statistic.DefaultManager.Current(onlyStatisticsProxy)
traffic := map[string]int64{ traffic := map[string]int64{
"up": up, "up": up,
"down": down, "down": down,
@@ -198,7 +157,7 @@ func handleGetTraffic(onlyStatisticsProxy bool) string {
} }
func handleGetTotalTraffic(onlyStatisticsProxy bool) string { func handleGetTotalTraffic(onlyStatisticsProxy bool) string {
up, down := statistic.DefaultManager.TotalTraffic(onlyStatisticsProxy) up, down := statistic.DefaultManager.Total(onlyStatisticsProxy)
traffic := map[string]int64{ traffic := map[string]int64{
"up": up, "up": up,
"down": down, "down": down,
@@ -328,9 +287,7 @@ func handleGetExternalProviders() string {
} }
eps = append(eps, *externalProvider) eps = append(eps, *externalProvider)
} }
slices.SortFunc(eps, func(a, b ExternalProvider) int { sort.Sort(ExternalProviders(eps))
return cmp.Compare(a.Name, b.Name)
})
data, err := json.Marshal(eps) data, err := json.Marshal(eps)
if err != nil { if err != nil {
return "" return ""
@@ -532,17 +489,14 @@ func handleDelFile(path string, result ActionResult) {
} }
func handleSetupConfig(bytes []byte) string { func handleSetupConfig(bytes []byte) string {
if !isInit {
return "not initialized"
}
var params = defaultSetupParams() var params = defaultSetupParams()
err := UnmarshalJson(bytes, params) err := UnmarshalJson(bytes, params)
if err != nil { if err != nil {
log.Errorln("unmarshalRawConfig error %v", err) log.Errorln("unmarshalRawConfig error %v", err)
_ = applyConfig(defaultSetupParams()) _ = setupConfig(defaultSetupParams())
return err.Error() return err.Error()
} }
err = applyConfig(params) err = setupConfig(params)
if err != nil { if err != nil {
return err.Error() return err.Error()
} }

View File

@@ -201,29 +201,9 @@ func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
//export startTUN //export startTUN
func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool { func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool {
handleStartTun(callback, int(fd), takeCString(stackChar), takeCString(addressChar), takeCString(dnsChar)) handleStartTun(callback, int(fd), takeCString(stackChar), takeCString(addressChar), takeCString(dnsChar))
if !isRunning {
handleStartListener()
} else {
handleResetConnections()
}
return true return true
} }
//export quickSetup
func quickSetup(callback unsafe.Pointer, initParamsChar *C.char, setupParamsChar *C.char) {
go func() {
initParamsString := takeCString(initParamsChar)
setupParamsString := takeCString(setupParamsChar)
if !handleInitClash(initParamsString) {
invokeResult(callback, "init failed")
return
}
isRunning = true
message := handleSetupConfig([]byte(setupParamsString))
invokeResult(callback, message)
}()
}
//export setEventListener //export setEventListener
func setEventListener(listener unsafe.Pointer) { func setEventListener(listener unsafe.Pointer) {
if eventListener != nil || listener == nil { if eventListener != nil || listener == nil {
@@ -261,9 +241,6 @@ func sendMessage(message Message) {
//export stopTun //export stopTun
func stopTun() { func stopTun() {
handleStopTun() handleStopTun()
if isRunning {
handleStopListener()
}
} }
//export suspend //export suspend

View File

@@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
@@ -26,7 +25,6 @@ class Application extends ConsumerStatefulWidget {
class ApplicationState extends ConsumerState<Application> { class ApplicationState extends ConsumerState<Application> {
Timer? _autoUpdateProfilesTaskTimer; Timer? _autoUpdateProfilesTaskTimer;
bool _preHasVpn = false;
final _pageTransitionsTheme = const PageTransitionsTheme( final _pageTransitionsTheme = const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{ builders: <TargetPlatform, PageTransitionsBuilder>{
@@ -47,22 +45,22 @@ class ApplicationState extends ConsumerState<Application> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_autoUpdateProfilesTask();
globalState.appController = AppController(context, ref);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final currentContext = globalState.navigatorKey.currentContext; final currentContext = globalState.navigatorKey.currentContext;
if (currentContext != null) { if (currentContext != null) {
await appController.attach(currentContext, ref); globalState.appController = AppController(currentContext, ref);
} else {
exit(0);
} }
_autoUpdateProfilesTask(); await globalState.appController.init();
appController.initLink(); globalState.appController.initLink();
app?.initShortcuts(); app?.initShortcuts();
}); });
} }
void _autoUpdateProfilesTask() { void _autoUpdateProfilesTask() {
_autoUpdateProfilesTaskTimer = Timer(const Duration(minutes: 20), () async { _autoUpdateProfilesTaskTimer = Timer(const Duration(minutes: 20), () async {
await appController.autoUpdateProfiles(); await globalState.appController.autoUpdateProfiles();
_autoUpdateProfilesTask(); _autoUpdateProfilesTask();
}); });
} }
@@ -83,13 +81,11 @@ class ApplicationState extends ConsumerState<Application> {
child: CoreManager( child: CoreManager(
child: ConnectivityManager( child: ConnectivityManager(
onConnectivityChanged: (results) async { onConnectivityChanged: (results) async {
commonPrint.log('connectivityChanged ${results.toString()}'); if (!results.contains(ConnectivityResult.vpn)) {
appController.updateLocalIp(); coreController.closeConnections();
final hasVpn = results.contains(ConnectivityResult.vpn);
if (_preHasVpn == hasVpn) {
appController.addCheckIp();
} }
_preHasVpn = hasVpn; globalState.appController.updateLocalIp();
globalState.appController.addCheckIpNumDebounce();
}, },
child: child, child: child,
), ),
@@ -167,7 +163,8 @@ class ApplicationState extends ConsumerState<Application> {
linkManager.destroy(); linkManager.destroy();
_autoUpdateProfilesTaskTimer?.cancel(); _autoUpdateProfilesTaskTimer?.cancel();
await coreController.destroy(); await coreController.destroy();
await appController.handleExit(); await globalState.appController.savePreferences();
await globalState.appController.handleExit();
super.dispose(); super.dispose();
} }
} }

View File

@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:archive/archive_io.dart'; import 'package:archive/archive_io.dart';
@@ -17,11 +18,14 @@ extension ArchiveExt on Archive {
final archiveFile = ArchiveFile(relativePath, data.length, data); final archiveFile = ArchiveFile(relativePath, data.length, data);
addFile(archiveFile); addFile(archiveFile);
} }
// else if (entity is Directory) {
// addDirectoryToArchive(entity.path, parentPath);
// }
} }
} }
// void addTextFile<T>(String name, T raw) { void addTextFile<T>(String name, T raw) {
// final data = json.encode(raw); final data = json.encode(raw);
// addFile(ArchiveFile.string(name, data)); addFile(ArchiveFile.string(name, data));
// } }
} }

View File

@@ -6,21 +6,17 @@ export 'constant.dart';
export 'context.dart'; export 'context.dart';
export 'converter.dart'; export 'converter.dart';
export 'datetime.dart'; export 'datetime.dart';
export 'file.dart';
export 'fixed.dart'; export 'fixed.dart';
export 'function.dart'; export 'function.dart';
export 'future.dart'; export 'future.dart';
export 'hive.dart';
export 'http.dart'; export 'http.dart';
export 'icons.dart'; export 'icons.dart';
export 'indexing.dart';
export 'iterable.dart'; export 'iterable.dart';
export 'keyboard.dart'; export 'keyboard.dart';
export 'launch.dart'; export 'launch.dart';
export 'link.dart'; export 'link.dart';
export 'lock.dart'; export 'lock.dart';
export 'measure.dart'; export 'measure.dart';
export 'migration.dart';
export 'mixin.dart'; export 'mixin.dart';
export 'navigation.dart'; export 'navigation.dart';
export 'navigator.dart'; export 'navigator.dart';
@@ -36,10 +32,8 @@ export 'proxy.dart';
export 'render.dart'; export 'render.dart';
export 'request.dart'; export 'request.dart';
export 'scroll.dart'; export 'scroll.dart';
export 'snowflake.dart';
export 'string.dart'; export 'string.dart';
export 'system.dart'; export 'system.dart';
export 'task.dart';
export 'text.dart'; export 'text.dart';
export 'tray.dart'; export 'tray.dart';
export 'utils.dart'; export 'utils.dart';

View File

@@ -1,65 +1,65 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'string.dart';
List<Group> computeSort({ List<Group> computeSort({
required List<Group> groups, required List<Group> groups,
required ProxiesSortType sortType, required ProxiesSortType sortType,
required DelayMap delayMap, required DelayMap delayMap,
required Map<String, String> selectedMap, required SelectedMap selectedMap,
required String defaultTestUrl, required String defaultTestUrl,
}) { }) {
List<Proxy> sortOfDelay({
required List<Group> groups,
required List<Proxy> proxies,
required DelayMap delayMap,
required Map<String, String> selectedMap,
required String testUrl,
}) {
return List.from(proxies)..sort((a, b) {
final aDelayState = computeProxyDelayState(
proxyName: a.name,
testUrl: testUrl,
groups: groups,
selectedMap: selectedMap,
delayMap: delayMap,
);
final bDelayState = computeProxyDelayState(
proxyName: b.name,
testUrl: testUrl,
groups: groups,
selectedMap: selectedMap,
delayMap: delayMap,
);
return aDelayState.compareTo(bDelayState);
});
}
List<Proxy> sortOfName(List<Proxy> proxies) {
return List.of(proxies)..sort((a, b) => a.name.compareTo(b.name));
}
return groups.map((group) { return groups.map((group) {
final proxies = group.all; final proxies = group.all;
final newProxies = switch (sortType) { final newProxies = switch (sortType) {
ProxiesSortType.none => proxies, ProxiesSortType.none => proxies,
ProxiesSortType.delay => sortOfDelay( ProxiesSortType.delay => _sortOfDelay(
groups: groups, groups: groups,
proxies: proxies, proxies: proxies,
delayMap: delayMap, delayMap: delayMap,
selectedMap: selectedMap, selectedMap: selectedMap,
testUrl: group.testUrl.takeFirstValid([defaultTestUrl]), testUrl: group.testUrl.getSafeValue(defaultTestUrl),
), ),
ProxiesSortType.name => sortOfName(proxies), ProxiesSortType.name => _sortOfName(proxies),
}; };
return group.copyWith(all: newProxies); return group.copyWith(all: newProxies);
}).toList(); }).toList();
} }
SelectedProxyState getRealSelectedProxyState( DelayState computeProxyDelayState({
required String proxyName,
required String testUrl,
required List<Group> groups,
required SelectedMap selectedMap,
required DelayMap delayMap,
}) {
final state = computeRealSelectedProxyState(
proxyName,
groups: groups,
selectedMap: selectedMap,
);
final currentDelayMap = delayMap[state.testUrl.getSafeValue(testUrl)] ?? {};
final delay = currentDelayMap[state.proxyName];
return DelayState(delay: delay ?? 0, group: state.group);
}
SelectedProxyState computeRealSelectedProxyState(
String proxyName, {
required List<Group> groups,
required SelectedMap selectedMap,
}) {
return _getRealSelectedProxyState(
SelectedProxyState(proxyName: proxyName),
groups: groups,
selectedMap: selectedMap,
);
}
SelectedProxyState _getRealSelectedProxyState(
SelectedProxyState state, { SelectedProxyState state, {
required List<Group> groups, required List<Group> groups,
required Map<String, String> selectedMap, required SelectedMap selectedMap,
}) { }) {
if (state.proxyName.isEmpty) return state; if (state.proxyName.isEmpty) return state;
final index = groups.indexWhere((element) => element.name == state.proxyName); final index = groups.indexWhere((element) => element.name == state.proxyName);
@@ -72,39 +72,39 @@ SelectedProxyState getRealSelectedProxyState(
if (currentSelectedName.isEmpty) { if (currentSelectedName.isEmpty) {
return newState; return newState;
} }
return getRealSelectedProxyState( return _getRealSelectedProxyState(
newState.copyWith(proxyName: currentSelectedName, testUrl: group.testUrl), newState.copyWith(proxyName: currentSelectedName, testUrl: group.testUrl),
groups: groups, groups: groups,
selectedMap: selectedMap, selectedMap: selectedMap,
); );
} }
SelectedProxyState computeRealSelectedProxyState( List<Proxy> _sortOfDelay({
String proxyName, {
required List<Group> groups, required List<Group> groups,
required Map<String, String> selectedMap, required List<Proxy> proxies,
required DelayMap delayMap,
required SelectedMap selectedMap,
required String testUrl,
}) { }) {
return getRealSelectedProxyState( return List.from(proxies)..sort((a, b) {
SelectedProxyState(proxyName: proxyName), final aDelayState = computeProxyDelayState(
groups: groups, proxyName: a.name,
selectedMap: selectedMap, testUrl: testUrl,
); groups: groups,
selectedMap: selectedMap,
delayMap: delayMap,
);
final bDelayState = computeProxyDelayState(
proxyName: b.name,
testUrl: testUrl,
groups: groups,
selectedMap: selectedMap,
delayMap: delayMap,
);
return aDelayState.compareTo(bDelayState);
});
} }
DelayState computeProxyDelayState({ List<Proxy> _sortOfName(List<Proxy> proxies) {
required String proxyName, return List.of(proxies)..sort((a, b) => a.name.compareTo(b.name));
required String testUrl,
required List<Group> groups,
required Map<String, String> selectedMap,
required DelayMap delayMap,
}) {
final state = computeRealSelectedProxyState(
proxyName,
groups: groups,
selectedMap: selectedMap,
);
final currentDelayMap =
delayMap[state.testUrl.takeFirstValid([testUrl])] ?? {};
final delay = currentDelayMap[state.proxyName];
return DelayState(delay: delay ?? 0, group: state.group);
} }

View File

@@ -20,18 +20,16 @@ const helperPort = 47890;
const maxTextScale = 1.4; const maxTextScale = 1.4;
const minTextScale = 0.8; const minTextScale = 0.8;
final baseInfoEdgeInsets = EdgeInsets.symmetric( final baseInfoEdgeInsets = EdgeInsets.symmetric(
vertical: 16.mAp, vertical: 16.ap,
horizontal: 16.mAp, horizontal: 16.ap,
); );
final listHeaderPadding = EdgeInsets.only( final listHeaderPadding = EdgeInsets.only(
left: 16.mAp, left: 16.ap,
right: 8.mAp, right: 8.ap,
top: 24.mAp, top: 24.ap,
bottom: 8.mAp, bottom: 8.ap,
); );
const watchExecution = true;
final defaultTextScaleFactor = final defaultTextScaleFactor =
WidgetsBinding.instance.platformDispatcher.textScaleFactor; WidgetsBinding.instance.platformDispatcher.textScaleFactor;
const httpTimeoutDuration = Duration(milliseconds: 5000); const httpTimeoutDuration = Duration(milliseconds: 5000);
@@ -65,26 +63,19 @@ final commonFilter = ImageFilter.blur(
tileMode: TileMode.mirror, tileMode: TileMode.mirror,
); );
const listEquality = ListEquality();
const navigationItemListEquality = ListEquality<NavigationItem>(); const navigationItemListEquality = ListEquality<NavigationItem>();
const trackerInfoListEquality = ListEquality<TrackerInfo>(); const trackerInfoListEquality = ListEquality<TrackerInfo>();
const stringListEquality = ListEquality<String>(); const stringListEquality = ListEquality<String>();
const intListEquality = ListEquality<int>(); const intListEquality = ListEquality<int>();
const logListEquality = ListEquality<Log>(); const logListEquality = ListEquality<Log>();
const groupListEquality = ListEquality<Group>(); const groupListEquality = ListEquality<Group>();
const ruleListEquality = ListEquality<Rule>(); const ruleEquality = ListEquality<Rule>();
const scriptListEquality = ListEquality<Script>();
const externalProviderListEquality = ListEquality<ExternalProvider>(); const externalProviderListEquality = ListEquality<ExternalProvider>();
const packageListEquality = ListEquality<Package>(); const packageListEquality = ListEquality<Package>();
const profileListEquality = ListEquality<Profile>();
const hotKeyActionListEquality = ListEquality<HotKeyAction>(); const hotKeyActionListEquality = ListEquality<HotKeyAction>();
const stringAndStringMapEquality = MapEquality<String, String>(); const stringAndStringMapEquality = MapEquality<String, String>();
const stringAndStringMapEntryListEquality =
ListEquality<MapEntry<String, String>>();
const stringAndStringMapEntryIterableEquality = const stringAndStringMapEntryIterableEquality =
IterableEquality<MapEntry<String, String>>(); IterableEquality<MapEntry<String, String>>();
const stringAndObjectMapEntryIterableEquality =
IterableEquality<MapEntry<String, Object?>>();
const delayMapEquality = MapEquality<String, Map<String, int?>>(); const delayMapEquality = MapEquality<String, Map<String, int?>>();
const stringSetEquality = SetEquality<String>(); const stringSetEquality = SetEquality<String>();
const keyboardModifierListEquality = SetEquality<KeyboardModifier>(); const keyboardModifierListEquality = SetEquality<KeyboardModifier>();
@@ -102,8 +93,7 @@ const profilesStoreKey = PageStorageKey<String>('profiles');
const defaultPrimaryColor = 0XFFD8C0C3; const defaultPrimaryColor = 0XFFD8C0C3;
double getWidgetHeight(num lines) { double getWidgetHeight(num lines) {
final space = 14.mAp; return max(lines * 80 + (lines - 1) * 16, 0).ap;
return max(lines * (80.ap + space) - space, 0);
} }
const maxLength = 1000; const maxLength = 1000;
@@ -126,6 +116,3 @@ const scriptTemplate = '''
const main = (config) => { const main = (config) => {
return config; return config;
}'''; }''';
const backupDatabaseName = 'database.sqlite';
const configJsonName = 'config.json';

View File

@@ -1,6 +1,6 @@
import 'package:fl_clash/l10n/l10n.dart'; import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/manager/manager.dart'; import 'package:fl_clash/manager/manager.dart';
import 'package:fl_clash/models/state.dart'; import 'package:fl_clash/models/widget.dart';
import 'package:fl_clash/widgets/scaffold.dart'; import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
@@ -9,10 +10,19 @@ class DAVClient {
Completer<bool> pingCompleter = Completer(); Completer<bool> pingCompleter = Completer();
late String fileName; late String fileName;
DAVClient(DAVProps dav) { DAVClient(DAV dav) {
client = newClient(dav.uri, user: dav.user, password: dav.password); client = newClient(
dav.uri,
user: dav.user,
password: dav.password,
);
fileName = dav.fileName; fileName = dav.fileName;
client.setHeaders({'accept-charset': 'utf-8', 'Content-Type': 'text/xml'}); client.setHeaders(
{
'accept-charset': 'utf-8',
'Content-Type': 'text/xml',
},
);
client.setConnectTimeout(8000); client.setConnectTimeout(8000);
client.setSendTimeout(60000); client.setSendTimeout(60000);
client.setReceiveTimeout(60000); client.setReceiveTimeout(60000);
@@ -32,16 +42,15 @@ class DAVClient {
String get backupFile => '$root/$fileName'; String get backupFile => '$root/$fileName';
Future<bool> backup(String localFilePath) async { Future<bool> backup(Uint8List data) async {
await client.mkdir(root); await client.mkdir(root);
await client.writeFromFile(localFilePath, backupFile); await client.write(backupFile, data);
return true; return true;
} }
Future<bool> restore() async { Future<List<int>> recovery() async {
await client.mkdir(root); await client.mkdir(root);
final backupFilePath = await appPath.backupFilePath; final data = await client.read(backupFile);
await client.read2File(backupFile, backupFilePath); return data;
return true;
} }
} }

View File

@@ -1,38 +0,0 @@
import 'dart:io';
extension FileExt on File {
Future<void> safeCopy(String newPath) async {
if (!await exists()) {
await create(recursive: true);
return;
}
final targetFile = File(newPath);
if (!await targetFile.exists()) {
await targetFile.create(recursive: true);
}
await copy(newPath);
}
Future<File> safeWriteAsString(String str) async {
if (!await exists()) {
await create(recursive: true);
}
return await writeAsString(str);
}
Future<File> safeWriteAsBytes(List<int> bytes) async {
if (!await exists()) {
await create(recursive: true);
}
return await writeAsBytes(bytes);
}
}
extension FileSystemEntityExt on FileSystemEntity {
Future<void> safeDelete({bool recursive = false}) async {
if (!await exists()) {
return;
}
await delete(recursive: recursive);
}
}

View File

@@ -1,15 +1,15 @@
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart'; import 'package:fl_clash/state.dart';
class FlClashHttpOverrides extends HttpOverrides { class FlClashHttpOverrides extends HttpOverrides {
static String handleFindProxy(Uri url) { static String handleFindProxy(Uri url) {
if ([localhost].contains(url.host)) { if ([localhost].contains(url.host)) {
return 'DIRECT'; return 'DIRECT';
} }
final port = appController.config.patchClashConfig.mixedPort; final port = globalState.config.patchClashConfig.mixedPort;
final isStart = appController.isStart; final isStart = globalState.appState.runTime != null;
commonPrint.log('find $url proxy:$isStart'); commonPrint.log('find $url proxy:$isStart');
if (!isStart) return 'DIRECT'; if (!isStart) return 'DIRECT';
return 'PROXY localhost:$port'; return 'PROXY localhost:$port';

View File

@@ -1,260 +0,0 @@
import 'dart:math';
class Indexing {
static const String digits =
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
static const String integerZero = 'a0';
static const String smallestInteger = 'A00000000000000000000000000';
static Indexing? _instance;
Indexing._internal();
factory Indexing() {
_instance ??= Indexing._internal();
return _instance!;
}
int _getIntegerLength(String head) {
if (head.compareTo('a') >= 0 && head.compareTo('z') <= 0) {
return head.codeUnitAt(0) - 'a'.codeUnitAt(0) + 2;
} else if (head.compareTo('A') >= 0 && head.compareTo('Z') <= 0) {
return 'Z'.codeUnitAt(0) - head.codeUnitAt(0) + 2;
} else {
throw Exception('Invalid order key head: $head');
}
}
bool _validateInteger(String integer) {
if (integer.length != _getIntegerLength(integer[0])) {
throw Exception('Invalid integer part of order key: $integer');
}
return true;
}
String? _incrementInteger(String x) {
_validateInteger(x);
String head = x[0];
List<String> digs = x.substring(1).split('');
bool carry = true;
for (int i = digs.length - 1; carry && i >= 0; i--) {
int d = digits.indexOf(digs[i]) + 1;
if (d == digits.length) {
digs[i] = '0';
} else {
digs[i] = digits[d];
carry = false;
}
}
if (carry) {
if (head == 'Z') {
return 'a0';
}
if (head == 'z') {
return null;
}
String h = String.fromCharCode(head.codeUnitAt(0) + 1);
if (h.compareTo('a') > 0) {
digs.add('0');
} else {
digs.removeLast();
}
return h + digs.join('');
} else {
return head + digs.join('');
}
}
String? _decrementInteger(String x) {
_validateInteger(x);
String head = x[0];
List<String> digs = x.substring(1).split('');
bool borrow = true;
for (int i = digs.length - 1; borrow && i >= 0; i--) {
int d = digits.indexOf(digs[i]) - 1;
if (d == -1) {
digs[i] = digits[digits.length - 1];
} else {
digs[i] = digits[d];
borrow = false;
}
}
if (borrow) {
if (head == 'a') {
return 'Z${digits[digits.length - 1]}';
}
if (head == 'A') {
return null;
}
String h = String.fromCharCode(head.codeUnitAt(0) - 1);
if (h.compareTo('Z') < 0) {
digs.add(digits[digits.length - 1]);
} else {
digs.removeLast();
}
return h + digs.join('');
} else {
return head + digs.join('');
}
}
String _midpoint(String a, String? b) {
if (b != null && a.compareTo(b) >= 0) {
throw Exception(
'Second order key must be greater than the first: $a, $b',
);
}
if (a.isNotEmpty && a[a.length - 1] == '0' ||
(b != null && b.isNotEmpty && b[b.length - 1] == '0')) {
throw Exception('Trailing zeros are not allowed: $a, $b');
}
if (b != null) {
int n = 0;
while ((n < a.length ? a[n] : '0') == b[n]) {
n++;
}
if (n > 0) {
return b.substring(0, n) +
_midpoint(
a.substring(min(n, a.length)),
b.substring(min(n, b.length)),
);
}
}
int digitA = (a.isNotEmpty) ? digits.indexOf(a[0]) : 0;
int digitB = (b != null && b.isNotEmpty)
? digits.indexOf(b[0])
: digits.length;
if (digitB - digitA > 1) {
int midDigit = (digitA + digitB + 1) ~/ 2;
return digits[midDigit];
} else {
if (b != null && b.length > 1) {
return b.substring(0, 1);
} else {
return digits[digitA] +
_midpoint(a.isNotEmpty ? a.substring(1) : '', null);
}
}
}
String _getIntegerPart(String key) {
int integerPartLength = _getIntegerLength(key[0]);
if (integerPartLength > key.length) {
throw Exception('Invalid order key: $key');
}
return key.substring(0, integerPartLength);
}
bool _validateOrderKey(String key) {
if (key == smallestInteger) {
throw Exception('Invalid order key: $key');
}
String i = _getIntegerPart(key);
String f = key.substring(i.length);
if (f.isNotEmpty && f[f.length - 1] == '0') {
throw Exception('Invalid order key: $key');
}
return true;
}
String? generateKeyBetween(String? a, String? b) {
if (a != null) {
_validateOrderKey(a);
}
if (b != null) {
_validateOrderKey(b);
}
if (a != null && b != null && a.compareTo(b) >= 0) {
throw Exception(
'Second order key must be greater than the first: $a, $b',
);
}
if (a == null && b == null) {
return integerZero;
}
if (a == null) {
b = b!;
String ib = _getIntegerPart(b);
String fb = b.substring(ib.length);
if (ib == smallestInteger) {
return ib + _midpoint('', fb);
}
return ib.compareTo(b) < 0 ? ib : _decrementInteger(ib);
}
if (b == null) {
String ia = _getIntegerPart(a);
String fa = a.substring(ia.length);
String? i = _incrementInteger(ia);
return i ?? ia + _midpoint(fa, null);
}
String ia = _getIntegerPart(a);
String fa = a.substring(ia.length);
String ib = _getIntegerPart(b);
String fb = b.substring(ib.length);
if (ia == ib) {
return ia + _midpoint(fa, fb);
}
String? i = _incrementInteger(ia);
return (i == null || i.compareTo(b) < 0) ? i : ia + _midpoint(fa, null);
}
List<String?> generateNKeysBetween(String? a, String? b, int n) {
if (n <= 0) {
return [];
}
if (n == 1) {
return [generateKeyBetween(a, b)];
}
if (b == null) {
String? c = generateKeyBetween(a, b);
List<String?> result = [c];
for (int i = 1; i < n; i++) {
c = generateKeyBetween(c, b);
result.add(c);
}
return result;
}
if (a == null) {
String? c = generateKeyBetween(a, b);
List<String?> result = [c];
for (int i = 1; i < n; i++) {
c = generateKeyBetween(a, c);
result.add(c);
}
return result.reversed.toList();
}
int mid = n ~/ 2;
String? c = generateKeyBetween(a, b);
return generateNKeysBetween(a, c, mid)
.followedBy([c])
.followedBy(generateNKeysBetween(c, b, n - mid - 1))
.toList();
}
}
final indexing = Indexing();

View File

@@ -1,5 +1,5 @@
extension IterableExt<E> on Iterable<E> { extension IterableExt<T> on Iterable<T> {
Iterable<E> separated(E separator) sync* { Iterable<T> separated(T separator) sync* {
final iterator = this.iterator; final iterator = this.iterator;
if (!iterator.moveNext()) return; if (!iterator.moveNext()) return;
@@ -11,7 +11,7 @@ extension IterableExt<E> on Iterable<E> {
} }
} }
Iterable<List<E>> chunks(int size) sync* { Iterable<List<T>> chunks(int size) sync* {
if (length == 0) return; if (length == 0) return;
var iterator = this.iterator; var iterator = this.iterator;
while (iterator.moveNext()) { while (iterator.moveNext()) {
@@ -23,7 +23,7 @@ extension IterableExt<E> on Iterable<E> {
} }
} }
Iterable<E> fill(int length, {required E Function(int count) filler}) sync* { Iterable<T> fill(int length, {required T Function(int count) filler}) sync* {
int count = 0; int count = 0;
for (var item in this) { for (var item in this) {
yield item; yield item;
@@ -36,7 +36,7 @@ extension IterableExt<E> on Iterable<E> {
} }
} }
Iterable<E> takeLast({int count = 50}) { Iterable<T> takeLast({int count = 50}) {
if (count <= 0) return Iterable.empty(); if (count <= 0) return Iterable.empty();
return count >= length ? this : toList().skip(length - count); return count >= length ? this : toList().skip(length - count);
} }
@@ -78,18 +78,9 @@ extension ListExt<T> on List<T> {
return sublist(start); return sublist(start);
} }
T? safeGet(int index, {T? defaultValue}) { T safeGet(int index) {
if (index < 0 || index >= length) { if (length > index) return this[index];
return defaultValue; return last;
}
return this[index];
}
T safeLast(T defaultValue) {
if (isNotEmpty) {
return last;
}
return defaultValue;
} }
void addOrRemove(T value) { void addOrRemove(T value) {
@@ -144,14 +135,4 @@ extension MapExt<K, V> on Map<K, V> {
} }
return this[key]!; return this[key]!;
} }
Map<K, V> copyWitUpdate(K key, V? value) {
final newMap = Map<K, V>.from(this);
if (value == null) {
newMap.remove(key);
} else {
newMap[key] = value;
}
return newMap;
}
} }

View File

@@ -1,53 +0,0 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
class Migration {
static Migration? _instance;
late int _oldVersion;
Migration._internal();
final currentVersion = 1;
factory Migration() {
_instance ??= Migration._internal();
return _instance!;
}
Future<Config> migrationIfNeeded(
Map<String, Object?>? configMap, {
required Future<Config> Function(MigrationData data) sync,
}) async {
_oldVersion = await preferences.getVersion();
if (_oldVersion == currentVersion) {
try {
return Config.realFromJson(configMap);
} catch (_) {
final isV0 = configMap?['proxiesStyle'] != null;
if (isV0) {
_oldVersion = 0;
} else {
throw 'Local data is damaged. A reset is required to fix this issue.';
}
}
}
MigrationData data = MigrationData(configMap: configMap);
if (_oldVersion == 0 && configMap != null) {
final clashConfigMap = await preferences.getClashConfigMap();
if (clashConfigMap != null) {
configMap['patchClashConfig'] = clashConfigMap;
await preferences.clearClashConfig();
}
data = await _oldToNow(configMap);
}
final res = await sync(data);
await preferences.setVersion(currentVersion);
return res;
}
Future<MigrationData> _oldToNow(Map<String, Object?> configMap) async {
return await oldToNowTask(configMap);
}
}
final migration = Migration();

View File

@@ -2,21 +2,17 @@ import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> { mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
T get value => state;
set value(T value) { set value(T value) {
state = value; if (ref.mounted) {
} state = value;
} else {
bool equals(T previous, T next) { onUpdate(value);
return false; }
} }
@override @override
bool updateShouldNotify(previous, next) { bool updateShouldNotify(previous, next) {
final res = !equals(previous, next) final res = super.updateShouldNotify(previous, next);
? super.updateShouldNotify(previous, next)
: true;
if (res) { if (res) {
onUpdate(next); onUpdate(next);
} }
@@ -25,19 +21,31 @@ mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
void onUpdate(T value) {} void onUpdate(T value) {}
void update(T? Function(T) builder) { void update(T Function(T) builder) {
final res = builder(value); final value = builder(state);
if (res == null) { this.value = value;
return;
}
value = res;
} }
} }
mixin AsyncNotifierMixin<T> on AnyNotifier<AsyncValue<T>, T> { mixin AnyNotifierMixin<T> on AnyNotifier<T, T> {
T get value; T get value;
set value(T value) { set value(T value) {
state = AsyncData(value); if (ref.mounted) {
state = value;
} else {
onUpdate(value);
}
} }
@override
bool updateShouldNotify(previous, next) {
final res = super.updateShouldNotify(previous, next);
if (res) {
onUpdate(next);
}
return res;
}
void onUpdate(T value) {}
} }

View File

@@ -1,11 +1,13 @@
import 'package:animations/animations.dart'; import 'package:animations/animations.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class BaseNavigator { class BaseNavigator {
static Future<T?> push<T>(BuildContext context, Widget child) async { static Future<T?> push<T>(BuildContext context, Widget child) async {
if (!appController.isMobile) { if (globalState.appState.viewMode != ViewMode.mobile) {
return await Navigator.of( return await Navigator.of(
context, context,
).push<T>(CommonDesktopRoute(builder: (context) => child)); ).push<T>(CommonDesktopRoute(builder: (context) => child));

View File

@@ -1,5 +1,3 @@
import 'dart:math';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart'; import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
@@ -22,10 +20,6 @@ extension NumExt on num {
return this * (1 + (globalState.theme.textScaleFactor - 1) * 0.5); return this * (1 + (globalState.theme.textScaleFactor - 1) * 0.5);
} }
double get mAp {
return this * min((1 + (globalState.theme.textScaleFactor - 1) * 0.5), 1);
}
TrafficShow get traffic { TrafficShow get traffic {
final units = TrafficUnit.values; final units = TrafficUnit.values;
var size = toDouble(); var size = toDouble();
@@ -39,20 +33,6 @@ extension NumExt on num {
unit: units[unitIndex].name, unit: units[unitIndex].name,
); );
} }
TrafficShow get shortTraffic {
final units = TrafficUnit.values;
var size = toDouble();
var unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return TrafficShow(
value: size.toStringAsFixed(0),
unit: ' ${units[unitIndex].name}',
);
}
} }
extension DoubleExt on double { extension DoubleExt on double {

View File

@@ -61,39 +61,19 @@ class AppPath {
return directory.path; return directory.path;
} }
Future<String> get databasePath async {
final mHomeDirPath = await homeDirPath;
return join(mHomeDirPath, 'database.sqlite');
}
Future<String> get backupFilePath async {
final mHomeDirPath = await homeDirPath;
return join(mHomeDirPath, 'backup.zip');
}
Future<String> get restoreDirPath async {
final mHomeDirPath = await homeDirPath;
return join(mHomeDirPath, 'restore');
}
Future<String> get tempFilePath async {
final mTempDir = await tempDir.future;
return join(mTempDir.path, 'temp${utils.id}');
}
Future<String> get lockFilePath async { Future<String> get lockFilePath async {
final homeDirPath = await appPath.homeDirPath; final homeDirPath = await appPath.homeDirPath;
return join(homeDirPath, 'FlClash.lock'); return join(homeDirPath, 'FlClash.lock');
} }
Future<String> get configFilePath async { Future<String> get configFilePath async {
final mHomeDirPath = await homeDirPath; final homeDirPath = await appPath.homeDirPath;
return join(mHomeDirPath, 'config.yaml'); return join(homeDirPath, 'config.yaml');
} }
Future<String> get sharedFilePath async { Future<String> get validateFilePath async {
final mHomeDirPath = await homeDirPath; final homeDirPath = await appPath.homeDirPath;
return join(mHomeDirPath, 'shared.json'); return join(homeDirPath, 'temp', 'validate${utils.id}.yaml');
} }
Future<String> get sharedPreferencesPath async { Future<String> get sharedPreferencesPath async {
@@ -106,18 +86,9 @@ class AppPath {
return join(directory.path, profilesDirectoryName); return join(directory.path, profilesDirectoryName);
} }
Future<String> getProfilePath(String fileName) async { Future<String> getProfilePath(String id) async {
return join(await profilesPath, '$fileName.yaml'); final directory = await profilesPath;
} return join(directory, '$id.yaml');
Future<String> get scriptsDirPath async {
final path = await homeDirPath;
return join(path, 'scripts');
}
Future<String> getScriptPath(String fileName) async {
final path = await scriptsDirPath;
return join(path, '$fileName.js');
} }
Future<String> getIconsCacheDir() async { Future<String> getIconsCacheDir() async {

View File

@@ -7,9 +7,9 @@ import 'package:image_picker/image_picker.dart';
import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:mobile_scanner/mobile_scanner.dart';
class Picker { class Picker {
Future<PlatformFile?> pickerFile({bool withData = true}) async { Future<PlatformFile?> pickerFile() async {
final filePickerResult = await FilePicker.platform.pickFiles( final filePickerResult = await FilePicker.platform.pickFiles(
withData: withData, withData: true,
allowMultiple: false, allowMultiple: false,
initialDirectory: await appPath.downloadDirPath, initialDirectory: await appPath.downloadDirPath,
); );
@@ -20,33 +20,15 @@ class Picker {
final path = await FilePicker.platform.saveFile( final path = await FilePicker.platform.saveFile(
fileName: fileName, fileName: fileName,
initialDirectory: await appPath.downloadDirPath, initialDirectory: await appPath.downloadDirPath,
bytes: bytes, bytes: system.isAndroid ? bytes : null,
); );
if (!system.isAndroid && path != null) { if (!system.isAndroid && path != null) {
final file = File(path); final file = await File(path).create(recursive: true);
await file.safeWriteAsBytes(bytes); await file.writeAsBytes(bytes);
} }
return path; return path;
} }
Future<String?> saveFileWithPath(String fileName, String localPath) async {
final localFile = File(localPath);
if (!await localFile.exists()) {
await localFile.create(recursive: true);
}
final bytes = Platform.isAndroid ? await localFile.readAsBytes() : null;
final path = await FilePicker.platform.saveFile(
fileName: fileName,
initialDirectory: await appPath.downloadDirPath,
bytes: bytes,
);
if (path != null && bytes == null) {
await localFile.copy(path);
}
await localFile.safeDelete();
return path;
}
Future<String?> pickerConfigQRCode() async { Future<String?> pickerConfigQRCode() async {
final xFile = await ImagePicker().pickImage(source: ImageSource.gallery); final xFile = await ImagePicker().pickImage(source: ImageSource.gallery);
if (xFile == null) { if (xFile == null) {

View File

@@ -24,70 +24,36 @@ class Preferences {
return _instance!; return _instance!;
} }
Future<int> getVersion() async { Future<ClashConfig?> getClashConfig() async {
final preferences = await sharedPreferencesCompleter.future; final preferences = await sharedPreferencesCompleter.future;
return preferences?.getInt('version') ?? 0; final clashConfigString = preferences?.getString(clashConfigKey);
} if (clashConfigString == null) return null;
final clashConfigMap = json.decode(clashConfigString);
Future<void> setVersion(int version) async { return ClashConfig.fromJson(clashConfigMap);
final preferences = await sharedPreferencesCompleter.future;
await preferences?.setInt('version', version);
}
Future<void> saveShareState(SharedState shareState) async {
final preferences = await sharedPreferencesCompleter.future;
await preferences?.setString('sharedState', json.encode(shareState));
}
Future<Map<String, Object?>?> getConfigMap() async {
try {
final preferences = await sharedPreferencesCompleter.future;
final configString = preferences?.getString(configKey);
if (configString == null) return null;
final Map<String, Object?>? configMap = json.decode(configString);
return configMap;
} catch (_) {
return null;
}
}
Future<Map<String, Object?>?> getClashConfigMap() async {
try {
final preferences = await sharedPreferencesCompleter.future;
final clashConfigString = preferences?.getString(clashConfigKey);
if (clashConfigString == null) return null;
return json.decode(clashConfigString);
} catch (_) {
return null;
}
}
Future<void> clearClashConfig() async {
try {
final preferences = await sharedPreferencesCompleter.future;
await preferences?.remove(clashConfigKey);
return;
} catch (_) {
return;
}
} }
Future<Config?> getConfig() async { Future<Config?> getConfig() async {
final configMap = await getConfigMap(); final preferences = await sharedPreferencesCompleter.future;
if (configMap == null) { final configString = preferences?.getString(configKey);
return null; if (configString == null) return null;
} final configMap = json.decode(configString);
return Config.fromJson(configMap); return Config.compatibleFromJson(configMap);
} }
Future<bool> saveConfig(Config config) async { Future<bool> saveConfig(Config config) async {
final preferences = await sharedPreferencesCompleter.future; final preferences = await sharedPreferencesCompleter.future;
return preferences?.setString(configKey, json.encode(config)) ?? false; return await preferences?.setString(configKey, json.encode(config)) ??
false;
}
Future<void> clearClashConfig() async {
final preferences = await sharedPreferencesCompleter.future;
preferences?.remove(clashConfigKey);
} }
Future<void> clearPreferences() async { Future<void> clearPreferences() async {
final sharedPreferencesIns = await sharedPreferencesCompleter.future; final sharedPreferencesIns = await sharedPreferencesCompleter.future;
await sharedPreferencesIns?.clear(); sharedPreferencesIns?.clear();
} }
} }

View File

@@ -1,7 +1,7 @@
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:flutter/material.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/cupertino.dart';
class CommonPrint { class CommonPrint {
static CommonPrint? _instance; static CommonPrint? _instance;
@@ -16,10 +16,12 @@ class CommonPrint {
void log(String? text, {LogLevel logLevel = LogLevel.info}) { void log(String? text, {LogLevel logLevel = LogLevel.info}) {
final payload = '[APP] $text'; final payload = '[APP] $text';
debugPrint(payload); debugPrint(payload);
if (!appController.isAttach) { if (!globalState.isInit) {
return; return;
} }
appController.addLog(Log.app(payload).copyWith(logLevel: logLevel)); globalState.appController.addLog(
Log.app(payload).copyWith(logLevel: logLevel),
);
} }
} }

View File

@@ -6,8 +6,6 @@ import 'dart:typed_data';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio/io.dart'; import 'package:dio/io.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@@ -24,7 +22,7 @@ class Request {
createHttpClient: () { createHttpClient: () {
final client = HttpClient(); final client = HttpClient();
client.findProxy = (Uri uri) { client.findProxy = (Uri uri) {
client.userAgent = appController.ua; client.userAgent = globalState.ua;
return FlClashHttpOverrides.handleFindProxy(uri); return FlClashHttpOverrides.handleFindProxy(uri);
}; };
return client; return client;
@@ -33,23 +31,10 @@ class Request {
} }
Future<Response<Uint8List>> getFileResponseForUrl(String url) async { Future<Response<Uint8List>> getFileResponseForUrl(String url) async {
try { return await _clashDio.get<Uint8List>(
return await _clashDio.get<Uint8List>( url,
url, options: Options(responseType: ResponseType.bytes),
options: Options(responseType: ResponseType.bytes), );
);
} catch (e) {
commonPrint.log('getFileResponseForUrl error ${e.toString()}');
if (e is DioException) {
if (e.type == DioExceptionType.unknown) {
throw appLocalizations.unknownNetworkError;
} else if (e.type == DioExceptionType.badResponse) {
throw appLocalizations.networkException;
}
rethrow;
}
throw appLocalizations.unknownNetworkError;
}
} }
Future<Response<String>> getTextResponseForUrl(String url) async { Future<Response<String>> getTextResponseForUrl(String url) async {
@@ -72,23 +57,18 @@ class Request {
} }
Future<Map<String, dynamic>?> checkForUpdate() async { Future<Map<String, dynamic>?> checkForUpdate() async {
try { final response = await dio.get(
final response = await dio.get( 'https://api.github.com/repos/$repository/releases/latest',
'https://api.github.com/repos/$repository/releases/latest', options: Options(responseType: ResponseType.json),
options: Options(responseType: ResponseType.json), );
); if (response.statusCode != 200) return null;
if (response.statusCode != 200) return null; final data = response.data as Map<String, dynamic>;
final data = response.data as Map<String, dynamic>; final remoteVersion = data['tag_name'];
final remoteVersion = data['tag_name']; final version = globalState.packageInfo.version;
final version = globalState.packageInfo.version; final hasUpdate =
final hasUpdate = utils.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0;
utils.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0; if (!hasUpdate) return null;
if (!hasUpdate) return null; return data;
return data;
} catch (e) {
commonPrint.log('checkForUpdate failed', logLevel: LogLevel.warning);
return null;
}
} }
final Map<String, IpInfo Function(Map<String, dynamic>)> _ipInfoSources = { final Map<String, IpInfo Function(Map<String, dynamic>)> _ipInfoSources = {
@@ -103,7 +83,6 @@ class Request {
Future<Result<IpInfo?>> checkIp({CancelToken? cancelToken}) async { Future<Result<IpInfo?>> checkIp({CancelToken? cancelToken}) async {
var failureCount = 0; var failureCount = 0;
final token = cancelToken ?? CancelToken();
final futures = _ipInfoSources.entries.map((source) async { final futures = _ipInfoSources.entries.map((source) async {
final Completer<Result<IpInfo?>> completer = Completer(); final Completer<Result<IpInfo?>> completer = Completer();
handleFailRes() { handleFailRes() {
@@ -115,7 +94,7 @@ class Request {
final future = dio final future = dio
.get<Map<String, dynamic>>( .get<Map<String, dynamic>>(
source.key, source.key,
cancelToken: token, cancelToken: cancelToken,
options: Options(responseType: ResponseType.json), options: Options(responseType: ResponseType.json),
) )
.timeout(const Duration(seconds: 10)); .timeout(const Duration(seconds: 10));
@@ -138,7 +117,7 @@ class Request {
return completer.future; return completer.future;
}); });
final res = await Future.any(futures); final res = await Future.any(futures);
token.cancel(); cancelToken?.cancel();
return res; return res;
} }

View File

@@ -1,58 +0,0 @@
class Snowflake {
static Snowflake? _instance;
Snowflake._internal();
factory Snowflake() {
_instance ??= Snowflake._internal();
return _instance!;
}
static const int twepoch = 1704067200000;
static const int workerIdBits = 10;
static const int sequenceBits = 12;
static const int maxWorkerId = -1 ^ (-1 << workerIdBits);
static const int sequenceMask = -1 ^ (-1 << sequenceBits);
static const int workerIdShift = sequenceBits;
static const int timestampLeftShift = sequenceBits + workerIdBits;
final int workerId = 1;
int _lastTimestamp = -1;
int _sequence = 0;
int get id {
int timestamp = DateTime.now().millisecondsSinceEpoch;
if (timestamp < _lastTimestamp) {
throw ArgumentError(
'Clock moved backwards. Refusing to generate id for ${_lastTimestamp - timestamp} milliseconds',
);
}
if (timestamp == _lastTimestamp) {
_sequence = (_sequence + 1) & sequenceMask;
if (_sequence == 0) {
timestamp = _getNextMillis(_lastTimestamp);
}
} else {
_sequence = 0;
}
_lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) |
(workerId << workerIdShift) |
_sequence;
}
int _getNextMillis(int lastTimestamp) {
int timestamp = DateTime.now().millisecondsSinceEpoch;
while (timestamp <= lastTimestamp) {
timestamp = DateTime.now().millisecondsSinceEpoch;
}
return timestamp;
}
}
final snowflake = Snowflake();

View File

@@ -1,33 +0,0 @@
import 'dart:async';
class Store<T> {
late T _data;
Store(Stream stream, T defaultValue) {
stream.listen((data) {
_add(data);
});
_data = defaultValue;
}
bool equals(T oldValue, T newValue) {
return oldValue == newValue;
}
void _add(T value) {
if (!equals(_data, value)) {
_streamController.add(value);
_data = value;
}
}
final StreamController<T> _streamController = StreamController<T>.broadcast();
Stream<T> get stream => _streamController.stream;
T get value => _data;
set value(T value) {
_add(value);
}
}

View File

@@ -2,7 +2,8 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:fl_clash/common/common.dart';
import 'print.dart';
extension StringExtension on String { extension StringExtension on String {
bool get isUrl { bool get isUrl {
@@ -21,16 +22,6 @@ extension StringExtension on String {
return toLowerCase().compareTo(other.toLowerCase()); return toLowerCase().compareTo(other.toLowerCase());
} }
String safeSubstring(int start, [int? end]) {
if (isEmpty) return '';
final safeStart = start.clamp(0, length);
if (end == null) {
return substring(safeStart);
}
final safeEnd = end.clamp(safeStart, length);
return substring(safeStart, safeEnd);
}
List<int> get encodeUtf16LeWithBom { List<int> get encodeUtf16LeWithBom {
final byteData = ByteData(length * 2); final byteData = ByteData(length * 2);
final bom = [0xFF, 0xFE]; final bom = [0xFF, 0xFE];
@@ -77,26 +68,13 @@ extension StringExtension on String {
// bool containsToLower(String target) { // bool containsToLower(String target) {
// return toLowerCase().contains(target); // return toLowerCase().contains(target);
// } // }
Future<T> commonToJSON<T>() async {
final thresholdLimit = 51200;
if (length < thresholdLimit) {
return json.decode(this);
} else {
return await decodeJSONTask<T>(this);
}
}
} }
extension StringNullExt on String? { extension StringExtensionSafe on String? {
String takeFirstValid(List<String?> others, {String defaultValue = ''}) { String getSafeValue(String defaultValue) {
if (this != null && this!.trim().isNotEmpty) return this!.trim(); if (this == null || this!.isEmpty) {
return defaultValue;
for (final s in others) {
if (s != null && s.trim().isNotEmpty) {
return s.trim();
}
} }
return defaultValue; return this!;
} }
} }

View File

@@ -260,9 +260,8 @@ class Windows {
await Future.delayed(Duration(milliseconds: 300)); await Future.delayed(Duration(milliseconds: 300));
final retryStatus = await retry( final retryStatus = await retry(
task: checkService, task: checkService,
maxAttempts: 5,
retryIf: (status) => status != WindowsHelperServiceStatus.running, retryIf: (status) => status != WindowsHelperServiceStatus.running,
delay: Duration(seconds: 1), delay: commonDuration,
); );
return res && retryStatus == WindowsHelperServiceStatus.running; return res && retryStatus == WindowsHelperServiceStatus.running;
} }

View File

@@ -1,612 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/database/database.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:path/path.dart';
Future<T> decodeJSONTask<T>(String data) async {
return await compute<String, T>(_decodeJSON, data);
}
Future<T> _decodeJSON<T>(String content) async {
return json.decode(content);
}
Future<String> encodeJSONTask<T>(T data) async {
return await compute<T, String>(_encodeJSON, data);
}
Future<String> _encodeJSON<T>(T content) async {
return json.encode(content);
}
Future<String> encodeYamlTask<T>(T data) async {
return await compute<T, String>(_encodeYaml, data);
}
Future<String> _encodeYaml<T>(T content) async {
return yaml.encode(content);
}
Future<List<Group>> toGroupsTask(ComputeGroupsState data) async {
return await compute<ComputeGroupsState, List<Group>>(_toGroupsTask, data);
}
Future<List<Group>> _toGroupsTask(ComputeGroupsState state) async {
final proxiesData = state.proxiesData;
final all = proxiesData.all;
final sortType = state.sortType;
final delayMap = state.delayMap;
final selectedMap = state.selectedMap;
final defaultTestUrl = state.defaultTestUrl;
final proxies = proxiesData.proxies;
if (proxies.isEmpty) return [];
final groupsRaw = all
.where((name) {
final proxy = proxies[name] ?? {};
return GroupTypeExtension.valueList.contains(proxy['type']);
})
.map((groupName) {
final group = proxies[groupName];
group['all'] = ((group['all'] ?? []) as List)
.map((name) => proxies[name])
.where((proxy) => proxy != null)
.toList();
return group;
})
.toList();
final groups = groupsRaw.map((e) => Group.fromJson(e)).toList();
return computeSort(
groups: groups,
sortType: sortType,
delayMap: delayMap,
selectedMap: selectedMap,
defaultTestUrl: defaultTestUrl,
);
}
Future<Map<String, dynamic>> makeRealProfileTask(
MakeRealProfileState data,
) async {
return await compute<MakeRealProfileState, Map<String, dynamic>>(
_makeRealProfileTask,
data,
);
}
Future<Map<String, dynamic>> _makeRealProfileTask(
MakeRealProfileState data,
) async {
final rawConfig = Map.from(data.rawConfig);
final realPatchConfig = data.realPatchConfig;
final profilesPath = data.profilesPath;
final profileId = data.profileId;
final overrideDns = data.overrideDns;
final addedRules = data.addedRules;
final appendSystemDns = data.appendSystemDns;
final defaultUA = data.defaultUA;
String getProvidersFilePathInner(String type, String url) {
return join(
profilesPath,
'providers',
profileId.toString(),
type,
url.toMd5(),
);
}
rawConfig['external-controller'] = realPatchConfig.externalController.value;
rawConfig['external-ui'] = '';
rawConfig['interface-name'] = '';
rawConfig['external-ui-url'] = '';
rawConfig['tcp-concurrent'] = realPatchConfig.tcpConcurrent;
rawConfig['unified-delay'] = realPatchConfig.unifiedDelay;
rawConfig['ipv6'] = realPatchConfig.ipv6;
rawConfig['log-level'] = realPatchConfig.logLevel.name;
rawConfig['port'] = 0;
rawConfig['socks-port'] = 0;
rawConfig['keep-alive-interval'] = realPatchConfig.keepAliveInterval;
rawConfig['mixed-port'] = realPatchConfig.mixedPort;
rawConfig['port'] = realPatchConfig.port;
rawConfig['socks-port'] = realPatchConfig.socksPort;
rawConfig['redir-port'] = realPatchConfig.redirPort;
rawConfig['tproxy-port'] = realPatchConfig.tproxyPort;
rawConfig['find-process-mode'] = realPatchConfig.findProcessMode.name;
rawConfig['allow-lan'] = realPatchConfig.allowLan;
rawConfig['mode'] = realPatchConfig.mode.name;
if (rawConfig['tun'] == null) {
rawConfig['tun'] = {};
}
rawConfig['tun']['enable'] = realPatchConfig.tun.enable;
rawConfig['tun']['device'] = realPatchConfig.tun.device;
rawConfig['tun']['dns-hijack'] = realPatchConfig.tun.dnsHijack;
rawConfig['tun']['stack'] = realPatchConfig.tun.stack.name;
rawConfig['tun']['route-address'] = realPatchConfig.tun.routeAddress;
rawConfig['tun']['auto-route'] = realPatchConfig.tun.autoRoute;
rawConfig['geodata-loader'] = realPatchConfig.geodataLoader.name;
if (rawConfig['sniffer']?['sniff'] != null) {
for (final value in (rawConfig['sniffer']?['sniff'] as Map).values) {
if (value['ports'] != null && value['ports'] is List) {
value['ports'] =
value['ports']?.map((item) => item.toString()).toList() ?? [];
}
}
}
if (rawConfig['profile'] == null) {
rawConfig['profile'] = {};
}
if (rawConfig['proxy-providers'] != null) {
final proxyProviders = rawConfig['proxy-providers'] as Map;
for (final key in proxyProviders.keys) {
final proxyProvider = proxyProviders[key];
if (proxyProvider['type'] != 'http') {
continue;
}
if (proxyProvider['url'] != null) {
proxyProvider['path'] = getProvidersFilePathInner(
'proxies',
proxyProvider['url'],
);
}
}
}
if (rawConfig['rule-providers'] != null) {
final ruleProviders = rawConfig['rule-providers'] as Map;
for (final key in ruleProviders.keys) {
final ruleProvider = ruleProviders[key];
if (ruleProvider['type'] != 'http') {
continue;
}
if (ruleProvider['url'] != null) {
ruleProvider['path'] = getProvidersFilePathInner(
'rules',
ruleProvider['url'],
);
}
}
}
rawConfig['profile']['store-selected'] = false;
rawConfig['geox-url'] = realPatchConfig.geoXUrl.toJson();
rawConfig['global-ua'] = realPatchConfig.globalUa ?? defaultUA;
if (rawConfig['hosts'] == null) {
rawConfig['hosts'] = {};
}
for (final host in realPatchConfig.hosts.entries) {
rawConfig['hosts'][host.key] = host.value.splitByMultipleSeparators;
}
if (rawConfig['dns'] == null) {
rawConfig['dns'] = {};
}
final isEnableDns = rawConfig['dns']['enable'] == true;
final systemDns = 'system://';
if (overrideDns || !isEnableDns) {
final dns = switch (!isEnableDns) {
true => realPatchConfig.dns.copyWith(
nameserver: [...realPatchConfig.dns.nameserver, systemDns],
),
false => realPatchConfig.dns,
};
rawConfig['dns'] = dns.toJson();
rawConfig['dns']['nameserver-policy'] = {};
for (final entry in dns.nameserverPolicy.entries) {
rawConfig['dns']['nameserver-policy'][entry.key] =
entry.value.splitByMultipleSeparators;
}
}
if (appendSystemDns) {
final List<String> nameserver = List<String>.from(
rawConfig['dns']['nameserver'] ?? [],
);
if (!nameserver.contains(systemDns)) {
rawConfig['dns']['nameserver'] = [...nameserver, systemDns];
}
}
List<String> rules = [];
if (rawConfig['rules'] != null) {
rules = List<String>.from(rawConfig['rules']);
}
rawConfig.remove('rules');
if (addedRules.isNotEmpty) {
final parsedNewRules = addedRules
.map((item) => ParsedRule.parseString(item.value))
.toList();
final hasMatchPlaceholder = parsedNewRules.any(
(item) => item.ruleTarget?.toUpperCase() == 'MATCH',
);
String? replacementTarget;
if (hasMatchPlaceholder) {
for (int i = rules.length - 1; i >= 0; i--) {
final parsed = ParsedRule.parseString(rules[i]);
if (parsed.ruleAction == RuleAction.MATCH) {
final target = parsed.ruleTarget;
if (target != null && target.isNotEmpty) {
replacementTarget = target;
break;
}
}
}
}
final List<String> finalAddedRules;
if (replacementTarget?.isNotEmpty == true) {
finalAddedRules = [];
for (int i = 0; i < parsedNewRules.length; i++) {
final parsed = parsedNewRules[i];
if (parsed.ruleTarget?.toUpperCase() == 'MATCH') {
finalAddedRules.add(
parsed.copyWith(ruleTarget: replacementTarget).value,
);
} else {
finalAddedRules.add(addedRules[i].value);
}
}
} else {
finalAddedRules = addedRules.map((e) => e.value).toList();
}
rules = [...finalAddedRules, ...rules];
}
rawConfig['rules'] = rules;
return Map<String, dynamic>.from(rawConfig);
}
Future<List<String>> shakingProfileTask(
VM2<Iterable<int>, Iterable<int>> data,
) async {
return await compute<
VM3<Iterable<int>, Iterable<int>, RootIsolateToken>,
List<String>
>(_shakingProfileTask, VM3(data.a, data.b, RootIsolateToken.instance!));
}
Future<List<String>> _shakingProfileTask(
VM3<Iterable<int>, Iterable<int>, RootIsolateToken> data,
) async {
final profileIds = data.a;
final scriptIds = data.b;
final token = data.c;
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
final profilesDir = Directory(await appPath.profilesPath);
final scriptsDir = Directory(await appPath.scriptsDirPath);
final providersDir = Directory(await appPath.getProvidersRootPath());
final List<String> targets = [];
void scanDirectory(
Directory dir,
Iterable<int> baseNames, {
bool skipProvidersFolder = false,
}) {
if (!dir.existsSync()) return;
final entities = dir.listSync(recursive: false, followLinks: false);
for (final entity in entities) {
if (entity is File) {
final id = basenameWithoutExtension(entity.path);
if (!baseNames.contains(int.tryParse(id))) {
targets.add(entity.path);
}
} else if (skipProvidersFolder && entity is Directory) {
if (basename(entity.path) == 'providers') {
continue;
}
}
}
}
scanDirectory(profilesDir, profileIds, skipProvidersFolder: true);
scanDirectory(providersDir, profileIds);
scanDirectory(scriptsDir, scriptIds);
return targets;
}
Future<String> encodeLogsTask(List<Log> data) async {
return await compute<List<Log>, String>(_encodeLogsTask, data);
}
Future<String> _encodeLogsTask(List<Log> data) async {
final logsRaw = data.map((item) => item.toString());
final logsRawString = logsRaw.join('\n');
return logsRawString;
}
Future<MigrationData> oldToNowTask(Map<String, Object?> data) async {
final homeDir = await appPath.homeDirPath;
return await compute<
VM3<Map<String, Object?>, String, String>,
MigrationData
>(_oldToNowTask, VM3(data, homeDir, homeDir));
}
Future<MigrationData> _oldToNowTask(
VM3<Map<String, Object?>, String, String> data,
) async {
final configMap = data.a;
final sourcePath = data.b;
final targetPath = data.c;
final accessControlMap = configMap['accessControl'];
final isAccessControl = configMap['isAccessControl'];
if (accessControlMap != null) {
(accessControlMap as Map)['enable'] = isAccessControl;
if (configMap['vpnProps'] != null) {
final vpnPropsRaw = configMap['vpnProps'] as Map;
vpnPropsRaw['accessControl'] = accessControlMap;
}
}
if (configMap['vpnProps'] != null) {
final vpnPropsRaw = configMap['vpnProps'] as Map;
vpnPropsRaw['accessControlProps'] = vpnPropsRaw['accessControl'];
}
configMap['davProps'] = configMap['dav'];
final appSettingProps = configMap['appSetting'] as Map? ?? {};
appSettingProps['restoreStrategy'] = appSettingProps['recoveryStrategy'];
configMap['appSettingProps'] = appSettingProps;
configMap['proxiesStyleProps'] = configMap['proxiesStyle'];
configMap['proxiesStyleProps'] = configMap['proxiesStyle'];
// final overwriteMap = configMap['overwrite'] as Map? ?? {};
// configMap['overwriteType'] = overwriteMap['type'];
// configMap['scriptId'] = overwriteMap['scriptOverwrite'];
List rawScripts = configMap['scripts'] as List<dynamic>? ?? [];
if (rawScripts.isEmpty) {
final scriptPropsJson = configMap['scriptProps'] as Map<String, dynamic>?;
if (scriptPropsJson != null) {
rawScripts = scriptPropsJson['scripts'] as List<dynamic>? ?? [];
}
}
final Map<String, int> idMap = {};
final List<Script> scripts = [];
for (final rawScript in rawScripts) {
final id = rawScript['id'] as String?;
final content = rawScript['content'] as String?;
final label = rawScript['label'] as String?;
if (id == null || content == null || label == null) {
continue;
}
final newId = idMap.updateCacheValue(rawScript['id'], () => snowflake.id);
final path = _getScriptPath(targetPath, newId.toString());
final file = File(path);
await file.safeWriteAsString(content);
scripts.add(
Script(id: newId, label: label, lastUpdateTime: DateTime.now()),
);
}
List rawRules = configMap['rules'] as List<dynamic>? ?? [];
final List<Rule> rules = [];
final List<ProfileRuleLink> links = [];
for (final rawRule in rawRules) {
final id = idMap.updateCacheValue(rawRule['id'], () => snowflake.id);
rawRule['id'] = id;
rules.add(Rule.fromJson(rawRule));
links.add(ProfileRuleLink(ruleId: id));
}
List rawProfiles = configMap['profiles'] as List<dynamic>? ?? [];
final List<Profile> profiles = [];
for (final rawProfile in rawProfiles) {
final rawId = rawProfile['id'] as String?;
if (rawId == null) {
continue;
}
final profileId = idMap.updateCacheValue(rawId, () => snowflake.id);
rawProfile['id'] = profileId;
final overwrite = rawProfile['overwrite'] as Map?;
if (overwrite != null) {
final standardOverwrite = overwrite['standardOverwrite'] as Map?;
if (standardOverwrite != null) {
final addedRules = standardOverwrite['addedRules'] as List? ?? [];
for (final addRule in addedRules) {
final id = idMap.updateCacheValue(addRule['id'], () => snowflake.id);
addRule['id'] = id;
rules.add(Rule.fromJson(addRule));
links.add(
ProfileRuleLink(
profileId: profileId,
ruleId: id,
scene: RuleScene.added,
),
);
}
final disabledRuleIds = standardOverwrite['disabledRuleIds'] as List?;
if (disabledRuleIds != null) {
for (final disabledRuleId in disabledRuleIds) {
final newDisabledRuleId = idMap[disabledRuleId];
if (newDisabledRuleId != null) {
links.add(
ProfileRuleLink(
profileId: profileId,
ruleId: newDisabledRuleId,
scene: RuleScene.disabled,
),
);
}
}
}
}
final scriptOverwrite = overwrite['scriptOverwrite'] as Map?;
if (scriptOverwrite != null) {
final scriptId = scriptOverwrite['scriptId'] as String?;
rawProfile['scriptId'] = scriptId != null ? idMap[scriptId] : null;
}
rawProfile['overwriteType'] = overwrite['type'];
}
final sourceFile = File(_getProfilePath(sourcePath, rawId));
final targetFilePath = _getProfilePath(targetPath, profileId.toString());
await sourceFile.safeCopy(targetFilePath);
profiles.add(Profile.fromJson(rawProfile));
}
final currentProfileId = configMap['currentProfileId'];
configMap['currentProfileId'] = currentProfileId != null
? idMap[currentProfileId]
: null;
return MigrationData(
configMap: configMap,
profiles: profiles,
rules: rules,
scripts: scripts,
links: links,
);
}
Future<String> backupTask(
Map<String, dynamic> configMap,
Iterable<String> fileNames,
) async {
return await compute<
VM3<Map<String, dynamic>, Iterable<String>, RootIsolateToken>,
String
>(_backupTask, VM3(configMap, fileNames, RootIsolateToken.instance!));
}
Future<String> _backupTask<T>(
VM3<Map<String, dynamic>, Iterable<String>, RootIsolateToken> args,
) async {
final configMap = args.a;
final fileNames = args.b;
final token = args.c;
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
final dbPath = await appPath.databasePath;
final configStr = json.encode(configMap);
final profilesDir = Directory(await appPath.profilesPath);
final scriptsDir = Directory(await appPath.scriptsDirPath);
final tempZipFilePath = await appPath.tempFilePath;
final tempDBFile = File(await appPath.tempFilePath);
final tempConfigFile = File(await appPath.tempFilePath);
final dbFile = File(dbPath);
if (await dbFile.exists()) {
await dbFile.copy(tempDBFile.path);
}
final encoder = ZipFileEncoder();
encoder.create(tempZipFilePath);
await tempConfigFile.writeAsString(configStr);
await encoder.addFile(tempDBFile, backupDatabaseName);
await encoder.addFile(tempConfigFile, configJsonName);
if (await profilesDir.exists()) {
await encoder.addDirectory(
profilesDir,
filter: (file, _) {
if (!fileNames.contains(basename(file.path))) {
return ZipFileOperation.skip;
}
return ZipFileOperation.include;
},
);
}
if (await scriptsDir.exists()) {
await encoder.addDirectory(
scriptsDir,
filter: (file, _) {
if (!fileNames.contains(basename(file.path))) {
return ZipFileOperation.skip;
}
return ZipFileOperation.include;
},
);
}
encoder.close();
await tempConfigFile.safeDelete();
await tempDBFile.safeDelete();
return tempZipFilePath;
}
Future<MigrationData> restoreTask() async {
return await compute<RootIsolateToken, MigrationData>(
_restoreTask,
RootIsolateToken.instance!,
);
}
Future<MigrationData> _restoreTask(RootIsolateToken token) async {
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
final backupFilePath = await appPath.backupFilePath;
final restoreDirPath = await appPath.restoreDirPath;
final homeDirPath = await appPath.homeDirPath;
final zipDecoder = ZipDecoder();
final input = InputFileStream(backupFilePath);
final archive = zipDecoder.decodeStream(input);
final dir = Directory(restoreDirPath);
await dir.create(recursive: true);
for (final file in archive.files) {
final outPath = join(restoreDirPath, posix.normalize(file.name));
final outputStream = OutputFileStream(outPath);
file.writeContent(outputStream);
await outputStream.close();
}
await input.close();
final restoreConfigFile = File(join(restoreDirPath, configJsonName));
if (!await restoreConfigFile.exists()) {
throw appLocalizations.invalidBackupFile;
}
final restoreConfigMap =
json.decode(await restoreConfigFile.readAsString())
as Map<String, Object?>?;
final version = restoreConfigMap?['version'] ?? 0;
MigrationData migrationData = MigrationData(configMap: restoreConfigMap);
if (version == 0 && restoreConfigMap != null) {
migrationData = await _oldToNowTask(
VM3(restoreConfigMap, restoreDirPath, homeDirPath),
);
return migrationData;
}
final backupDatabaseFile = File(join(restoreDirPath, backupDatabaseName));
if (!await backupDatabaseFile.exists()) {
return migrationData;
}
final database = Database(
driftDatabase(
name: 'database',
native: DriftNativeOptions(
databaseDirectory: () async => Directory(restoreDirPath),
),
),
);
final results = await Future.wait([
database.profilesDao.all().get(),
database.scriptsDao.all().get(),
database.rules.all().map((item) => item.toRule()).get(),
database.profileRuleLinks.all().map((item) => item.toLink()).get(),
]);
final profiles = results[0].cast<Profile>();
final scripts = results[1].cast<Script>();
final profilesMigration = profiles.map(
(item) => VM2(
_getProfilePath(restoreDirPath, item.id.toString()),
_getProfilePath(homeDirPath, item.id.toString()),
),
);
final scriptsMigration = scripts.map(
(item) => VM2(
_getScriptPath(restoreDirPath, item.id.toString()),
_getScriptPath(homeDirPath, item.id.toString()),
),
);
await _copyWithMapList([...profilesMigration, ...scriptsMigration]);
migrationData = migrationData.copyWith(
profiles: profiles,
scripts: scripts,
rules: results[2].cast<Rule>(),
links: results[3].cast<ProfileRuleLink>(),
);
await database.close();
return migrationData;
}
Future<void> _copyWithMapList(List<VM2<String, String>> copyMapList) async {
await Future.wait(
copyMapList.map((item) => File(item.a).safeCopy(item.b)).toList(),
);
}
String _getScriptPath(String root, String fileName) {
return join(root, 'scripts', '$fileName.js');
}
String _getProfilePath(String root, String fileName) {
return join(root, 'profiles', '$fileName.yaml');
}

View File

@@ -1,8 +1,10 @@
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/controller.dart'; import 'package:fl_clash/common/utils.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:tray_manager/tray_manager.dart'; import 'package:tray_manager/tray_manager.dart';
@@ -13,60 +15,38 @@ import 'system.dart';
import 'window.dart'; import 'window.dart';
class Tray { class Tray {
static Tray? _instance;
Tray._internal();
factory Tray() {
_instance ??= Tray._internal();
return _instance!;
}
String get trayIconSuffix {
return system.isWindows ? 'ico' : 'png';
}
Future<void> destroy() async {
await trayManager.destroy();
}
String getTryIcon({required bool isStart, required bool tunEnable}) {
if (system.isMacOS || !isStart) {
return 'assets/images/icon/status_1.$trayIconSuffix';
}
if (!tunEnable) {
return 'assets/images/icon/status_2.$trayIconSuffix';
}
return 'assets/images/icon/status_3.$trayIconSuffix';
}
Future _updateSystemTray({ Future _updateSystemTray({
required bool isStart, required Brightness? brightness,
required bool tunEnable, bool force = false,
}) async { }) async {
if (Platform.isLinux) { if (Platform.isLinux || force) {
await trayManager.destroy(); await trayManager.destroy();
} }
await trayManager.setIcon( await trayManager.setIcon(
getTryIcon(isStart: isStart, tunEnable: tunEnable), utils.getTrayIconPath(
brightness: brightness ??
WidgetsBinding.instance.platformDispatcher.platformBrightness,
),
isTemplate: true, isTemplate: true,
); );
if (!Platform.isLinux) { if (!Platform.isLinux) {
await trayManager.setToolTip(appName); await trayManager.setToolTip(
appName,
);
} }
} }
Future<void> update({ Future<void> update({
required TrayState trayState, required TrayState trayState,
required Traffic traffic, bool focus = false,
}) async { }) async {
if (system.isAndroid) { if (system.isAndroid) {
return; return;
} }
if (!system.isLinux) { if (!Platform.isLinux) {
await _updateSystemTray( await _updateSystemTray(
isStart: trayState.isStart, brightness: trayState.brightness,
tunEnable: trayState.tunEnable, force: focus,
); );
} }
List<MenuItem> menuItems = []; List<MenuItem> menuItems = [];
@@ -80,28 +60,18 @@ class Tray {
final startMenuItem = MenuItem.checkbox( final startMenuItem = MenuItem.checkbox(
label: trayState.isStart ? appLocalizations.stop : appLocalizations.start, label: trayState.isStart ? appLocalizations.stop : appLocalizations.start,
onClick: (_) async { onClick: (_) async {
appController.updateStart(); globalState.appController.updateStart();
}, },
checked: false, checked: false,
); );
menuItems.add(startMenuItem); menuItems.add(startMenuItem);
if (system.isMacOS) {
final speedStatistics = MenuItem.checkbox(
label: appLocalizations.speedStatistics,
onClick: (_) async {
appController.updateSpeedStatistics();
},
checked: trayState.showTrayTitle,
);
menuItems.add(speedStatistics);
}
menuItems.add(MenuItem.separator()); menuItems.add(MenuItem.separator());
for (final mode in Mode.values) { for (final mode in Mode.values) {
menuItems.add( menuItems.add(
MenuItem.checkbox( MenuItem.checkbox(
label: Intl.message(mode.name), label: Intl.message(mode.name),
onClick: (_) { onClick: (_) {
appController.changeMode(mode); globalState.appController.changeMode(mode);
}, },
checked: mode == trayState.mode, checked: mode == trayState.mode,
), ),
@@ -115,10 +85,13 @@ class Tray {
subMenuItems.add( subMenuItems.add(
MenuItem.checkbox( MenuItem.checkbox(
label: proxy.name, label: proxy.name,
checked: checked: trayState.selectedMap[group.name] == proxy.name,
appController.getSelectedProxyName(group.name) == proxy.name,
onClick: (_) { onClick: (_) {
appController.updateCurrentSelectedMap(group.name, proxy.name); final appController = globalState.appController;
appController.updateCurrentSelectedMap(
group.name,
proxy.name,
);
appController.changeProxy( appController.changeProxy(
groupName: group.name, groupName: group.name,
proxyName: proxy.name, proxyName: proxy.name,
@@ -130,7 +103,9 @@ class Tray {
menuItems.add( menuItems.add(
MenuItem.submenu( MenuItem.submenu(
label: group.name, label: group.name,
submenu: Menu(items: subMenuItems), submenu: Menu(
items: subMenuItems,
),
), ),
); );
} }
@@ -143,7 +118,7 @@ class Tray {
MenuItem.checkbox( MenuItem.checkbox(
label: appLocalizations.tun, label: appLocalizations.tun,
onClick: (_) { onClick: (_) {
appController.updateTun(); globalState.appController.updateTun();
}, },
checked: trayState.tunEnable, checked: trayState.tunEnable,
), ),
@@ -152,7 +127,7 @@ class Tray {
MenuItem.checkbox( MenuItem.checkbox(
label: appLocalizations.systemProxy, label: appLocalizations.systemProxy,
onClick: (_) { onClick: (_) {
appController.updateSystemProxy(); globalState.appController.updateSystemProxy();
}, },
checked: trayState.systemProxy, checked: trayState.systemProxy,
), ),
@@ -162,7 +137,7 @@ class Tray {
final autoStartMenuItem = MenuItem.checkbox( final autoStartMenuItem = MenuItem.checkbox(
label: appLocalizations.autoLaunch, label: appLocalizations.autoLaunch,
onClick: (_) async { onClick: (_) async {
appController.updateAutoLaunch(); globalState.appController.updateAutoLaunch();
}, },
checked: trayState.autoLaunch, checked: trayState.autoLaunch,
); );
@@ -178,43 +153,44 @@ class Tray {
final exitMenuItem = MenuItem( final exitMenuItem = MenuItem(
label: appLocalizations.exit, label: appLocalizations.exit,
onClick: (_) async { onClick: (_) async {
await appController.handleExit(); await globalState.appController.handleExit();
}, },
); );
menuItems.add(exitMenuItem); menuItems.add(exitMenuItem);
final menu = Menu(items: menuItems); final menu = Menu(items: menuItems);
await trayManager.setContextMenu(menu); await trayManager.setContextMenu(menu);
if (system.isLinux) { if (Platform.isLinux) {
await _updateSystemTray( await _updateSystemTray(
isStart: trayState.isStart, brightness: trayState.brightness,
tunEnable: trayState.tunEnable, force: focus,
); );
} }
updateTrayTitle(showTrayTitle: trayState.showTrayTitle, traffic: traffic);
} }
Future<void> updateTrayTitle({ Future<void> updateTrayTitle([Traffic? traffic]) async {
required bool showTrayTitle, // if (!system.isMacOS) {
required Traffic traffic, // return;
}) async { // }
if (!system.isMacOS) { // if (traffic == null) {
return; // await trayManager.setTitle("");
} // } else {
if (!showTrayTitle) { // await trayManager.setTitle(
await trayManager.setTitle(''); // "${traffic.up.shortShow} ↑ \n${traffic.down.shortShow} ↓",
} else { // );
await trayManager.setTitle(traffic.trayTitle); // }
}
} }
Future<void> _copyEnv(int port) async { Future<void> _copyEnv(int port) async {
final url = 'http://127.0.0.1:$port'; final url = 'http://127.0.0.1:$port';
final cmdline = system.isWindows final cmdline =
? 'set \$env:all_proxy=$url' system.isWindows ? 'set \$env:all_proxy=$url' : 'export all_proxy=$url';
: 'export all_proxy=$url';
await Clipboard.setData(ClipboardData(text: cmdline)); await Clipboard.setData(
ClipboardData(
text: cmdline,
),
);
} }
} }

View File

@@ -10,15 +10,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class Utils { class Utils {
static Utils? _instance;
Utils._internal();
factory Utils() {
_instance ??= Utils._internal();
return _instance!;
}
Color? getDelayColor(int? delay) { Color? getDelayColor(int? delay) {
if (delay == null) return null; if (delay == null) return null;
if (delay < 0) return Colors.red; if (delay < 0) return Colors.red;
@@ -151,9 +142,16 @@ class Utils {
} }
} }
String get traySuffix { String getTrayIconPath({required Brightness brightness}) {
if (system.isMacOS) {
return 'assets/images/icon_white.png';
}
final suffix = system.isWindows ? 'ico' : 'png'; final suffix = system.isWindows ? 'ico' : 'png';
return 'assets/images/icon/status_2.$suffix'; return 'assets/images/icon.$suffix';
// return switch (brightness) {
// Brightness.dark => "assets/images/icon_white.$suffix",
// Brightness.light => "assets/images/icon_black.$suffix",
// };
} }
int compareVersions(String version1, String version2) { int compareVersions(String version1, String version2) {
@@ -328,7 +326,7 @@ class Utils {
required Function function, required Function function,
required void Function(T data, int elapsedMilliseconds) onWatch, required void Function(T data, int elapsedMilliseconds) onWatch,
}) async { }) async {
if (kDebugMode && watchExecution) { if (kDebugMode) {
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
final res = await function(); final res = await function();
stopwatch.stop(); stopwatch.stop();
@@ -337,21 +335,6 @@ class Utils {
} }
return await function(); return await function();
} }
int fastHash(String string) {
var hash = 0xcbf29ce484222325;
var i = 0;
while (i < string.length) {
final codeUnit = string.codeUnitAt(i++);
hash ^= codeUnit >> 8;
hash *= 0x100000001b3;
hash ^= codeUnit & 0xFF;
hash *= 0x100000001b3;
}
return hash;
}
} }
final utils = Utils(); final utils = Utils();

View File

@@ -1,22 +1,14 @@
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/config.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:screen_retriever/screen_retriever.dart'; import 'package:screen_retriever/screen_retriever.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
class Window { class Window {
static Window? _instance; Future<void> init(int version) async {
final props = globalState.config.windowProps;
Window._internal();
factory Window() {
_instance ??= Window._internal();
return _instance!;
}
Future<void> init(int version, WindowProps props) async {
final acquire = await singleInstanceLock.acquire(); final acquire = await singleInstanceLock.acquire();
if (!acquire) { if (!acquire) {
exit(0); exit(0);
@@ -27,22 +19,13 @@ class Window {
protocol.register('flclash'); protocol.register('flclash');
} }
await windowManager.ensureInitialized(); await windowManager.ensureInitialized();
// kDebugMode ? Size(680, 580) :
WindowOptions windowOptions = WindowOptions( WindowOptions windowOptions = WindowOptions(
size: props.size, size: Size(props.width, props.height),
minimumSize: const Size(380, 400), minimumSize: const Size(380, 400),
); );
if (!system.isMacOS || version > 10) { if (!system.isMacOS || version > 10) {
await windowManager.setTitleBarStyle(TitleBarStyle.hidden); await windowManager.setTitleBarStyle(TitleBarStyle.hidden);
} }
await windowManager.setMaximizable(false);
await _windowPosition(props);
await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.setPreventClose(true);
});
}
Future<void> _windowPosition(WindowProps props) async {
if (!system.isMacOS) { if (!system.isMacOS) {
final left = props.left ?? 0; final left = props.left ?? 0;
final top = props.top ?? 0; final top = props.top ?? 0;
@@ -67,6 +50,9 @@ class Window {
} }
} }
} }
await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.setPreventClose(true);
});
} }
Future<void> show() async { Future<void> show() async {
@@ -83,7 +69,6 @@ class Window {
} }
Future<void> close() async { Future<void> close() async {
await windowManager.close();
exit(0); exit(0);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:isolate';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/core/core.dart'; import 'package:fl_clash/core/core.dart';
import 'package:fl_clash/core/interface.dart'; import 'package:fl_clash/core/interface.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
@@ -65,23 +67,25 @@ class CoreController {
); );
} }
Future<void> shutdown(bool isUser) async { Future<void> shutdown() async {
await _interface.shutdown(isUser); await _interface.shutdown();
} }
FutureOr<bool> get isInit => _interface.isInit; FutureOr<bool> get isInit => _interface.isInit;
Future<String> validateConfig(String path) async { Future<String> validateConfig(String data) async {
final path = await appPath.validateFilePath;
await globalState.genValidateFile(path, data);
final res = await _interface.validateConfig(path); final res = await _interface.validateConfig(path);
await File(path).delete();
return res; return res;
} }
Future<String> validateConfigWithData(String data) async { Future<String> validateConfigFormBytes(Uint8List bytes) async {
final path = await appPath.tempFilePath; final path = await appPath.validateFilePath;
final file = File(path); await globalState.genValidateFileFormBytes(path, bytes);
await file.safeWriteAsString(data);
final res = await _interface.validateConfig(path); final res = await _interface.validateConfig(path);
await File(path).safeDelete(); await File(path).delete();
return res; return res;
} }
@@ -104,19 +108,36 @@ class CoreController {
Future<List<Group>> getProxiesGroups({ Future<List<Group>> getProxiesGroups({
required ProxiesSortType sortType, required ProxiesSortType sortType,
required DelayMap delayMap, required DelayMap delayMap,
required Map<String, String> selectedMap, required SelectedMap selectedMap,
required String defaultTestUrl, required String defaultTestUrl,
}) async { }) async {
final proxiesData = await _interface.getProxies(); final proxies = await _interface.getProxies();
return toGroupsTask( return Isolate.run<List<Group>>(() {
ComputeGroupsState( if (proxies.isEmpty) return [];
proxiesData: proxiesData, final groupNames = [
UsedProxy.GLOBAL.name,
...(proxies[UsedProxy.GLOBAL.name]['all'] as List).where((e) {
final proxy = proxies[e] ?? {};
return GroupTypeExtension.valueList.contains(proxy['type']);
}),
];
final groupsRaw = groupNames.map((groupName) {
final group = proxies[groupName];
group['all'] = ((group['all'] ?? []) as List)
.map((name) => proxies[name])
.where((proxy) => proxy != null)
.toList();
return group;
}).toList();
final groups = groupsRaw.map((e) => Group.fromJson(e)).toList();
return computeSort(
groups: groups,
sortType: sortType, sortType: sortType,
delayMap: delayMap, delayMap: delayMap,
selectedMap: selectedMap, selectedMap: selectedMap,
defaultTestUrl: defaultTestUrl, defaultTestUrl: defaultTestUrl,
), );
); });
} }
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async { FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async {
@@ -147,11 +168,13 @@ class CoreController {
if (externalProvidersRawString.isEmpty) { if (externalProvidersRawString.isEmpty) {
return []; return [];
} }
final externalProviders = return Isolate.run<List<ExternalProvider>>(() {
(await externalProvidersRawString.commonToJSON<List<dynamic>>()) final externalProviders =
.map((item) => ExternalProvider.fromJson(item)) (json.decode(externalProvidersRawString) as List<dynamic>)
.toList(); .map((item) => ExternalProvider.fromJson(item))
return externalProviders; .toList();
return externalProviders;
});
} }
Future<ExternalProvider?> getExternalProvider( Future<ExternalProvider?> getExternalProvider(
@@ -197,14 +220,11 @@ class CoreController {
return Delay.fromJson(json.decode(data)); return Delay.fromJson(json.decode(data));
} }
Future<Map<String, dynamic>> getConfig(int id) async { Future<Map<String, dynamic>> getConfig(String id) async {
final profilePath = await appPath.getProfilePath(id.toString()); final profilePath = await appPath.getProfilePath(id);
final res = await _interface.getConfig(profilePath); final res = await _interface.getConfig(profilePath);
if (res.isSuccess) { if (res.isSuccess) {
final data = Map<String, dynamic>.from(res.data); return res.data;
data['rules'] = data['rule'];
data.remove('rule');
return data;
} else { } else {
throw res.message; throw res.message;
} }

View File

@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:isolate';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
@@ -11,7 +12,7 @@ mixin CoreInterface {
Future<String> preload(); Future<String> preload();
Future<bool> shutdown(bool isUser); Future<bool> shutdown();
Future<bool> get isInit; Future<bool> get isInit;
@@ -27,7 +28,7 @@ mixin CoreInterface {
Future<String> setupConfig(SetupParams setupParams); Future<String> setupConfig(SetupParams setupParams);
Future<ProxiesData> getProxies(); Future<Map> getProxies();
Future<String> changeProxy(ChangeProxyParams changeProxyParams); Future<String> changeProxy(ChangeProxyParams changeProxyParams);
@@ -94,13 +95,13 @@ abstract class CoreHandlerInterface with CoreInterface {
); );
return null; return null;
} }
if (kDebugMode && watchExecution) { if (kDebugMode) {
commonPrint.log('Invoke ${method.name} ${DateTime.now()} $data'); commonPrint.log('Invoke ${method.name} ${DateTime.now()} $data');
} }
return await utils.handleWatch( return utils.handleWatch(
function: () async { function: () async {
return await invoke<T>(method: method, data: data, timeout: timeout); return await invoke(method: method, data: data, timeout: timeout);
}, },
onWatch: (data, elapsedMilliseconds) { onWatch: (data, elapsedMilliseconds) {
commonPrint.log('Invoke ${method.name} ${elapsedMilliseconds}ms'); commonPrint.log('Invoke ${method.name} ${elapsedMilliseconds}ms');
@@ -131,7 +132,7 @@ abstract class CoreHandlerInterface with CoreInterface {
} }
@override @override
Future<bool> shutdown(bool isUser); Future<bool> shutdown();
@override @override
Future<bool> get isInit async { Future<bool> get isInit async {
@@ -163,15 +164,16 @@ abstract class CoreHandlerInterface with CoreInterface {
@override @override
Future<Result> getConfig(String path) async { Future<Result> getConfig(String path) async {
final res = await _invoke(method: ActionMethod.getConfig, data: path); return await _invoke<Result>(method: ActionMethod.getConfig, data: path) ??
return res ?? Result.success({}); Result.success({});
} }
@override @override
Future<String> setupConfig(SetupParams setupParams) async { Future<String> setupConfig(SetupParams setupParams) async {
final data = await Isolate.run(() => json.encode(setupParams));
return await _invoke<String>( return await _invoke<String>(
method: ActionMethod.setupConfig, method: ActionMethod.setupConfig,
data: json.encode(setupParams), data: data,
) ?? ) ??
''; '';
} }
@@ -182,13 +184,9 @@ abstract class CoreHandlerInterface with CoreInterface {
} }
@override @override
Future<ProxiesData> getProxies() async { Future<Map> getProxies() async {
final data = await _invoke<Map<String, dynamic>>( final map = await _invoke<Map>(method: ActionMethod.getProxies);
method: ActionMethod.getProxies, return map ?? {};
);
return data != null
? ProxiesData.fromJson(data)
: ProxiesData(proxies: {}, all: []);
} }
@override @override

View File

@@ -1,10 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/core.dart'; import 'package:fl_clash/models/core.dart';
import 'package:fl_clash/plugins/service.dart'; import 'package:fl_clash/plugins/service.dart';
import 'package:fl_clash/state.dart';
import 'interface.dart'; import 'interface.dart';
@@ -22,7 +22,9 @@ class CoreLib extends CoreHandlerInterface {
return res ?? ''; return res ?? '';
} }
_connectedCompleter.complete(true); _connectedCompleter.complete(true);
final syncRes = await service?.syncState(appController.sharedState); final syncRes = await service?.syncAndroidState(
globalState.getAndroidState(),
);
return syncRes ?? ''; return syncRes ?? '';
} }
@@ -37,12 +39,10 @@ class CoreLib extends CoreHandlerInterface {
} }
@override @override
Future<bool> shutdown(_) async { Future<bool> shutdown() async {
if (!_connectedCompleter.isCompleted) { await service?.shutdown();
return false;
}
_connectedCompleter = Completer(); _connectedCompleter = Completer();
return service?.shutdown() ?? true; return true;
} }
@override @override

View File

@@ -16,8 +16,6 @@ class CoreService extends CoreHandlerInterface {
Completer<Socket> _socketCompleter = Completer(); Completer<Socket> _socketCompleter = Completer();
Completer<bool> _shutdownCompleter = Completer();
final Map<String, Completer> _callbackCompleterMap = {}; final Map<String, Completer> _callbackCompleterMap = {};
Process? _process; Process? _process;
@@ -37,9 +35,6 @@ class CoreService extends CoreHandlerInterface {
if (result.id?.isEmpty == true) { if (result.id?.isEmpty == true) {
coreEventManager.sendEvent(CoreEvent.fromJson(result.data)); coreEventManager.sendEvent(CoreEvent.fromJson(result.data));
} }
if (completer?.isCompleted == true) {
return;
}
completer?.complete(data); completer?.complete(data);
} }
@@ -75,15 +70,11 @@ class CoreService extends CoreHandlerInterface {
.transform(uint8ListToListIntConverter) .transform(uint8ListToListIntConverter)
.transform(utf8.decoder) .transform(utf8.decoder)
.transform(LineSplitter()) .transform(LineSplitter())
.listen((data) async { .listen((data) {
final dataJson = await data.trim().commonToJSON<dynamic>(); handleResult(ActionResult.fromJson(json.decode(data.trim())));
handleResult(ActionResult.fromJson(dataJson));
}) })
.onDone(() { .onDone(() {
_handleInvokeCrashEvent(); _handleInvokeCrashEvent();
if (!_shutdownCompleter.isCompleted) {
_shutdownCompleter.complete(true);
}
}); });
} }
@@ -95,7 +86,7 @@ class CoreService extends CoreHandlerInterface {
Future<void> start() async { Future<void> start() async {
if (_process != null) { if (_process != null) {
await shutdown(false); await shutdown();
} }
final serverSocket = await _serverCompleter.future; final serverSocket = await _serverCompleter.future;
final arg = system.isWindows final arg = system.isWindows
@@ -121,7 +112,7 @@ class CoreService extends CoreHandlerInterface {
@override @override
destroy() async { destroy() async {
final server = await _serverCompleter.future; final server = await _serverCompleter.future;
await shutdown(false); await shutdown();
await server.close(); await server.close();
await _deleteSocketFile(); await _deleteSocketFile();
return true; return true;
@@ -135,7 +126,9 @@ class CoreService extends CoreHandlerInterface {
Future<void> _deleteSocketFile() async { Future<void> _deleteSocketFile() async {
if (!system.isWindows) { if (!system.isWindows) {
final file = File(unixSocketPath); final file = File(unixSocketPath);
await file.safeDelete(); if (await file.exists()) {
await file.delete();
}
} }
} }
@@ -143,16 +136,12 @@ class CoreService extends CoreHandlerInterface {
if (_socketCompleter.isCompleted) { if (_socketCompleter.isCompleted) {
final socket = await _socketCompleter.future; final socket = await _socketCompleter.future;
_socketCompleter = Completer(); _socketCompleter = Completer();
await socket.close(); socket.close();
} }
} }
@override @override
shutdown(bool isUser) async { shutdown() async {
if (!_socketCompleter.isCompleted && _process == null) {
return false;
}
_shutdownCompleter = Completer();
await _destroySocket(); await _destroySocket();
_clearCompleter(); _clearCompleter();
if (system.isWindows) { if (system.isWindows) {
@@ -160,11 +149,7 @@ class CoreService extends CoreHandlerInterface {
} }
_process?.kill(); _process?.kill();
_process = null; _process = null;
if (isUser) { return true;
return _shutdownCompleter.future;
} else {
return true;
}
} }
void _clearCompleter() { void _clearCompleter() {

View File

@@ -1,78 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
part 'generated/database.g.dart';
part 'links.dart';
part 'profiles.dart';
part 'rules.dart';
part 'scripts.dart';
@DriftDatabase(
tables: [Profiles, Scripts, Rules, ProfileRuleLinks],
daos: [ProfilesDao, ScriptsDao, RulesDao],
)
class Database extends _$Database {
Database([QueryExecutor? executor]) : super(executor ?? _openConnection());
@override
int get schemaVersion => 1;
static LazyDatabase _openConnection() {
return LazyDatabase(() async {
final databaseFile = File(await appPath.databasePath);
return NativeDatabase.createInBackground(databaseFile);
});
}
Future<void> restore(
List<Profile> profiles,
List<Script> scripts,
List<Rule> rules,
List<ProfileRuleLink> links, {
bool isOverride = false,
}) async {
if (profiles.isNotEmpty ||
scripts.isNotEmpty ||
rules.isNotEmpty ||
links.isNotEmpty) {
await batch((b) {
isOverride
? profilesDao.setAllWithBatch(b, profiles)
: profilesDao.putAllWithBatch(
b,
profiles.map((item) => item.toCompanion()),
);
scriptsDao.setAllWithBatch(b, scripts);
rulesDao.restoreWithBatch(b, rules, links);
});
}
}
}
extension TableInfoExt<Tbl extends Table, Row> on TableInfo<Tbl, Row> {
void setAll(
Batch batch,
Iterable<Insertable<Row>> items, {
required Expression<bool> Function(Tbl tbl) deleteFilter,
}) async {
batch.insertAllOnConflictUpdate(this, items);
batch.deleteWhere(this, deleteFilter);
}
Future<int> remove(Expression<bool> Function(Tbl tbl) filter) async {
return await (delete()..where(filter)).go();
}
Future<int> put(Insertable<Row> item) async {
return await insertOnConflictUpdate(item);
}
}
final database = Database();

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +0,0 @@
part of 'database.dart';
@DataClassName('RawProfileRuleLink')
@TableIndex(
name: 'idx_profile_scene_order',
columns: {#profileId, #scene, #order},
)
class ProfileRuleLinks extends Table {
@override
String get tableName => 'profile_rule_mapping';
TextColumn get id => text()();
IntColumn get profileId => integer().nullable().references(
Profiles,
#id,
onDelete: KeyAction.cascade,
)();
IntColumn get ruleId =>
integer().references(Rules, #id, onDelete: KeyAction.cascade)();
TextColumn get scene => textEnum<RuleScene>().nullable()();
TextColumn get order => text().nullable()();
@override
Set<Column> get primaryKey => {id};
}
extension RawProfileRuleLinkExt on RawProfileRuleLink {
ProfileRuleLink toLink() {
return ProfileRuleLink(
profileId: profileId,
ruleId: ruleId,
scene: scene,
order: order,
);
}
}
extension ProfileRuleLinksCompanionExt on ProfileRuleLink {
ProfileRuleLinksCompanion toCompanion() {
return ProfileRuleLinksCompanion.insert(
id: key,
ruleId: ruleId,
scene: Value(scene),
profileId: Value(profileId),
order: Value(order),
);
}
}

View File

@@ -1,168 +0,0 @@
part of 'database.dart';
@DataClassName('RawProfile')
class Profiles extends Table {
@override
String get tableName => 'profiles';
IntColumn get id => integer()();
TextColumn get label => text()();
TextColumn get currentGroupName => text().nullable()();
TextColumn get url => text()();
DateTimeColumn get lastUpdateDate => dateTime().nullable()();
TextColumn get overwriteType => textEnum<OverwriteType>()();
IntColumn get scriptId => integer().nullable()();
IntColumn get autoUpdateDurationMillis => integer()();
TextColumn get subscriptionInfo =>
text().map(const SubscriptionInfoConverter()).nullable()();
BoolColumn get autoUpdate => boolean()();
TextColumn get selectedMap => text().map(const StringMapConverter())();
TextColumn get unfoldSet => text().map(const StringSetConverter())();
IntColumn get order => integer().nullable()();
@override
Set<Column> get primaryKey => {id};
}
class SubscriptionInfoConverter
extends TypeConverter<SubscriptionInfo?, String?> {
const SubscriptionInfoConverter();
@override
SubscriptionInfo? fromSql(String? fromDb) {
if (fromDb == null) return null;
return SubscriptionInfo.fromJson(json.decode(fromDb));
}
@override
String? toSql(SubscriptionInfo? value) {
if (value == null) return null;
return json.encode(value.toJson());
}
}
@DriftAccessor(tables: [Profiles])
class ProfilesDao extends DatabaseAccessor<Database> with _$ProfilesDaoMixin {
ProfilesDao(super.attachedDatabase);
Selectable<Profile> all() {
final stmt = profiles.select();
stmt.orderBy([
(t) => OrderingTerm(expression: t.order, nulls: NullsOrder.last),
(t) => OrderingTerm.asc(t.id),
]);
return stmt.map((item) => item.toProfile());
}
Future<void> setAll(Iterable<Profile> profiles) async {
await batch((b) async {
setAllWithBatch(b, profiles);
});
}
Future<void> putAll<T extends Table, D extends DataClass>(
Iterable<Insertable<D>> items,
) async {
await batch((b) async {
putAllWithBatch(b, items);
});
}
void putAllWithBatch<T extends Table, D extends DataClass>(
Batch batch,
Iterable<Insertable<D>> items,
) {
batch.insertAllOnConflictUpdate(profiles, items);
}
void setAllWithBatch(Batch batch, Iterable<Profile> profiles) {
final List<ProfilesCompanion> items = [];
final List<int> ids = [];
profiles.forEachIndexed((index, profile) {
ids.add(profile.id);
items.add(profile.toCompanion(index));
});
this.profiles.setAll(batch, items, deleteFilter: (t) => t.id.isNotIn(ids));
}
}
class StringMapConverter extends TypeConverter<Map<String, String>, String> {
const StringMapConverter();
@override
Map<String, String> fromSql(String fromDb) {
return Map<String, String>.from(json.decode(fromDb));
}
@override
String toSql(Map<String, String> value) {
return json.encode(value);
}
}
class StringSetConverter extends TypeConverter<Set<String>, String> {
const StringSetConverter();
@override
Set<String> fromSql(String fromDb) {
return Set<String>.from(json.decode(fromDb));
}
@override
String toSql(Set<String> value) {
return json.encode(value.toList());
}
}
extension RawProfilExt on RawProfile {
Profile toProfile() {
return Profile(
id: id,
label: label,
currentGroupName: currentGroupName,
url: url,
lastUpdateDate: lastUpdateDate,
autoUpdateDuration: Duration(milliseconds: autoUpdateDurationMillis),
subscriptionInfo: subscriptionInfo,
autoUpdate: autoUpdate,
selectedMap: selectedMap,
unfoldSet: unfoldSet,
overwriteType: overwriteType,
scriptId: scriptId,
order: order,
);
}
}
extension ProfilesCompanionExt on Profile {
ProfilesCompanion toCompanion([int? order]) {
return ProfilesCompanion.insert(
id: Value(id),
label: label,
currentGroupName: Value(currentGroupName),
url: url,
lastUpdateDate: Value(lastUpdateDate),
autoUpdateDurationMillis: autoUpdateDuration.inMilliseconds,
subscriptionInfo: Value(subscriptionInfo),
autoUpdate: autoUpdate,
selectedMap: selectedMap,
unfoldSet: unfoldSet,
overwriteType: overwriteType,
scriptId: Value(scriptId),
order: Value(order ?? this.order),
);
}
}

View File

@@ -1,283 +0,0 @@
part of 'database.dart';
@DataClassName('RawRule')
class Rules extends Table {
@override
String get tableName => 'rules';
IntColumn get id => integer()();
TextColumn get value => text()();
@override
Set<Column> get primaryKey => {id};
}
@DriftAccessor(tables: [Rules, ProfileRuleLinks])
class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
RulesDao(super.attachedDatabase);
Selectable<Rule> allGlobalAddedRules() {
return _get();
}
Selectable<Rule> allProfileAddedRules(int profileId) {
return _get(profileId: profileId, scene: RuleScene.added);
}
Selectable<Rule> allProfileDisabledRules(int profileId) {
return _get(profileId: profileId, scene: RuleScene.disabled);
}
Selectable<Rule> allAddedRules(int profileId) {
final disabledIdsQuery = selectOnly(profileRuleLinks)
..addColumns([profileRuleLinks.ruleId])
..where(
profileRuleLinks.profileId.equals(profileId) &
profileRuleLinks.scene.equalsValue(RuleScene.disabled),
);
final query = select(rules).join([
innerJoin(profileRuleLinks, profileRuleLinks.ruleId.equalsExp(rules.id)),
]);
query.where(
(profileRuleLinks.profileId.isNull() |
(profileRuleLinks.profileId.equals(profileId) &
profileRuleLinks.scene.equalsValue(RuleScene.added))) &
profileRuleLinks.ruleId.isNotInQuery(disabledIdsQuery),
);
query.orderBy([
OrderingTerm.desc(
profileRuleLinks.profileId.isNull().caseMatch<int>(
when: {const Constant(true): const Constant(1)},
orElse: const Constant(0),
),
),
OrderingTerm.desc(profileRuleLinks.order),
]);
return query.map((row) {
final ruleData = row.readTable(rules);
final order = row.read(profileRuleLinks.order);
return ruleData.toRule(order);
});
}
void restoreWithBatch(
Batch batch,
Iterable<Rule> rules,
Iterable<ProfileRuleLink> links,
) {
batch.insertAllOnConflictUpdate(
this.rules,
rules.map((item) => item.toCompanion()),
);
final ruleIds = rules.map((item) => item.id);
batch.deleteWhere(this.rules, (t) => t.id.isNotIn(ruleIds));
batch.insertAllOnConflictUpdate(
profileRuleLinks,
links.map((item) => item.toCompanion()),
);
final linkKeys = links.map((item) => item.key);
batch.deleteWhere(profileRuleLinks, (t) => t.id.isNotIn(linkKeys));
}
Future<void> delRules(Iterable<int> ruleIds) {
return _delAll(ruleIds);
}
Future<void> putGlobalRule(Rule rule) {
return _put(rule);
}
Future<void> putProfileAddedRule(int profileId, Rule rule) {
return _put(rule, profileId: profileId, scene: RuleScene.added);
}
Future<void> putProfileDisabledRule(int profileId, Rule rule) {
return _put(rule, profileId: profileId, scene: RuleScene.added);
}
Future<void> putGlobalRules(Iterable<Rule> rules) {
return _putAll(rules);
}
Future<void> setGlobalRules(Iterable<Rule> rules) {
return _set(rules);
}
Future<int> putDisabledLink(int profileId, int ruleId) async {
return await profileRuleLinks.insertOnConflictUpdate(
ProfileRuleLink(
ruleId: ruleId,
profileId: profileId,
scene: RuleScene.disabled,
).toCompanion(),
);
}
Future<bool> delDisabledLink(int profileId, int ruleId) async {
return await profileRuleLinks.deleteOne(
ProfileRuleLink(
profileId: profileId,
ruleId: ruleId,
scene: RuleScene.disabled,
).toCompanion(),
);
}
Future<int> orderGlobalRule({
required int ruleId,
required String order,
}) async {
return await _order(ruleId: ruleId, order: order);
}
Future<int> orderProfileAddedRule(
int profileId, {
required int ruleId,
required String order,
}) async {
return await _order(
ruleId: ruleId,
order: order,
profileId: profileId,
scene: RuleScene.added,
);
}
Selectable<Rule> _get({int? profileId, RuleScene? scene}) {
final query = select(rules).join([
innerJoin(profileRuleLinks, profileRuleLinks.ruleId.equalsExp(rules.id)),
]);
query.where(
profileId == null
? profileRuleLinks.profileId.isNull()
: profileRuleLinks.profileId.equals(profileId) &
profileRuleLinks.scene.equalsValue(scene),
);
query.orderBy([
OrderingTerm.desc(profileRuleLinks.order),
OrderingTerm.desc(profileRuleLinks.id),
]);
return query.map((row) {
return row.readTable(rules).toRule(row.read(profileRuleLinks.order));
});
}
Future<int> _order({
required int ruleId,
required String order,
int? profileId,
RuleScene? scene,
}) async {
final stmt = profileRuleLinks.update();
stmt.where((t) {
return (profileId == null
? t.profileId.isNull()
: t.profileId.equals(profileId)) &
t.ruleId.equals(ruleId) &
t.scene.equalsValue(scene);
});
return await stmt.write(ProfileRuleLinksCompanion(order: Value(order)));
}
Future<int> _put(Rule rule, {int? profileId, RuleScene? scene}) async {
return transaction(() async {
final row = await rules.insertOnConflictUpdate(rule.toCompanion());
if (row == 0) {
return 0;
}
return await profileRuleLinks.insertOnConflictUpdate(
ProfileRuleLink(
ruleId: rule.id,
profileId: profileId,
scene: scene,
).toCompanion(),
);
});
}
Future<void> _delAll(Iterable<int> ruleIds) async {
await rules.deleteWhere((t) => t.id.isIn(ruleIds));
}
Future<void> _putAll(
Iterable<Rule> rules, {
int? profileId,
RuleScene? scene,
}) async {
await batch((b) {
b.insertAllOnConflictUpdate(
this.rules,
rules.map((item) => item.toCompanion()),
);
b.insertAllOnConflictUpdate(
profileRuleLinks,
rules.map(
(item) => ProfileRuleLink(
ruleId: item.id,
profileId: profileId,
scene: scene,
).toCompanion(),
),
);
});
}
Future<void> _set(
Iterable<Rule> rules, {
int? profileId,
RuleScene? scene,
}) async {
await batch((b) {
b.insertAllOnConflictUpdate(
this.rules,
rules.map((item) => item.toCompanion()),
);
b.deleteWhere(
profileRuleLinks,
(t) =>
(profileId == null
? t.profileId.isNull()
: t.profileId.equals(profileId)) &
(scene == null ? const Constant(true) : t.scene.equalsValue(scene)),
);
b.insertAllOnConflictUpdate(
profileRuleLinks,
rules.map(
(item) => ProfileRuleLink(
ruleId: item.id,
profileId: profileId,
scene: scene,
).toCompanion(),
),
);
b.deleteWhere(this.rules, (r) {
final linkedIds = selectOnly(profileRuleLinks);
linkedIds.addColumns([profileRuleLinks.ruleId]);
return r.id.isNotInQuery(linkedIds);
});
});
}
}
extension RawRuleExt on RawRule {
Rule toRule([String? order]) {
return Rule(id: id, value: value, order: order);
}
}
extension RulesCompanionExt on Rule {
RulesCompanion toCompanion() {
return RulesCompanion.insert(id: Value(id), value: value);
}
}

View File

@@ -1,63 +0,0 @@
part of 'database.dart';
@DataClassName('RawScript')
class Scripts extends Table {
@override
String get tableName => 'scripts';
IntColumn get id => integer()();
TextColumn get label => text()();
DateTimeColumn get lastUpdateTime => dateTime()();
@override
Set<Column> get primaryKey => {id};
}
@DriftAccessor(tables: [Scripts])
class ScriptsDao extends DatabaseAccessor<Database> with _$ScriptsDaoMixin {
ScriptsDao(super.attachedDatabase);
Selectable<Script> all() {
return scripts.select().map((item) => item.toScript());
}
Selectable<Script> get(int scriptId) {
final stmt = scripts.select();
stmt.where((t) => t.id.equals(scriptId));
return stmt.map((it) => it.toScript());
}
Future<void> setAll(Iterable<Script> scripts) async {
await batch((b) async {
await setAllWithBatch(b, scripts);
});
}
Future<void> setAllWithBatch(Batch batch, Iterable<Script> scripts) async {
final List<ScriptsCompanion> items = [];
final List<int> ids = [];
for (final script in scripts) {
ids.add(script.id);
items.add(script.toCompanion());
}
this.scripts.setAll(batch, items, deleteFilter: (t) => t.id.isNotIn(ids));
}
}
extension RawScriptExt on RawScript {
Script toScript() {
return Script(id: id, label: label, lastUpdateTime: lastUpdateTime);
}
}
extension ScriptsCompanionExt on Script {
ScriptsCompanion toCompanion() {
return ScriptsCompanion.insert(
id: Value(id),
label: label,
lastUpdateTime: lastUpdateTime,
);
}
}

View File

@@ -133,7 +133,7 @@ enum InvokeMessageType { protect, process }
enum FindProcessMode { always, off } enum FindProcessMode { always, off }
enum RestoreOption { all, onlyProfiles } enum RecoveryOption { all, onlyProfiles }
enum ChipType { action, delete } enum ChipType { action, delete }
@@ -197,7 +197,7 @@ extension KeyboardModifierExt on KeyboardModifier {
enum HotAction { start, view, mode, proxy, tun } enum HotAction { start, view, mode, proxy, tun }
enum ProxiesIconStyle { none, standard, icon } enum ProxiesIconStyle { standard, none, icon }
enum FontFamily { enum FontFamily {
twEmoji('Twemoji'), twEmoji('Twemoji'),
@@ -260,8 +260,8 @@ enum AuthorizeCode { none, success, error }
enum WindowsHelperServiceStatus { none, presence, running } enum WindowsHelperServiceStatus { none, presence, running }
enum FunctionTag { enum FunctionTag {
updateConfig, updateClashConfig,
setupConfig, setupClashConfig,
updateStatus, updateStatus,
updateGroups, updateGroups,
addCheckIpNum, addCheckIpNum,
@@ -280,8 +280,6 @@ enum FunctionTag {
logs, logs,
requests, requests,
autoScrollToEnd, autoScrollToEnd,
loadedProvider,
saveSharedFile,
} }
enum DashboardWidget { enum DashboardWidget {
@@ -407,7 +405,7 @@ enum OverwriteType {
enum RuleTarget { DIRECT, REJECT, MATCH } enum RuleTarget { DIRECT, REJECT, MATCH }
enum RestoreStrategy { compatible, override } enum RecoveryStrategy { compatible, override }
enum CacheTag { logs, rules, requests, proxiesList } enum CacheTag { logs, rules, requests, proxiesList }
@@ -417,10 +415,6 @@ enum ImportOption { file, url }
enum ScrollPositionCacheKey { tools, profiles, proxiesList, proxiesTabList } enum ScrollPositionCacheKey { tools, profiles, proxiesList, proxiesTabList }
enum QueryTag { proxies, access } enum QueryTag { proxies }
enum LoadingTag { profiles, backup_restore, access, proxies }
enum CoreStatus { connecting, connected, disconnected } enum CoreStatus { connecting, connected, disconnected }
enum RuleScene { added, disabled, custom }

View File

@@ -7,14 +7,13 @@ import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/card.dart'; import 'package:fl_clash/widgets/card.dart';
import 'package:fl_clash/widgets/dialog.dart'; import 'package:fl_clash/widgets/dialog.dart';
import 'package:fl_clash/widgets/input.dart'; import 'package:fl_clash/widgets/input.dart';
import 'package:fl_clash/widgets/list.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class RuleItem extends StatelessWidget { class RuleItem extends StatelessWidget {
final bool isSelected; final bool isSelected;
final bool isEditing; final bool isEditing;
final Rule rule; final Rule rule;
final void Function() onSelected; final void Function(String id) onSelected;
final void Function(Rule rule) onEdit; final void Function(Rule rule) onEdit;
const RuleItem({ const RuleItem({
@@ -28,18 +27,48 @@ class RuleItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CommonSelectedListItem( return Material(
isSelected: isSelected, color: Colors.transparent,
onSelected: () { child: Container(
onSelected(); margin: EdgeInsets.symmetric(vertical: 4),
}, child: CommonCard(
title: Text( padding: EdgeInsets.zero,
rule.value, radius: 18,
style: context.textTheme.bodyMedium?.toJetBrainsMono, type: CommonCardType.filled,
isSelected: isSelected,
onPressed: () {
if (isEditing) {
onSelected(rule.id);
return;
}
onEdit(rule);
},
onLongPress: () {
onSelected(rule.id);
},
child: ListTile(
minTileHeight: 0,
minVerticalPadding: 0,
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
trailing: SizedBox(
width: 24,
height: 24,
child: CommonCheckBox(
value: isSelected,
isCircle: true,
onChanged: (_) {
onSelected(rule.id);
},
),
),
title: Text(rule.value),
),
),
), ),
onPressed: () {
onEdit(rule);
},
); );
} }
} }
@@ -204,10 +233,6 @@ class _AddOrEditRuleDialogState extends State<AddOrEditRuleDialog> {
), ),
SizedBox(height: 24), SizedBox(height: 24),
TextFormField( TextFormField(
keyboardType: TextInputType.text,
onFieldSubmitted: (_) {
_handleSubmit();
},
controller: _contentController, controller: _contentController,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),

View File

@@ -81,7 +81,6 @@ class MessageLookup extends MessageLookupByLibrary {
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"), "action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
"action_view": MessageLookupByLibrary.simpleMessage("Show/Hide"), "action_view": MessageLookupByLibrary.simpleMessage("Show/Hide"),
"add": MessageLookupByLibrary.simpleMessage("Add"), "add": MessageLookupByLibrary.simpleMessage("Add"),
"addProfile": MessageLookupByLibrary.simpleMessage("Add Profile"),
"addRule": MessageLookupByLibrary.simpleMessage("Add rule"), "addRule": MessageLookupByLibrary.simpleMessage("Add rule"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage( "addedOriginRules": MessageLookupByLibrary.simpleMessage(
"Attach on the original rules", "Attach on the original rules",
@@ -165,11 +164,11 @@ class MessageLookup extends MessageLookupByLibrary {
"Auto update interval (minutes)", "Auto update interval (minutes)",
), ),
"backup": MessageLookupByLibrary.simpleMessage("Backup"), "backup": MessageLookupByLibrary.simpleMessage("Backup"),
"backupAndRestore": MessageLookupByLibrary.simpleMessage( "backupAndRecovery": MessageLookupByLibrary.simpleMessage(
"Backup and Restore", "Backup and Recovery",
), ),
"backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage( "backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Sync data via WebDAV or files", "Sync data via WebDAV or file",
), ),
"backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"), "backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"),
"basicConfig": MessageLookupByLibrary.simpleMessage("Basic configuration"), "basicConfig": MessageLookupByLibrary.simpleMessage("Basic configuration"),
@@ -270,7 +269,6 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("Default"), "defaultText": MessageLookupByLibrary.simpleMessage("Default"),
"delay": MessageLookupByLibrary.simpleMessage("Delay"), "delay": MessageLookupByLibrary.simpleMessage("Delay"),
"delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"), "delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"),
"delayTest": MessageLookupByLibrary.simpleMessage("Delay Test"),
"delete": MessageLookupByLibrary.simpleMessage("Delete"), "delete": MessageLookupByLibrary.simpleMessage("Delete"),
"deleteMultipTip": m1, "deleteMultipTip": m1,
"deleteTip": m2, "deleteTip": m2,
@@ -427,9 +425,6 @@ class MessageLookup extends MessageLookupByLibrary {
"internet": MessageLookupByLibrary.simpleMessage("Internet"), "internet": MessageLookupByLibrary.simpleMessage("Internet"),
"interval": MessageLookupByLibrary.simpleMessage("Interval"), "interval": MessageLookupByLibrary.simpleMessage("Interval"),
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"), "intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage(
"Invalid backup file",
),
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"), "ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage( "ipv6Desc": MessageLookupByLibrary.simpleMessage(
"When turned on it will be able to receive IPv6 traffic", "When turned on it will be able to receive IPv6 traffic",
@@ -450,11 +445,13 @@ class MessageLookup extends MessageLookupByLibrary {
"list": MessageLookupByLibrary.simpleMessage("List"), "list": MessageLookupByLibrary.simpleMessage("List"),
"listen": MessageLookupByLibrary.simpleMessage("Listen"), "listen": MessageLookupByLibrary.simpleMessage("Listen"),
"loadTest": MessageLookupByLibrary.simpleMessage("Load test"), "loadTest": MessageLookupByLibrary.simpleMessage("Load test"),
"loading": MessageLookupByLibrary.simpleMessage("Loading..."),
"local": MessageLookupByLibrary.simpleMessage("Local"), "local": MessageLookupByLibrary.simpleMessage("Local"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage( "localBackupDesc": MessageLookupByLibrary.simpleMessage(
"Backup local data to local", "Backup local data to local",
), ),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Recovery data from file",
),
"log": MessageLookupByLibrary.simpleMessage("Log"), "log": MessageLookupByLibrary.simpleMessage("Log"),
"logLevel": MessageLookupByLibrary.simpleMessage("LogLevel"), "logLevel": MessageLookupByLibrary.simpleMessage("LogLevel"),
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"), "logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
@@ -506,12 +503,6 @@ class MessageLookup extends MessageLookupByLibrary {
"networkDetection": MessageLookupByLibrary.simpleMessage( "networkDetection": MessageLookupByLibrary.simpleMessage(
"Network detection", "Network detection",
), ),
"networkException": MessageLookupByLibrary.simpleMessage(
"Network exception, please check your connection and try again",
),
"networkRequestException": MessageLookupByLibrary.simpleMessage(
"Network request exception, please try again later.",
),
"networkSpeed": MessageLookupByLibrary.simpleMessage("Network speed"), "networkSpeed": MessageLookupByLibrary.simpleMessage("Network speed"),
"networkType": MessageLookupByLibrary.simpleMessage("Network type"), "networkType": MessageLookupByLibrary.simpleMessage("Network type"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("Neutral"), "neutralScheme": MessageLookupByLibrary.simpleMessage("Neutral"),
@@ -572,10 +563,6 @@ class MessageLookup extends MessageLookupByLibrary {
"Override the original rule", "Override the original rule",
), ),
"overrideScript": MessageLookupByLibrary.simpleMessage("Override script"), "overrideScript": MessageLookupByLibrary.simpleMessage("Override script"),
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage("Custom"),
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
"Custom mode, fully customize proxy groups and rules",
),
"palette": MessageLookupByLibrary.simpleMessage("Palette"), "palette": MessageLookupByLibrary.simpleMessage("Palette"),
"password": MessageLookupByLibrary.simpleMessage("Password"), "password": MessageLookupByLibrary.simpleMessage("Password"),
"paste": MessageLookupByLibrary.simpleMessage("Paste"), "paste": MessageLookupByLibrary.simpleMessage("Paste"),
@@ -648,13 +635,27 @@ class MessageLookup extends MessageLookupByLibrary {
"Set the Clash listening port", "Set the Clash listening port",
), ),
"proxyProviders": MessageLookupByLibrary.simpleMessage("Proxy providers"), "proxyProviders": MessageLookupByLibrary.simpleMessage("Proxy providers"),
"pruneCache": MessageLookupByLibrary.simpleMessage("Prune cache"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Pure black mode"), "pureBlackMode": MessageLookupByLibrary.simpleMessage("Pure black mode"),
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"), "qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage( "qrcodeDesc": MessageLookupByLibrary.simpleMessage(
"Scan QR code to obtain profile", "Scan QR code to obtain profile",
), ),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("Rainbow"), "rainbowScheme": MessageLookupByLibrary.simpleMessage("Rainbow"),
"recovery": MessageLookupByLibrary.simpleMessage("Recovery"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("Recovery all data"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
"Only recovery profiles",
),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage(
"Recovery strategy",
),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage(
"Compatible",
),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
"Override",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("Recovery success"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir Port"), "redirPort": MessageLookupByLibrary.simpleMessage("Redir Port"),
"redo": MessageLookupByLibrary.simpleMessage("redo"), "redo": MessageLookupByLibrary.simpleMessage("redo"),
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"), "regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
@@ -666,6 +667,9 @@ class MessageLookup extends MessageLookupByLibrary {
"remoteDestination": MessageLookupByLibrary.simpleMessage( "remoteDestination": MessageLookupByLibrary.simpleMessage(
"Remote destination", "Remote destination",
), ),
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Recovery data from WebDAV",
),
"remove": MessageLookupByLibrary.simpleMessage("Remove"), "remove": MessageLookupByLibrary.simpleMessage("Remove"),
"rename": MessageLookupByLibrary.simpleMessage("Rename"), "rename": MessageLookupByLibrary.simpleMessage("Rename"),
"request": MessageLookupByLibrary.simpleMessage("Request"), "request": MessageLookupByLibrary.simpleMessage("Request"),
@@ -674,9 +678,6 @@ class MessageLookup extends MessageLookupByLibrary {
"View recently request records", "View recently request records",
), ),
"reset": MessageLookupByLibrary.simpleMessage("Reset"), "reset": MessageLookupByLibrary.simpleMessage("Reset"),
"resetPageChangesTip": MessageLookupByLibrary.simpleMessage(
"The current page has changes. Are you sure you want to reset?",
),
"resetTip": MessageLookupByLibrary.simpleMessage("Make sure to reset"), "resetTip": MessageLookupByLibrary.simpleMessage("Make sure to reset"),
"resources": MessageLookupByLibrary.simpleMessage("Resources"), "resources": MessageLookupByLibrary.simpleMessage("Resources"),
"resourcesDesc": MessageLookupByLibrary.simpleMessage( "resourcesDesc": MessageLookupByLibrary.simpleMessage(
@@ -690,28 +691,6 @@ class MessageLookup extends MessageLookupByLibrary {
"restartCoreTip": MessageLookupByLibrary.simpleMessage( "restartCoreTip": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to restart the core?", "Are you sure you want to restart the core?",
), ),
"restore": MessageLookupByLibrary.simpleMessage("Restore"),
"restoreAllData": MessageLookupByLibrary.simpleMessage("Restore all data"),
"restoreException": MessageLookupByLibrary.simpleMessage(
"Recovery exception",
),
"restoreFromFileDesc": MessageLookupByLibrary.simpleMessage(
"Restore data via file",
),
"restoreFromWebDAVDesc": MessageLookupByLibrary.simpleMessage(
"Restore data via WebDAV",
),
"restoreOnlyConfig": MessageLookupByLibrary.simpleMessage(
"Restore configuration files only",
),
"restoreStrategy": MessageLookupByLibrary.simpleMessage("Restore strategy"),
"restoreStrategy_compatible": MessageLookupByLibrary.simpleMessage(
"Compatible",
),
"restoreStrategy_override": MessageLookupByLibrary.simpleMessage(
"Override",
),
"restoreSuccess": MessageLookupByLibrary.simpleMessage("Restore success"),
"routeAddress": MessageLookupByLibrary.simpleMessage("Route address"), "routeAddress": MessageLookupByLibrary.simpleMessage("Route address"),
"routeAddressDesc": MessageLookupByLibrary.simpleMessage( "routeAddressDesc": MessageLookupByLibrary.simpleMessage(
"Config listen route address", "Config listen route address",
@@ -756,7 +735,6 @@ class MessageLookup extends MessageLookupByLibrary {
"sourceIp": MessageLookupByLibrary.simpleMessage("Source IP"), "sourceIp": MessageLookupByLibrary.simpleMessage("Source IP"),
"specialProxy": MessageLookupByLibrary.simpleMessage("Special proxy"), "specialProxy": MessageLookupByLibrary.simpleMessage("Special proxy"),
"specialRules": MessageLookupByLibrary.simpleMessage("special rules"), "specialRules": MessageLookupByLibrary.simpleMessage("special rules"),
"speedStatistics": MessageLookupByLibrary.simpleMessage("Speed statistics"),
"stackMode": MessageLookupByLibrary.simpleMessage("Stack mode"), "stackMode": MessageLookupByLibrary.simpleMessage("Stack mode"),
"standard": MessageLookupByLibrary.simpleMessage("Standard"), "standard": MessageLookupByLibrary.simpleMessage("Standard"),
"standardModeDesc": MessageLookupByLibrary.simpleMessage( "standardModeDesc": MessageLookupByLibrary.simpleMessage(
@@ -823,9 +801,6 @@ class MessageLookup extends MessageLookupByLibrary {
"Remove extra delays such as handshaking", "Remove extra delays such as handshaking",
), ),
"unknown": MessageLookupByLibrary.simpleMessage("Unknown"), "unknown": MessageLookupByLibrary.simpleMessage("Unknown"),
"unknownNetworkError": MessageLookupByLibrary.simpleMessage(
"Unknown network error",
),
"unnamed": MessageLookupByLibrary.simpleMessage("Unnamed"), "unnamed": MessageLookupByLibrary.simpleMessage("Unnamed"),
"update": MessageLookupByLibrary.simpleMessage("Update"), "update": MessageLookupByLibrary.simpleMessage("Update"),
"upload": MessageLookupByLibrary.simpleMessage("Upload"), "upload": MessageLookupByLibrary.simpleMessage("Upload"),

View File

@@ -72,7 +72,6 @@ class MessageLookup extends MessageLookupByLibrary {
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"), "action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
"action_view": MessageLookupByLibrary.simpleMessage("表示/非表示"), "action_view": MessageLookupByLibrary.simpleMessage("表示/非表示"),
"add": MessageLookupByLibrary.simpleMessage("追加"), "add": MessageLookupByLibrary.simpleMessage("追加"),
"addProfile": MessageLookupByLibrary.simpleMessage("プロファイルを追加"),
"addRule": MessageLookupByLibrary.simpleMessage("ルールを追加"), "addRule": MessageLookupByLibrary.simpleMessage("ルールを追加"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage("元のルールに追加"), "addedOriginRules": MessageLookupByLibrary.simpleMessage("元のルールに追加"),
"addedRules": MessageLookupByLibrary.simpleMessage("追加ルール"), "addedRules": MessageLookupByLibrary.simpleMessage("追加ルール"),
@@ -118,9 +117,9 @@ class MessageLookup extends MessageLookupByLibrary {
"autoUpdate": MessageLookupByLibrary.simpleMessage("自動更新"), "autoUpdate": MessageLookupByLibrary.simpleMessage("自動更新"),
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自動更新間隔(分)"), "autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自動更新間隔(分)"),
"backup": MessageLookupByLibrary.simpleMessage("バックアップ"), "backup": MessageLookupByLibrary.simpleMessage("バックアップ"),
"backupAndRestore": MessageLookupByLibrary.simpleMessage("バックアップと復元"), "backupAndRecovery": MessageLookupByLibrary.simpleMessage("バックアップと復元"),
"backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage( "backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"WebDAVまたはファイルを介してデータを同期する", "WebDAVまたはファイルデータを同期",
), ),
"backupSuccess": MessageLookupByLibrary.simpleMessage("バックアップ成功"), "backupSuccess": MessageLookupByLibrary.simpleMessage("バックアップ成功"),
"basicConfig": MessageLookupByLibrary.simpleMessage("基本設定"), "basicConfig": MessageLookupByLibrary.simpleMessage("基本設定"),
@@ -205,7 +204,6 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("デフォルト"), "defaultText": MessageLookupByLibrary.simpleMessage("デフォルト"),
"delay": MessageLookupByLibrary.simpleMessage("遅延"), "delay": MessageLookupByLibrary.simpleMessage("遅延"),
"delaySort": MessageLookupByLibrary.simpleMessage("遅延順"), "delaySort": MessageLookupByLibrary.simpleMessage("遅延順"),
"delayTest": MessageLookupByLibrary.simpleMessage("遅延テスト"),
"delete": MessageLookupByLibrary.simpleMessage("削除"), "delete": MessageLookupByLibrary.simpleMessage("削除"),
"deleteMultipTip": m1, "deleteMultipTip": m1,
"deleteTip": m2, "deleteTip": m2,
@@ -320,7 +318,6 @@ class MessageLookup extends MessageLookupByLibrary {
"internet": MessageLookupByLibrary.simpleMessage("インターネット"), "internet": MessageLookupByLibrary.simpleMessage("インターネット"),
"interval": MessageLookupByLibrary.simpleMessage("インターバル"), "interval": MessageLookupByLibrary.simpleMessage("インターバル"),
"intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"), "intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage("無効なバックアップファイル"),
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"), "ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"), "ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"),
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("IPv6インバウンドを許可"), "ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("IPv6インバウンドを許可"),
@@ -337,9 +334,9 @@ class MessageLookup extends MessageLookupByLibrary {
"list": MessageLookupByLibrary.simpleMessage("リスト"), "list": MessageLookupByLibrary.simpleMessage("リスト"),
"listen": MessageLookupByLibrary.simpleMessage("リスン"), "listen": MessageLookupByLibrary.simpleMessage("リスン"),
"loadTest": MessageLookupByLibrary.simpleMessage("読み込みテスト"), "loadTest": MessageLookupByLibrary.simpleMessage("読み込みテスト"),
"loading": MessageLookupByLibrary.simpleMessage("読み込み中..."),
"local": MessageLookupByLibrary.simpleMessage("ローカル"), "local": MessageLookupByLibrary.simpleMessage("ローカル"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage("ローカルにデータをバックアップ"), "localBackupDesc": MessageLookupByLibrary.simpleMessage("ローカルにデータをバックアップ"),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("ファイルからデータを復元"),
"log": MessageLookupByLibrary.simpleMessage("ログ"), "log": MessageLookupByLibrary.simpleMessage("ログ"),
"logLevel": MessageLookupByLibrary.simpleMessage("ログレベル"), "logLevel": MessageLookupByLibrary.simpleMessage("ログレベル"),
"logcat": MessageLookupByLibrary.simpleMessage("ログキャット"), "logcat": MessageLookupByLibrary.simpleMessage("ログキャット"),
@@ -377,12 +374,6 @@ class MessageLookup extends MessageLookupByLibrary {
"network": MessageLookupByLibrary.simpleMessage("ネットワーク"), "network": MessageLookupByLibrary.simpleMessage("ネットワーク"),
"networkDesc": MessageLookupByLibrary.simpleMessage("ネットワーク関連設定の変更"), "networkDesc": MessageLookupByLibrary.simpleMessage("ネットワーク関連設定の変更"),
"networkDetection": MessageLookupByLibrary.simpleMessage("ネットワーク検出"), "networkDetection": MessageLookupByLibrary.simpleMessage("ネットワーク検出"),
"networkException": MessageLookupByLibrary.simpleMessage(
"ネットワーク例外、接続を確認してもう一度お試しください",
),
"networkRequestException": MessageLookupByLibrary.simpleMessage(
"ネットワーク要求例外、後でもう一度試してください。",
),
"networkSpeed": MessageLookupByLibrary.simpleMessage("ネットワーク速度"), "networkSpeed": MessageLookupByLibrary.simpleMessage("ネットワーク速度"),
"networkType": MessageLookupByLibrary.simpleMessage("ネットワーク種別"), "networkType": MessageLookupByLibrary.simpleMessage("ネットワーク種別"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("ニュートラル"), "neutralScheme": MessageLookupByLibrary.simpleMessage("ニュートラル"),
@@ -431,10 +422,6 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideMode": MessageLookupByLibrary.simpleMessage("上書きモード"), "overrideMode": MessageLookupByLibrary.simpleMessage("上書きモード"),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを上書き"), "overrideOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを上書き"),
"overrideScript": MessageLookupByLibrary.simpleMessage("上書きスクリプト"), "overrideScript": MessageLookupByLibrary.simpleMessage("上書きスクリプト"),
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage("カスタム"),
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
"カスタムモード、プロキシグループとルールを完全にカスタマイズ可能",
),
"palette": MessageLookupByLibrary.simpleMessage("パレット"), "palette": MessageLookupByLibrary.simpleMessage("パレット"),
"password": MessageLookupByLibrary.simpleMessage("パスワード"), "password": MessageLookupByLibrary.simpleMessage("パスワード"),
"paste": MessageLookupByLibrary.simpleMessage("貼り付け"), "paste": MessageLookupByLibrary.simpleMessage("貼り付け"),
@@ -495,11 +482,19 @@ class MessageLookup extends MessageLookupByLibrary {
"proxyPort": MessageLookupByLibrary.simpleMessage("プロキシポート"), "proxyPort": MessageLookupByLibrary.simpleMessage("プロキシポート"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("Clashのリスニングポートを設定"), "proxyPortDesc": MessageLookupByLibrary.simpleMessage("Clashのリスニングポートを設定"),
"proxyProviders": MessageLookupByLibrary.simpleMessage("プロキシプロバイダー"), "proxyProviders": MessageLookupByLibrary.simpleMessage("プロキシプロバイダー"),
"pruneCache": MessageLookupByLibrary.simpleMessage("キャッシュの削除"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("純黒モード"), "pureBlackMode": MessageLookupByLibrary.simpleMessage("純黒モード"),
"qrcode": MessageLookupByLibrary.simpleMessage("QRコード"), "qrcode": MessageLookupByLibrary.simpleMessage("QRコード"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("QRコードをスキャンしてプロファイルを取得"), "qrcodeDesc": MessageLookupByLibrary.simpleMessage("QRコードをスキャンしてプロファイルを取得"),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("レインボー"), "rainbowScheme": MessageLookupByLibrary.simpleMessage("レインボー"),
"recovery": MessageLookupByLibrary.simpleMessage("復元"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("全データ復元"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("プロファイルのみ復元"),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage("リカバリー戦略"),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("互換性"),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
"オーバーライド",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("復元成功"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redirポート"), "redirPort": MessageLookupByLibrary.simpleMessage("Redirポート"),
"redo": MessageLookupByLibrary.simpleMessage("やり直す"), "redo": MessageLookupByLibrary.simpleMessage("やり直す"),
"regExp": MessageLookupByLibrary.simpleMessage("正規表現"), "regExp": MessageLookupByLibrary.simpleMessage("正規表現"),
@@ -509,15 +504,15 @@ class MessageLookup extends MessageLookupByLibrary {
"WebDAVにデータをバックアップ", "WebDAVにデータをバックアップ",
), ),
"remoteDestination": MessageLookupByLibrary.simpleMessage("リモート宛先"), "remoteDestination": MessageLookupByLibrary.simpleMessage("リモート宛先"),
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"WebDAVからデータを復元",
),
"remove": MessageLookupByLibrary.simpleMessage("削除"), "remove": MessageLookupByLibrary.simpleMessage("削除"),
"rename": MessageLookupByLibrary.simpleMessage("リネーム"), "rename": MessageLookupByLibrary.simpleMessage("リネーム"),
"request": MessageLookupByLibrary.simpleMessage("リクエスト"), "request": MessageLookupByLibrary.simpleMessage("リクエスト"),
"requests": MessageLookupByLibrary.simpleMessage("リクエスト"), "requests": MessageLookupByLibrary.simpleMessage("リクエスト"),
"requestsDesc": MessageLookupByLibrary.simpleMessage("最近のリクエスト記録を表示"), "requestsDesc": MessageLookupByLibrary.simpleMessage("最近のリクエスト記録を表示"),
"reset": MessageLookupByLibrary.simpleMessage("リセット"), "reset": MessageLookupByLibrary.simpleMessage("リセット"),
"resetPageChangesTip": MessageLookupByLibrary.simpleMessage(
"現在のページに変更があります。リセットしてもよろしいですか?",
),
"resetTip": MessageLookupByLibrary.simpleMessage("リセットを確定"), "resetTip": MessageLookupByLibrary.simpleMessage("リセットを確定"),
"resources": MessageLookupByLibrary.simpleMessage("リソース"), "resources": MessageLookupByLibrary.simpleMessage("リソース"),
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部リソース関連情報"), "resourcesDesc": MessageLookupByLibrary.simpleMessage("外部リソース関連情報"),
@@ -527,20 +522,6 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"restart": MessageLookupByLibrary.simpleMessage("再起動"), "restart": MessageLookupByLibrary.simpleMessage("再起動"),
"restartCoreTip": MessageLookupByLibrary.simpleMessage("コアを再起動してもよろしいですか?"), "restartCoreTip": MessageLookupByLibrary.simpleMessage("コアを再起動してもよろしいですか?"),
"restore": MessageLookupByLibrary.simpleMessage("復元"),
"restoreAllData": MessageLookupByLibrary.simpleMessage("すべてのデータを復元する"),
"restoreException": MessageLookupByLibrary.simpleMessage("復元例外"),
"restoreFromFileDesc": MessageLookupByLibrary.simpleMessage(
"ファイルを介してデータを復元する",
),
"restoreFromWebDAVDesc": MessageLookupByLibrary.simpleMessage(
"WebDAVを介してデータを復元する",
),
"restoreOnlyConfig": MessageLookupByLibrary.simpleMessage("設定ファイルのみを復元する"),
"restoreStrategy": MessageLookupByLibrary.simpleMessage("復元ストラテジー"),
"restoreStrategy_compatible": MessageLookupByLibrary.simpleMessage("互換"),
"restoreStrategy_override": MessageLookupByLibrary.simpleMessage("上書き"),
"restoreSuccess": MessageLookupByLibrary.simpleMessage("復元に成功しました"),
"routeAddress": MessageLookupByLibrary.simpleMessage("ルートアドレス"), "routeAddress": MessageLookupByLibrary.simpleMessage("ルートアドレス"),
"routeAddressDesc": MessageLookupByLibrary.simpleMessage("ルートアドレスを設定"), "routeAddressDesc": MessageLookupByLibrary.simpleMessage("ルートアドレスを設定"),
"routeMode": MessageLookupByLibrary.simpleMessage("ルートモード"), "routeMode": MessageLookupByLibrary.simpleMessage("ルートモード"),
@@ -577,7 +558,6 @@ class MessageLookup extends MessageLookupByLibrary {
"sourceIp": MessageLookupByLibrary.simpleMessage("送信元IP"), "sourceIp": MessageLookupByLibrary.simpleMessage("送信元IP"),
"specialProxy": MessageLookupByLibrary.simpleMessage("特殊プロキシ"), "specialProxy": MessageLookupByLibrary.simpleMessage("特殊プロキシ"),
"specialRules": MessageLookupByLibrary.simpleMessage("特殊ルール"), "specialRules": MessageLookupByLibrary.simpleMessage("特殊ルール"),
"speedStatistics": MessageLookupByLibrary.simpleMessage("速度統計"),
"stackMode": MessageLookupByLibrary.simpleMessage("スタックモード"), "stackMode": MessageLookupByLibrary.simpleMessage("スタックモード"),
"standard": MessageLookupByLibrary.simpleMessage("標準"), "standard": MessageLookupByLibrary.simpleMessage("標準"),
"standardModeDesc": MessageLookupByLibrary.simpleMessage( "standardModeDesc": MessageLookupByLibrary.simpleMessage(
@@ -634,7 +614,6 @@ class MessageLookup extends MessageLookupByLibrary {
"ハンドシェイクなどの余分な遅延を削除", "ハンドシェイクなどの余分な遅延を削除",
), ),
"unknown": MessageLookupByLibrary.simpleMessage("不明"), "unknown": MessageLookupByLibrary.simpleMessage("不明"),
"unknownNetworkError": MessageLookupByLibrary.simpleMessage("不明なネットワークエラー"),
"unnamed": MessageLookupByLibrary.simpleMessage("無題"), "unnamed": MessageLookupByLibrary.simpleMessage("無題"),
"update": MessageLookupByLibrary.simpleMessage("更新"), "update": MessageLookupByLibrary.simpleMessage("更新"),
"upload": MessageLookupByLibrary.simpleMessage("アップロード"), "upload": MessageLookupByLibrary.simpleMessage("アップロード"),

View File

@@ -80,7 +80,6 @@ class MessageLookup extends MessageLookupByLibrary {
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"), "action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
"action_view": MessageLookupByLibrary.simpleMessage("Показать/Скрыть"), "action_view": MessageLookupByLibrary.simpleMessage("Показать/Скрыть"),
"add": MessageLookupByLibrary.simpleMessage("Добавить"), "add": MessageLookupByLibrary.simpleMessage("Добавить"),
"addProfile": MessageLookupByLibrary.simpleMessage("Добавить профиль"),
"addRule": MessageLookupByLibrary.simpleMessage("Добавить правило"), "addRule": MessageLookupByLibrary.simpleMessage("Добавить правило"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage( "addedOriginRules": MessageLookupByLibrary.simpleMessage(
"Добавить к оригинальным правилам", "Добавить к оригинальным правилам",
@@ -162,11 +161,11 @@ class MessageLookup extends MessageLookupByLibrary {
"Интервал автообновления (минуты)", "Интервал автообновления (минуты)",
), ),
"backup": MessageLookupByLibrary.simpleMessage("Резервное копирование"), "backup": MessageLookupByLibrary.simpleMessage("Резервное копирование"),
"backupAndRestore": MessageLookupByLibrary.simpleMessage( "backupAndRecovery": MessageLookupByLibrary.simpleMessage(
"Резервное копирование и восстановление", "Резервное копирование и восстановление",
), ),
"backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage( "backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Синхронизация данных через WebDAV или файлы", "Синхронизация данных через WebDAV или файл",
), ),
"backupSuccess": MessageLookupByLibrary.simpleMessage( "backupSuccess": MessageLookupByLibrary.simpleMessage(
"Резервное копирование успешно", "Резервное копирование успешно",
@@ -277,7 +276,6 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("По умолчанию"), "defaultText": MessageLookupByLibrary.simpleMessage("По умолчанию"),
"delay": MessageLookupByLibrary.simpleMessage("Задержка"), "delay": MessageLookupByLibrary.simpleMessage("Задержка"),
"delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"), "delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"),
"delayTest": MessageLookupByLibrary.simpleMessage("Тест задержки"),
"delete": MessageLookupByLibrary.simpleMessage("Удалить"), "delete": MessageLookupByLibrary.simpleMessage("Удалить"),
"deleteMultipTip": m1, "deleteMultipTip": m1,
"deleteTip": m2, "deleteTip": m2,
@@ -446,9 +444,6 @@ class MessageLookup extends MessageLookupByLibrary {
"internet": MessageLookupByLibrary.simpleMessage("Интернет"), "internet": MessageLookupByLibrary.simpleMessage("Интернет"),
"interval": MessageLookupByLibrary.simpleMessage("Интервал"), "interval": MessageLookupByLibrary.simpleMessage("Интервал"),
"intranetIP": MessageLookupByLibrary.simpleMessage("Внутренний IP"), "intranetIP": MessageLookupByLibrary.simpleMessage("Внутренний IP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage(
"Неверный файл резервной копии",
),
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"), "ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage( "ipv6Desc": MessageLookupByLibrary.simpleMessage(
"При включении будет возможно получать IPv6 трафик", "При включении будет возможно получать IPv6 трафик",
@@ -469,11 +464,13 @@ class MessageLookup extends MessageLookupByLibrary {
"list": MessageLookupByLibrary.simpleMessage("Список"), "list": MessageLookupByLibrary.simpleMessage("Список"),
"listen": MessageLookupByLibrary.simpleMessage("Слушать"), "listen": MessageLookupByLibrary.simpleMessage("Слушать"),
"loadTest": MessageLookupByLibrary.simpleMessage("Тест загрузки"), "loadTest": MessageLookupByLibrary.simpleMessage("Тест загрузки"),
"loading": MessageLookupByLibrary.simpleMessage("Загрузка..."),
"local": MessageLookupByLibrary.simpleMessage("Локальный"), "local": MessageLookupByLibrary.simpleMessage("Локальный"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage( "localBackupDesc": MessageLookupByLibrary.simpleMessage(
"Резервное копирование локальных данных на локальный диск", "Резервное копирование локальных данных на локальный диск",
), ),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Восстановление данных из файла",
),
"log": MessageLookupByLibrary.simpleMessage("Журнал"), "log": MessageLookupByLibrary.simpleMessage("Журнал"),
"logLevel": MessageLookupByLibrary.simpleMessage("Уровень логов"), "logLevel": MessageLookupByLibrary.simpleMessage("Уровень логов"),
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"), "logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
@@ -529,12 +526,6 @@ class MessageLookup extends MessageLookupByLibrary {
"networkDetection": MessageLookupByLibrary.simpleMessage( "networkDetection": MessageLookupByLibrary.simpleMessage(
"Обнаружение сети", "Обнаружение сети",
), ),
"networkException": MessageLookupByLibrary.simpleMessage(
"Ошибка сети, проверьте соединение и попробуйте еще раз",
),
"networkRequestException": MessageLookupByLibrary.simpleMessage(
"Исключение сетевого запроса, пожалуйста, попробуйте позже.",
),
"networkSpeed": MessageLookupByLibrary.simpleMessage("Скорость сети"), "networkSpeed": MessageLookupByLibrary.simpleMessage("Скорость сети"),
"networkType": MessageLookupByLibrary.simpleMessage("Тип сети"), "networkType": MessageLookupByLibrary.simpleMessage("Тип сети"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("Нейтральные"), "neutralScheme": MessageLookupByLibrary.simpleMessage("Нейтральные"),
@@ -603,12 +594,6 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideScript": MessageLookupByLibrary.simpleMessage( "overrideScript": MessageLookupByLibrary.simpleMessage(
"Скрипт переопределения", "Скрипт переопределения",
), ),
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage(
"Пользовательский",
),
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
"Пользовательский режим, полная настройка групп прокси и правил",
),
"palette": MessageLookupByLibrary.simpleMessage("Палитра"), "palette": MessageLookupByLibrary.simpleMessage("Палитра"),
"password": MessageLookupByLibrary.simpleMessage("Пароль"), "password": MessageLookupByLibrary.simpleMessage("Пароль"),
"paste": MessageLookupByLibrary.simpleMessage("Вставить"), "paste": MessageLookupByLibrary.simpleMessage("Вставить"),
@@ -683,13 +668,31 @@ class MessageLookup extends MessageLookupByLibrary {
"Установить порт прослушивания Clash", "Установить порт прослушивания Clash",
), ),
"proxyProviders": MessageLookupByLibrary.simpleMessage("Провайдеры прокси"), "proxyProviders": MessageLookupByLibrary.simpleMessage("Провайдеры прокси"),
"pruneCache": MessageLookupByLibrary.simpleMessage("Очистить кэш"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Чисто черный режим"), "pureBlackMode": MessageLookupByLibrary.simpleMessage("Чисто черный режим"),
"qrcode": MessageLookupByLibrary.simpleMessage("QR-код"), "qrcode": MessageLookupByLibrary.simpleMessage("QR-код"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage( "qrcodeDesc": MessageLookupByLibrary.simpleMessage(
"Сканируйте QR-код для получения профиля", "Сканируйте QR-код для получения профиля",
), ),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("Радужные"), "rainbowScheme": MessageLookupByLibrary.simpleMessage("Радужные"),
"recovery": MessageLookupByLibrary.simpleMessage("Восстановление"),
"recoveryAll": MessageLookupByLibrary.simpleMessage(
"Восстановить все данные",
),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
"Только восстановление профилей",
),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage(
"Стратегия восстановления",
),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage(
"Совместимый",
),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
"Переопределение",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage(
"Восстановление успешно",
),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir-порт"), "redirPort": MessageLookupByLibrary.simpleMessage("Redir-порт"),
"redo": MessageLookupByLibrary.simpleMessage("Повторить"), "redo": MessageLookupByLibrary.simpleMessage("Повторить"),
"regExp": MessageLookupByLibrary.simpleMessage("Регулярное выражение"), "regExp": MessageLookupByLibrary.simpleMessage("Регулярное выражение"),
@@ -701,6 +704,9 @@ class MessageLookup extends MessageLookupByLibrary {
"remoteDestination": MessageLookupByLibrary.simpleMessage( "remoteDestination": MessageLookupByLibrary.simpleMessage(
"Удалённое назначение", "Удалённое назначение",
), ),
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Восстановление данных с WebDAV",
),
"remove": MessageLookupByLibrary.simpleMessage("Удалить"), "remove": MessageLookupByLibrary.simpleMessage("Удалить"),
"rename": MessageLookupByLibrary.simpleMessage("Переименовать"), "rename": MessageLookupByLibrary.simpleMessage("Переименовать"),
"request": MessageLookupByLibrary.simpleMessage("Запрос"), "request": MessageLookupByLibrary.simpleMessage("Запрос"),
@@ -709,9 +715,6 @@ class MessageLookup extends MessageLookupByLibrary {
"Просмотр последних записей запросов", "Просмотр последних записей запросов",
), ),
"reset": MessageLookupByLibrary.simpleMessage("Сброс"), "reset": MessageLookupByLibrary.simpleMessage("Сброс"),
"resetPageChangesTip": MessageLookupByLibrary.simpleMessage(
"На текущей странице есть изменения. Вы уверены, что хотите сбросить?",
),
"resetTip": MessageLookupByLibrary.simpleMessage( "resetTip": MessageLookupByLibrary.simpleMessage(
"Убедитесь, что хотите сбросить", "Убедитесь, что хотите сбросить",
), ),
@@ -727,34 +730,6 @@ class MessageLookup extends MessageLookupByLibrary {
"restartCoreTip": MessageLookupByLibrary.simpleMessage( "restartCoreTip": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите перезапустить ядро?", "Вы уверены, что хотите перезапустить ядро?",
), ),
"restore": MessageLookupByLibrary.simpleMessage("Восстановить"),
"restoreAllData": MessageLookupByLibrary.simpleMessage(
"Восстановить все данные",
),
"restoreException": MessageLookupByLibrary.simpleMessage(
"Ошибка восстановления",
),
"restoreFromFileDesc": MessageLookupByLibrary.simpleMessage(
"Восстановить данные из файла",
),
"restoreFromWebDAVDesc": MessageLookupByLibrary.simpleMessage(
"Восстановить данные через WebDAV",
),
"restoreOnlyConfig": MessageLookupByLibrary.simpleMessage(
"Восстановить только файлы конфигурации",
),
"restoreStrategy": MessageLookupByLibrary.simpleMessage(
"Стратегия восстановления",
),
"restoreStrategy_compatible": MessageLookupByLibrary.simpleMessage(
"Совместимый",
),
"restoreStrategy_override": MessageLookupByLibrary.simpleMessage(
"Перезаписать",
),
"restoreSuccess": MessageLookupByLibrary.simpleMessage(
"Восстановление успешно",
),
"routeAddress": MessageLookupByLibrary.simpleMessage("Адрес маршрутизации"), "routeAddress": MessageLookupByLibrary.simpleMessage("Адрес маршрутизации"),
"routeAddressDesc": MessageLookupByLibrary.simpleMessage( "routeAddressDesc": MessageLookupByLibrary.simpleMessage(
"Настройка адреса прослушивания маршрутизации", "Настройка адреса прослушивания маршрутизации",
@@ -799,9 +774,6 @@ class MessageLookup extends MessageLookupByLibrary {
"sourceIp": MessageLookupByLibrary.simpleMessage("Исходный IP"), "sourceIp": MessageLookupByLibrary.simpleMessage("Исходный IP"),
"specialProxy": MessageLookupByLibrary.simpleMessage("Специальный прокси"), "specialProxy": MessageLookupByLibrary.simpleMessage("Специальный прокси"),
"specialRules": MessageLookupByLibrary.simpleMessage("Специальные правила"), "specialRules": MessageLookupByLibrary.simpleMessage("Специальные правила"),
"speedStatistics": MessageLookupByLibrary.simpleMessage(
"Статистика скорости",
),
"stackMode": MessageLookupByLibrary.simpleMessage("Режим стека"), "stackMode": MessageLookupByLibrary.simpleMessage("Режим стека"),
"standard": MessageLookupByLibrary.simpleMessage("Стандартный"), "standard": MessageLookupByLibrary.simpleMessage("Стандартный"),
"standardModeDesc": MessageLookupByLibrary.simpleMessage( "standardModeDesc": MessageLookupByLibrary.simpleMessage(
@@ -872,9 +844,6 @@ class MessageLookup extends MessageLookupByLibrary {
"Убрать дополнительные задержки, такие как рукопожатие", "Убрать дополнительные задержки, такие как рукопожатие",
), ),
"unknown": MessageLookupByLibrary.simpleMessage("Неизвестно"), "unknown": MessageLookupByLibrary.simpleMessage("Неизвестно"),
"unknownNetworkError": MessageLookupByLibrary.simpleMessage(
"Неизвестная сетевая ошибка",
),
"unnamed": MessageLookupByLibrary.simpleMessage("Без имени"), "unnamed": MessageLookupByLibrary.simpleMessage("Без имени"),
"update": MessageLookupByLibrary.simpleMessage("Обновить"), "update": MessageLookupByLibrary.simpleMessage("Обновить"),
"upload": MessageLookupByLibrary.simpleMessage("Загрузка"), "upload": MessageLookupByLibrary.simpleMessage("Загрузка"),

View File

@@ -70,7 +70,6 @@ class MessageLookup extends MessageLookupByLibrary {
"action_tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"), "action_tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
"action_view": MessageLookupByLibrary.simpleMessage("显示/隐藏"), "action_view": MessageLookupByLibrary.simpleMessage("显示/隐藏"),
"add": MessageLookupByLibrary.simpleMessage("添加"), "add": MessageLookupByLibrary.simpleMessage("添加"),
"addProfile": MessageLookupByLibrary.simpleMessage("添加配置"),
"addRule": MessageLookupByLibrary.simpleMessage("添加规则"), "addRule": MessageLookupByLibrary.simpleMessage("添加规则"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage("附加到原始规则"), "addedOriginRules": MessageLookupByLibrary.simpleMessage("附加到原始规则"),
"addedRules": MessageLookupByLibrary.simpleMessage("附加规则"), "addedRules": MessageLookupByLibrary.simpleMessage("附加规则"),
@@ -110,8 +109,8 @@ class MessageLookup extends MessageLookupByLibrary {
"autoUpdate": MessageLookupByLibrary.simpleMessage("自动更新"), "autoUpdate": MessageLookupByLibrary.simpleMessage("自动更新"),
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自动更新间隔(分钟)"), "autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自动更新间隔(分钟)"),
"backup": MessageLookupByLibrary.simpleMessage("备份"), "backup": MessageLookupByLibrary.simpleMessage("备份"),
"backupAndRestore": MessageLookupByLibrary.simpleMessage("备份与恢复"), "backupAndRecovery": MessageLookupByLibrary.simpleMessage("备份与恢复"),
"backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage( "backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"通过WebDAV或者文件同步数据", "通过WebDAV或者文件同步数据",
), ),
"backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"), "backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"),
@@ -185,7 +184,6 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("默认"), "defaultText": MessageLookupByLibrary.simpleMessage("默认"),
"delay": MessageLookupByLibrary.simpleMessage("延迟"), "delay": MessageLookupByLibrary.simpleMessage("延迟"),
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"), "delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
"delayTest": MessageLookupByLibrary.simpleMessage("延迟测试"),
"delete": MessageLookupByLibrary.simpleMessage("删除"), "delete": MessageLookupByLibrary.simpleMessage("删除"),
"deleteMultipTip": m1, "deleteMultipTip": m1,
"deleteTip": m2, "deleteTip": m2,
@@ -286,7 +284,6 @@ class MessageLookup extends MessageLookupByLibrary {
"internet": MessageLookupByLibrary.simpleMessage("互联网"), "internet": MessageLookupByLibrary.simpleMessage("互联网"),
"interval": MessageLookupByLibrary.simpleMessage("间隔"), "interval": MessageLookupByLibrary.simpleMessage("间隔"),
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"), "intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage("无效备份文件"),
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"), "ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"), "ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"), "ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"),
@@ -301,9 +298,9 @@ class MessageLookup extends MessageLookupByLibrary {
"list": MessageLookupByLibrary.simpleMessage("列表"), "list": MessageLookupByLibrary.simpleMessage("列表"),
"listen": MessageLookupByLibrary.simpleMessage("监听"), "listen": MessageLookupByLibrary.simpleMessage("监听"),
"loadTest": MessageLookupByLibrary.simpleMessage("加载测试"), "loadTest": MessageLookupByLibrary.simpleMessage("加载测试"),
"loading": MessageLookupByLibrary.simpleMessage("加载中..."),
"local": MessageLookupByLibrary.simpleMessage("本地"), "local": MessageLookupByLibrary.simpleMessage("本地"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"), "localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),
"log": MessageLookupByLibrary.simpleMessage("日志"), "log": MessageLookupByLibrary.simpleMessage("日志"),
"logLevel": MessageLookupByLibrary.simpleMessage("日志等级"), "logLevel": MessageLookupByLibrary.simpleMessage("日志等级"),
"logcat": MessageLookupByLibrary.simpleMessage("日志捕获"), "logcat": MessageLookupByLibrary.simpleMessage("日志捕获"),
@@ -337,10 +334,6 @@ class MessageLookup extends MessageLookupByLibrary {
"network": MessageLookupByLibrary.simpleMessage("网络"), "network": MessageLookupByLibrary.simpleMessage("网络"),
"networkDesc": MessageLookupByLibrary.simpleMessage("修改网络相关设置"), "networkDesc": MessageLookupByLibrary.simpleMessage("修改网络相关设置"),
"networkDetection": MessageLookupByLibrary.simpleMessage("网络检测"), "networkDetection": MessageLookupByLibrary.simpleMessage("网络检测"),
"networkException": MessageLookupByLibrary.simpleMessage("网络异常,请检查连接后重试"),
"networkRequestException": MessageLookupByLibrary.simpleMessage(
"网络请求异常,请稍后再试。",
),
"networkSpeed": MessageLookupByLibrary.simpleMessage("网络速度"), "networkSpeed": MessageLookupByLibrary.simpleMessage("网络速度"),
"networkType": MessageLookupByLibrary.simpleMessage("网络类型"), "networkType": MessageLookupByLibrary.simpleMessage("网络类型"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("中性"), "neutralScheme": MessageLookupByLibrary.simpleMessage("中性"),
@@ -379,10 +372,6 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideMode": MessageLookupByLibrary.simpleMessage("覆写模式"), "overrideMode": MessageLookupByLibrary.simpleMessage("覆写模式"),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("覆盖原始规则"), "overrideOriginRules": MessageLookupByLibrary.simpleMessage("覆盖原始规则"),
"overrideScript": MessageLookupByLibrary.simpleMessage("覆写脚本"), "overrideScript": MessageLookupByLibrary.simpleMessage("覆写脚本"),
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage("自定义"),
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
"自定义模式,支持完全自定义修改代理组以及规则",
),
"palette": MessageLookupByLibrary.simpleMessage("调色板"), "palette": MessageLookupByLibrary.simpleMessage("调色板"),
"password": MessageLookupByLibrary.simpleMessage("密码"), "password": MessageLookupByLibrary.simpleMessage("密码"),
"paste": MessageLookupByLibrary.simpleMessage("粘贴"), "paste": MessageLookupByLibrary.simpleMessage("粘贴"),
@@ -433,11 +422,17 @@ class MessageLookup extends MessageLookupByLibrary {
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"), "proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"), "proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
"proxyProviders": MessageLookupByLibrary.simpleMessage("代理提供者"), "proxyProviders": MessageLookupByLibrary.simpleMessage("代理提供者"),
"pruneCache": MessageLookupByLibrary.simpleMessage("修剪缓存"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"), "pureBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"), "qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"), "qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("彩虹"), "rainbowScheme": MessageLookupByLibrary.simpleMessage("彩虹"),
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage("恢复策略"),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("兼容"),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage("覆盖"),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir端口"), "redirPort": MessageLookupByLibrary.simpleMessage("Redir端口"),
"redo": MessageLookupByLibrary.simpleMessage("重做"), "redo": MessageLookupByLibrary.simpleMessage("重做"),
"regExp": MessageLookupByLibrary.simpleMessage("正则"), "regExp": MessageLookupByLibrary.simpleMessage("正则"),
@@ -445,15 +440,13 @@ class MessageLookup extends MessageLookupByLibrary {
"remote": MessageLookupByLibrary.simpleMessage("远程"), "remote": MessageLookupByLibrary.simpleMessage("远程"),
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"), "remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
"remoteDestination": MessageLookupByLibrary.simpleMessage("远程目标"), "remoteDestination": MessageLookupByLibrary.simpleMessage("远程目标"),
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过WebDAV恢复数据"),
"remove": MessageLookupByLibrary.simpleMessage("移除"), "remove": MessageLookupByLibrary.simpleMessage("移除"),
"rename": MessageLookupByLibrary.simpleMessage("重命名"), "rename": MessageLookupByLibrary.simpleMessage("重命名"),
"request": MessageLookupByLibrary.simpleMessage("请求"), "request": MessageLookupByLibrary.simpleMessage("请求"),
"requests": MessageLookupByLibrary.simpleMessage("请求"), "requests": MessageLookupByLibrary.simpleMessage("请求"),
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"), "requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"),
"reset": MessageLookupByLibrary.simpleMessage("重置"), "reset": MessageLookupByLibrary.simpleMessage("重置"),
"resetPageChangesTip": MessageLookupByLibrary.simpleMessage(
"当前页面存在更改,确定重置吗?",
),
"resetTip": MessageLookupByLibrary.simpleMessage("确定要重置吗?"), "resetTip": MessageLookupByLibrary.simpleMessage("确定要重置吗?"),
"resources": MessageLookupByLibrary.simpleMessage("资源"), "resources": MessageLookupByLibrary.simpleMessage("资源"),
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"), "resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"),
@@ -463,18 +456,6 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"restart": MessageLookupByLibrary.simpleMessage("重启"), "restart": MessageLookupByLibrary.simpleMessage("重启"),
"restartCoreTip": MessageLookupByLibrary.simpleMessage("您确定要重启核心吗?"), "restartCoreTip": MessageLookupByLibrary.simpleMessage("您确定要重启核心吗?"),
"restore": MessageLookupByLibrary.simpleMessage("恢复"),
"restoreAllData": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
"restoreException": MessageLookupByLibrary.simpleMessage("恢复异常"),
"restoreFromFileDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),
"restoreFromWebDAVDesc": MessageLookupByLibrary.simpleMessage(
"通过WebDAV恢复数据",
),
"restoreOnlyConfig": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
"restoreStrategy": MessageLookupByLibrary.simpleMessage("恢复策略"),
"restoreStrategy_compatible": MessageLookupByLibrary.simpleMessage("兼容"),
"restoreStrategy_override": MessageLookupByLibrary.simpleMessage("覆盖"),
"restoreSuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
"routeAddress": MessageLookupByLibrary.simpleMessage("路由地址"), "routeAddress": MessageLookupByLibrary.simpleMessage("路由地址"),
"routeAddressDesc": MessageLookupByLibrary.simpleMessage("配置监听路由地址"), "routeAddressDesc": MessageLookupByLibrary.simpleMessage("配置监听路由地址"),
"routeMode": MessageLookupByLibrary.simpleMessage("路由模式"), "routeMode": MessageLookupByLibrary.simpleMessage("路由模式"),
@@ -509,7 +490,6 @@ class MessageLookup extends MessageLookupByLibrary {
"sourceIp": MessageLookupByLibrary.simpleMessage("源IP"), "sourceIp": MessageLookupByLibrary.simpleMessage("源IP"),
"specialProxy": MessageLookupByLibrary.simpleMessage("特殊代理"), "specialProxy": MessageLookupByLibrary.simpleMessage("特殊代理"),
"specialRules": MessageLookupByLibrary.simpleMessage("特殊规则"), "specialRules": MessageLookupByLibrary.simpleMessage("特殊规则"),
"speedStatistics": MessageLookupByLibrary.simpleMessage("网速统计"),
"stackMode": MessageLookupByLibrary.simpleMessage("栈模式"), "stackMode": MessageLookupByLibrary.simpleMessage("栈模式"),
"standard": MessageLookupByLibrary.simpleMessage("标准"), "standard": MessageLookupByLibrary.simpleMessage("标准"),
"standardModeDesc": MessageLookupByLibrary.simpleMessage( "standardModeDesc": MessageLookupByLibrary.simpleMessage(
@@ -562,7 +542,6 @@ class MessageLookup extends MessageLookupByLibrary {
"unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"), "unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"),
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"), "unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"),
"unknown": MessageLookupByLibrary.simpleMessage("未知"), "unknown": MessageLookupByLibrary.simpleMessage("未知"),
"unknownNetworkError": MessageLookupByLibrary.simpleMessage("未知网络错误"),
"unnamed": MessageLookupByLibrary.simpleMessage("未命名"), "unnamed": MessageLookupByLibrary.simpleMessage("未命名"),
"update": MessageLookupByLibrary.simpleMessage("更新"), "update": MessageLookupByLibrary.simpleMessage("更新"),
"upload": MessageLookupByLibrary.simpleMessage("上传"), "upload": MessageLookupByLibrary.simpleMessage("上传"),

View File

@@ -999,6 +999,26 @@ class AppLocalizations {
return Intl.message('tip', name: 'tip', desc: '', args: []); return Intl.message('tip', name: 'tip', desc: '', args: []);
} }
/// `Backup and Recovery`
String get backupAndRecovery {
return Intl.message(
'Backup and Recovery',
name: 'backupAndRecovery',
desc: '',
args: [],
);
}
/// `Sync data via WebDAV or file`
String get backupAndRecoveryDesc {
return Intl.message(
'Sync data via WebDAV or file',
name: 'backupAndRecoveryDesc',
desc: '',
args: [],
);
}
/// `Account` /// `Account`
String get account { String get account {
return Intl.message('Account', name: 'account', desc: '', args: []); return Intl.message('Account', name: 'account', desc: '', args: []);
@@ -1009,6 +1029,41 @@ class AppLocalizations {
return Intl.message('Backup', name: 'backup', desc: '', args: []); return Intl.message('Backup', name: 'backup', desc: '', args: []);
} }
/// `Recovery`
String get recovery {
return Intl.message('Recovery', name: 'recovery', desc: '', args: []);
}
/// `Only recovery profiles`
String get recoveryProfiles {
return Intl.message(
'Only recovery profiles',
name: 'recoveryProfiles',
desc: '',
args: [],
);
}
/// `Recovery all data`
String get recoveryAll {
return Intl.message(
'Recovery all data',
name: 'recoveryAll',
desc: '',
args: [],
);
}
/// `Recovery success`
String get recoverySuccess {
return Intl.message(
'Recovery success',
name: 'recoverySuccess',
desc: '',
args: [],
);
}
/// `Backup success` /// `Backup success`
String get backupSuccess { String get backupSuccess {
return Intl.message( return Intl.message(
@@ -1634,6 +1689,16 @@ class AppLocalizations {
); );
} }
/// `Recovery data from WebDAV`
String get remoteRecoveryDesc {
return Intl.message(
'Recovery data from WebDAV',
name: 'remoteRecoveryDesc',
desc: '',
args: [],
);
}
/// `Backup local data to local` /// `Backup local data to local`
String get localBackupDesc { String get localBackupDesc {
return Intl.message( return Intl.message(
@@ -1644,6 +1709,16 @@ class AppLocalizations {
); );
} }
/// `Recovery data from file`
String get localRecoveryDesc {
return Intl.message(
'Recovery data from file',
name: 'localRecoveryDesc',
desc: '',
args: [],
);
}
/// `Mode` /// `Mode`
String get mode { String get mode {
return Intl.message('Mode', name: 'mode', desc: '', args: []); return Intl.message('Mode', name: 'mode', desc: '', args: []);
@@ -2859,31 +2934,31 @@ class AppLocalizations {
return Intl.message('Contact me', name: 'contactMe', desc: '', args: []); return Intl.message('Contact me', name: 'contactMe', desc: '', args: []);
} }
/// `Restore strategy` /// `Recovery strategy`
String get restoreStrategy { String get recoveryStrategy {
return Intl.message( return Intl.message(
'Restore strategy', 'Recovery strategy',
name: 'restoreStrategy', name: 'recoveryStrategy',
desc: '', desc: '',
args: [], args: [],
); );
} }
/// `Override` /// `Override`
String get restoreStrategy_override { String get recoveryStrategy_override {
return Intl.message( return Intl.message(
'Override', 'Override',
name: 'restoreStrategy_override', name: 'recoveryStrategy_override',
desc: '', desc: '',
args: [], args: [],
); );
} }
/// `Compatible` /// `Compatible`
String get restoreStrategy_compatible { String get recoveryStrategy_compatible {
return Intl.message( return Intl.message(
'Compatible', 'Compatible',
name: 'restoreStrategy_compatible', name: 'recoveryStrategy_compatible',
desc: '', desc: '',
args: [], args: [],
); );
@@ -3434,11 +3509,6 @@ class AppLocalizations {
); );
} }
/// `Loading...`
String get loading {
return Intl.message('Loading...', name: 'loading', desc: '', args: []);
}
/// `Load test` /// `Load test`
String get loadTest { String get loadTest {
return Intl.message('Load test', name: 'loadTest', desc: '', args: []); return Intl.message('Load test', name: 'loadTest', desc: '', args: []);
@@ -3568,186 +3638,6 @@ class AppLocalizations {
String get restart { String get restart {
return Intl.message('Restart', name: 'restart', desc: '', args: []); return Intl.message('Restart', name: 'restart', desc: '', args: []);
} }
/// `Speed statistics`
String get speedStatistics {
return Intl.message(
'Speed statistics',
name: 'speedStatistics',
desc: '',
args: [],
);
}
/// `The current page has changes. Are you sure you want to reset?`
String get resetPageChangesTip {
return Intl.message(
'The current page has changes. Are you sure you want to reset?',
name: 'resetPageChangesTip',
desc: '',
args: [],
);
}
/// `Custom`
String get overwriteTypeCustom {
return Intl.message(
'Custom',
name: 'overwriteTypeCustom',
desc: '',
args: [],
);
}
/// `Custom mode, fully customize proxy groups and rules`
String get overwriteTypeCustomDesc {
return Intl.message(
'Custom mode, fully customize proxy groups and rules',
name: 'overwriteTypeCustomDesc',
desc: '',
args: [],
);
}
/// `Unknown network error`
String get unknownNetworkError {
return Intl.message(
'Unknown network error',
name: 'unknownNetworkError',
desc: '',
args: [],
);
}
/// `Network request exception, please try again later.`
String get networkRequestException {
return Intl.message(
'Network request exception, please try again later.',
name: 'networkRequestException',
desc: '',
args: [],
);
}
/// `Recovery exception`
String get restoreException {
return Intl.message(
'Recovery exception',
name: 'restoreException',
desc: '',
args: [],
);
}
/// `Network exception, please check your connection and try again`
String get networkException {
return Intl.message(
'Network exception, please check your connection and try again',
name: 'networkException',
desc: '',
args: [],
);
}
/// `Invalid backup file`
String get invalidBackupFile {
return Intl.message(
'Invalid backup file',
name: 'invalidBackupFile',
desc: '',
args: [],
);
}
/// `Prune cache`
String get pruneCache {
return Intl.message('Prune cache', name: 'pruneCache', desc: '', args: []);
}
/// `Backup and Restore`
String get backupAndRestore {
return Intl.message(
'Backup and Restore',
name: 'backupAndRestore',
desc: '',
args: [],
);
}
/// `Sync data via WebDAV or files`
String get backupAndRestoreDesc {
return Intl.message(
'Sync data via WebDAV or files',
name: 'backupAndRestoreDesc',
desc: '',
args: [],
);
}
/// `Restore`
String get restore {
return Intl.message('Restore', name: 'restore', desc: '', args: []);
}
/// `Restore success`
String get restoreSuccess {
return Intl.message(
'Restore success',
name: 'restoreSuccess',
desc: '',
args: [],
);
}
/// `Restore data via WebDAV`
String get restoreFromWebDAVDesc {
return Intl.message(
'Restore data via WebDAV',
name: 'restoreFromWebDAVDesc',
desc: '',
args: [],
);
}
/// `Restore data via file`
String get restoreFromFileDesc {
return Intl.message(
'Restore data via file',
name: 'restoreFromFileDesc',
desc: '',
args: [],
);
}
/// `Restore configuration files only`
String get restoreOnlyConfig {
return Intl.message(
'Restore configuration files only',
name: 'restoreOnlyConfig',
desc: '',
args: [],
);
}
/// `Restore all data`
String get restoreAllData {
return Intl.message(
'Restore all data',
name: 'restoreAllData',
desc: '',
args: [],
);
}
/// `Add Profile`
String get addProfile {
return Intl.message('Add Profile', name: 'addProfile', desc: '', args: []);
}
/// `Delay Test`
String get delayTest {
return Intl.message('Delay Test', name: 'delayTest', desc: '', args: []);
}
} }
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> { class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -1,31 +1,65 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/pages/error.dart'; import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/tile.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'application.dart'; import 'application.dart';
import 'common/common.dart'; import 'common/common.dart';
import 'core/controller.dart';
Future<void> main() async { Future<void> main() async {
try { WidgetsFlutterBinding.ensureInitialized();
WidgetsFlutterBinding.ensureInitialized(); final version = await system.version;
final version = await system.version; await globalState.initApp(version);
final container = await globalState.init(version); HttpOverrides.global = FlClashHttpOverrides();
HttpOverrides.global = FlClashHttpOverrides(); runApp(ProviderScope(child: const Application()));
runApp( }
UncontrolledProviderScope(
container: container, @pragma('vm:entry-point')
child: const Application(), Future<void> _service(List<String> flags) async {
), WidgetsFlutterBinding.ensureInitialized();
); globalState.isService = true;
} catch (e, s) { await globalState.init();
return runApp( await coreController.preload();
MaterialApp( tile?.addListener(
home: InitErrorScreen(error: e, stack: s), _TileListenerWithService(
), onStop: () async {
); await app?.tip(appLocalizations.stopVpn);
await globalState.handleStop();
},
),
);
app?.tip(appLocalizations.startVpn);
final version = await system.version;
await coreController.init(version);
final clashConfig = globalState.config.patchClashConfig.copyWith.tun(
enable: false,
);
final setupState = globalState.getSetupState(
globalState.config.currentProfileId,
);
globalState.setupConfig(
setupState: setupState,
patchConfig: clashConfig,
preloadInvoke: () {
globalState.handleStart();
},
);
}
@immutable
class _TileListenerWithService with TileListener {
final Function() _onStop;
const _TileListenerWithService({required Function() onStop})
: _onStop = onStop;
@override
void onStop() {
_onStop();
} }
} }

View File

@@ -1,7 +1,6 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/core/core.dart'; import 'package:fl_clash/core/core.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/core.dart';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/service.dart'; import 'package:fl_clash/plugins/service.dart';
import 'package:fl_clash/providers/providers.dart'; import 'package:fl_clash/providers/providers.dart';
@@ -28,14 +27,9 @@ class _AndroidContainerState extends ConsumerState<AndroidManager>
) { ) {
app?.updateExcludeFromRecents(next); app?.updateExcludeFromRecents(next);
}, fireImmediately: true); }, fireImmediately: true);
ref.listenManual(sharedStateProvider, (prev, next) { ref.listenManual(androidStateProvider, (prev, next) {
if (prev != next) { if (prev != next) {
debouncer.call(FunctionTag.saveSharedFile, () async { service?.syncAndroidState(next);
preferences.saveShareState(next);
}, duration: Duration(seconds: 1));
if (prev?.needSyncSharedState != next.needSyncSharedState) {
service?.syncState(next.needSyncSharedState);
}
} }
}); });
service?.addListener(this); service?.addListener(this);

Some files were not shown because too many files have changed in this diff Show More