Compare commits
7 Commits
v0.8.78
...
v0.8.81-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8c0519ffe | ||
|
|
de9c5ba9cc | ||
|
|
2aae00cf68 | ||
|
|
68be2d34a1 | ||
|
|
7895ccf720 | ||
|
|
e92900dbbd | ||
|
|
eada271c49 |
22
.github/workflows/build.yaml
vendored
22
.github/workflows/build.yaml
vendored
@@ -4,6 +4,8 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
env:
|
||||
IS_STABLE: ${{ !contains(github.ref, '-') }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -67,7 +69,6 @@ jobs:
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: 3.24.5
|
||||
channel: stable
|
||||
cache: true
|
||||
|
||||
@@ -75,7 +76,7 @@ jobs:
|
||||
run: flutter pub get
|
||||
|
||||
- name: Setup
|
||||
run: dart setup.dart ${{ matrix.platform }} ${{ matrix.arch && format('--arch {0}', matrix.arch) }}
|
||||
run: dart setup.dart ${{ matrix.platform }} ${{ matrix.arch && format('--arch {0}', matrix.arch) }} ${{ env.IS_STABLE == 'true' && '--env stable' || '' }}
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -89,14 +90,13 @@ jobs:
|
||||
needs: [ build ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
if: ${{ !contains(github.ref, '+') }}
|
||||
uses: actions/checkout@v4
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: refs/heads/main
|
||||
|
||||
- name: Generate
|
||||
if: ${{ !contains(github.ref, '+') }}
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
run: |
|
||||
tags=($(git tag --merged $(git rev-parse HEAD) --sort=-creatordate))
|
||||
preTag=$(grep -oP '^## \K.*' CHANGELOG.md | head -n 1)
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
cat NEW_CHANGELOG.md > CHANGELOG.md
|
||||
|
||||
- name: Commit
|
||||
if: ${{ !contains(github.ref, '+') }}
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
run: |
|
||||
git add CHANGELOG.md
|
||||
if ! git diff --cached --quiet; then
|
||||
@@ -207,7 +207,7 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
python release.py
|
||||
python release_telegram.py
|
||||
|
||||
- name: Patch release.md
|
||||
run: |
|
||||
@@ -215,21 +215,21 @@ jobs:
|
||||
sed "s|VERSION|$version|g" ./.github/release_template.md >> release.md
|
||||
|
||||
- name: Release
|
||||
if: ${{ !contains(github.ref, '+') }}
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ./dist/*
|
||||
body_path: './release.md'
|
||||
|
||||
- name: Create Fdroid Source Dir
|
||||
if: ${{ !contains(github.ref, '+') }}
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
run: |
|
||||
mkdir -p ./tmp
|
||||
cp ./dist/*android-arm64-v8a* ./tmp/ || true
|
||||
echo "Files copied successfully"
|
||||
|
||||
- name: Push to fdroid repo
|
||||
if: ${{ !contains(github.ref, '+') }}
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
uses: cpina/github-action-push-to-another-repository@v1.7.2
|
||||
env:
|
||||
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
|
||||
@@ -239,7 +239,7 @@ jobs:
|
||||
destination-repository-name: FlClash-fdroid-repo
|
||||
user-name: 'github-actions[bot]'
|
||||
user-email: 'github-actions[bot]@users.noreply.github.com'
|
||||
target-branch: action-pr
|
||||
target-branch: main
|
||||
commit-message: Update from ${{ github.ref_name }}
|
||||
target-directory: /tmp/
|
||||
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,7 +1,7 @@
|
||||
[submodule "core/Clash.Meta"]
|
||||
path = core/Clash.Meta
|
||||
url = git@github.com:chen08209/Clash.Meta.git
|
||||
branch = FlClash-Alpha
|
||||
branch = FlClash
|
||||
[submodule "plugins/flutter_distributor"]
|
||||
path = plugins/flutter_distributor
|
||||
url = git@github.com:chen08209/flutter_distributor.git
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,3 +1,27 @@
|
||||
## 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
|
||||
|
||||
@@ -1,8 +1 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
|
||||
analyzer:
|
||||
plugins:
|
||||
- custom_lint
|
||||
|
||||
@@ -33,7 +33,7 @@ def isRelease = defStoreFile.exists() && defStorePassword != null && defKeyAlias
|
||||
|
||||
android {
|
||||
namespace "com.follow.clash"
|
||||
compileSdkVersion 34
|
||||
compileSdkVersion 35
|
||||
ndkVersion "27.1.12297006"
|
||||
|
||||
compileOptions {
|
||||
@@ -63,7 +63,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId "com.follow.clash"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
targetSdkVersion 35
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
@@ -10,14 +10,12 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
|
||||
tools:ignore="SystemPermissionTypo" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
@@ -64,7 +62,9 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="false" />
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||
android:value="false" />
|
||||
|
||||
<activity
|
||||
android:name=".TempActivity"
|
||||
@@ -87,7 +87,6 @@
|
||||
<service
|
||||
android:name=".services.FlClashTileService"
|
||||
android:exported="true"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:icon="@drawable/ic_stat_name"
|
||||
android:label="FlClash"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
@@ -125,7 +124,7 @@
|
||||
<service
|
||||
android:name=".services.FlClashVpnService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
@@ -138,7 +137,7 @@
|
||||
<service
|
||||
android:name=".services.FlClashService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="specialUse">
|
||||
android:foregroundServiceType="dataSync">
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="service" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.follow.clash;
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context;
|
||||
import android.content.Context
|
||||
|
||||
class FlClashApplication : Application() {
|
||||
companion object {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.follow.clash
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.follow.clash.plugins.AppPlugin
|
||||
import com.follow.clash.plugins.ServicePlugin
|
||||
import com.follow.clash.plugins.TilePlugin
|
||||
import com.follow.clash.plugins.VpnPlugin
|
||||
import io.flutter.FlutterInjector
|
||||
|
||||
@@ -291,16 +291,18 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
private fun getPackages(): List<Package> {
|
||||
val packageManager = FlClashApplication.getAppContext().packageManager
|
||||
if (packages.isNotEmpty()) return packages
|
||||
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter {
|
||||
it.packageName != FlClashApplication.getAppContext().packageName
|
||||
|| it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
||||
|| it.packageName == "android"
|
||||
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA or PackageManager.GET_PERMISSIONS)
|
||||
?.filter {
|
||||
it.packageName != FlClashApplication.getAppContext().packageName && (
|
||||
it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
||||
|| it.packageName == "android"
|
||||
)
|
||||
|
||||
}?.map {
|
||||
}?.map {
|
||||
Package(
|
||||
packageName = it.packageName,
|
||||
label = it.applicationInfo.loadLabel(packageManager).toString(),
|
||||
isSystem = (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 1,
|
||||
label = it.applicationInfo?.loadLabel(packageManager).toString(),
|
||||
isSystem = (it.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM)) == 1,
|
||||
lastUpdateTime = it.lastUpdateTime
|
||||
)
|
||||
}?.let { packages.addAll(it) }
|
||||
@@ -353,7 +355,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
}
|
||||
|
||||
suspend fun getText(text: String): String? {
|
||||
return withContext(Dispatchers.Default){
|
||||
return withContext(Dispatchers.Default) {
|
||||
channel.awaitResult<String>("getText", text)
|
||||
}
|
||||
}
|
||||
@@ -391,31 +393,33 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
}.forEach {
|
||||
if (it.name.matches(chinaAppRegex)) return true
|
||||
}
|
||||
ZipFile(File(packageInfo.applicationInfo.publicSourceDir)).use {
|
||||
for (packageEntry in it.entries()) {
|
||||
if (packageEntry.name.startsWith("firebase-")) return false
|
||||
}
|
||||
for (packageEntry in it.entries()) {
|
||||
if (!(packageEntry.name.startsWith("classes") && packageEntry.name.endsWith(
|
||||
".dex"
|
||||
))
|
||||
) {
|
||||
continue
|
||||
packageInfo.applicationInfo?.publicSourceDir?.let {
|
||||
ZipFile(File(it)).use {
|
||||
for (packageEntry in it.entries()) {
|
||||
if (packageEntry.name.startsWith("firebase-")) return false
|
||||
}
|
||||
if (packageEntry.size > 15000000) {
|
||||
return true
|
||||
}
|
||||
val input = it.getInputStream(packageEntry).buffered()
|
||||
val dexFile = try {
|
||||
DexBackedDexFile.fromInputStream(null, input)
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
for (clazz in dexFile.classes) {
|
||||
val clazzName =
|
||||
clazz.type.substring(1, clazz.type.length - 1).replace("/", ".")
|
||||
.replace("$", ".")
|
||||
if (clazzName.matches(chinaAppRegex)) return true
|
||||
for (packageEntry in it.entries()) {
|
||||
if (!(packageEntry.name.startsWith("classes") && packageEntry.name.endsWith(
|
||||
".dex"
|
||||
))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if (packageEntry.size > 15000000) {
|
||||
return true
|
||||
}
|
||||
val input = it.getInputStream(packageEntry).buffered()
|
||||
val dexFile = try {
|
||||
DexBackedDexFile.fromInputStream(null, input)
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
for (clazz in dexFile.classes) {
|
||||
val clazzName =
|
||||
clazz.type.substring(1, clazz.type.length - 1).replace("/", ".")
|
||||
.replace("$", ".")
|
||||
if (clazzName.matches(chinaAppRegex)) return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.follow.clash.plugins
|
||||
|
||||
import com.follow.clash.FlClashApplication
|
||||
import com.follow.clash.GlobalState
|
||||
import com.follow.clash.models.VpnOptions
|
||||
import com.google.gson.Gson
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.core.content.getSystemService
|
||||
import com.follow.clash.FlClashApplication
|
||||
import com.follow.clash.GlobalState
|
||||
|
||||
@@ -7,7 +7,7 @@ import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
@@ -87,6 +87,7 @@ class FlClashService : Service(), BaseServiceInterface {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getNotificationBuilder(): NotificationCompat.Builder {
|
||||
return notificationBuilderDeferred.await()
|
||||
}
|
||||
@@ -100,7 +101,8 @@ class FlClashService : Service(), BaseServiceInterface {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ForegroundServiceType", "WrongConstant")
|
||||
|
||||
@SuppressLint("ForegroundServiceType")
|
||||
override suspend fun startForeground(title: String, content: String) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val manager = getSystemService(NotificationManager::class.java)
|
||||
@@ -116,7 +118,11 @@ class FlClashService : Service(), BaseServiceInterface {
|
||||
.setContentTitle(title)
|
||||
.setContentText(content).build()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||
try {
|
||||
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
||||
} catch (_: Exception) {
|
||||
startForeground(notificationId, notification)
|
||||
}
|
||||
} else {
|
||||
startForeground(notificationId, notification)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
import android.net.ProxyInfo
|
||||
import android.net.VpnService
|
||||
import android.os.Binder
|
||||
@@ -45,9 +45,16 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
|
||||
addAddress(cidr.address, cidr.prefixLength)
|
||||
val routeAddress = options.getIpv4RouteAddress()
|
||||
if (routeAddress.isNotEmpty()) {
|
||||
routeAddress.forEach { i ->
|
||||
Log.d("addRoute4", "address: ${i.address} prefixLength:${i.prefixLength}")
|
||||
addRoute(i.address, i.prefixLength)
|
||||
try {
|
||||
routeAddress.forEach { i ->
|
||||
Log.d(
|
||||
"addRoute4",
|
||||
"address: ${i.address} prefixLength:${i.prefixLength}"
|
||||
)
|
||||
addRoute(i.address, i.prefixLength)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
addRoute("0.0.0.0", 0)
|
||||
}
|
||||
} else {
|
||||
addRoute("0.0.0.0", 0)
|
||||
@@ -58,9 +65,16 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
|
||||
addAddress(cidr.address, cidr.prefixLength)
|
||||
val routeAddress = options.getIpv6RouteAddress()
|
||||
if (routeAddress.isNotEmpty()) {
|
||||
routeAddress.forEach { i ->
|
||||
Log.d("addRoute6", "address: ${i.address} prefixLength:${i.prefixLength}")
|
||||
addRoute(i.address, i.prefixLength)
|
||||
try {
|
||||
routeAddress.forEach { i ->
|
||||
Log.d(
|
||||
"addRoute6",
|
||||
"address: ${i.address} prefixLength:${i.prefixLength}"
|
||||
)
|
||||
addRoute(i.address, i.prefixLength)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
addRoute("::", 0)
|
||||
}
|
||||
} else {
|
||||
addRoute("::", 0)
|
||||
@@ -165,7 +179,7 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
|
||||
return notificationBuilderDeferred.await()
|
||||
}
|
||||
|
||||
@SuppressLint("ForegroundServiceType", "WrongConstant")
|
||||
@SuppressLint("ForegroundServiceType")
|
||||
override suspend fun startForeground(title: String, content: String) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val manager = getSystemService(NotificationManager::class.java)
|
||||
@@ -182,7 +196,11 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
|
||||
.setContentText(content)
|
||||
.build()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||
try {
|
||||
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
||||
} catch (_: Exception) {
|
||||
startForeground(notificationId, notification)
|
||||
}
|
||||
} else {
|
||||
startForeground(notificationId, notification)
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx4G
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
kotlin_version=1.9.22
|
||||
agp_version=8.2.1
|
||||
agp_version=8.9.1
|
||||
|
||||
@@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||
|
||||
|
||||
BIN
assets/fonts/JetBrainsMono-Regular.ttf
Normal file
BIN
assets/fonts/JetBrainsMono-Regular.ttf
Normal file
Binary file not shown.
Binary file not shown.
Submodule core/Clash.Meta updated: 76b0d7e8bc...f19dad529f
@@ -28,6 +28,18 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
func splitByMultipleSeparators(s string) interface{} {
|
||||
isSeparator := func(r rune) bool {
|
||||
return r == ',' || r == ' ' || r == ';'
|
||||
}
|
||||
|
||||
parts := strings.FieldsFunc(s, isSeparator)
|
||||
if len(parts) > 1 {
|
||||
return parts
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var (
|
||||
isRunning = false
|
||||
runLock sync.Mutex
|
||||
@@ -160,9 +172,19 @@ func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig
|
||||
return prof
|
||||
}
|
||||
|
||||
func genHosts(hosts, patchHosts map[string]any) {
|
||||
func attachHosts(hosts, patchHosts map[string]any) {
|
||||
for k, v := range patchHosts {
|
||||
hosts[k] = v
|
||||
if str, ok := v.(string); ok {
|
||||
hosts[k] = splitByMultipleSeparators(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updatePatchDns(dns config.RawDNS) {
|
||||
for pair := dns.NameServerPolicy.Oldest(); pair != nil; pair = pair.Next() {
|
||||
if str, ok := pair.Value.(string); ok {
|
||||
dns.NameServerPolicy.Set(pair.Key, splitByMultipleSeparators(str))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,26 +195,25 @@ func trimArr(arr []string) (r []string) {
|
||||
return
|
||||
}
|
||||
|
||||
func overrideRules(rules *[]string) {
|
||||
var target = ""
|
||||
for _, line := range *rules {
|
||||
func overrideRules(rules, patchRules []string) []string {
|
||||
target := ""
|
||||
for _, line := range rules {
|
||||
rule := trimArr(strings.Split(line, ","))
|
||||
l := len(rule)
|
||||
if l != 2 {
|
||||
return
|
||||
if len(rule) != 2 {
|
||||
continue
|
||||
}
|
||||
if strings.ToUpper(rule[0]) == "MATCH" {
|
||||
if strings.EqualFold(rule[0], "MATCH") {
|
||||
target = rule[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if target == "" {
|
||||
return
|
||||
return rules
|
||||
}
|
||||
var rulesExt = lo.Map(ips, func(ip string, index int) string {
|
||||
return fmt.Sprintf("DOMAIN %s %s", ip, target)
|
||||
rulesExt := lo.Map(ips, func(ip string, _ int) string {
|
||||
return fmt.Sprintf("DOMAIN,%s,%s", ip, target)
|
||||
})
|
||||
*rules = append(rulesExt, *rules...)
|
||||
return append(append(rulesExt, patchRules...), rules...)
|
||||
}
|
||||
|
||||
func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) {
|
||||
@@ -226,15 +247,20 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
|
||||
for idx := range targetConfig.ProxyGroup {
|
||||
targetConfig.ProxyGroup[idx]["url"] = ""
|
||||
}
|
||||
genHosts(targetConfig.Hosts, patchConfig.Hosts)
|
||||
attachHosts(targetConfig.Hosts, patchConfig.Hosts)
|
||||
if configParams.OverrideDns {
|
||||
updatePatchDns(patchConfig.DNS)
|
||||
targetConfig.DNS = patchConfig.DNS
|
||||
} else {
|
||||
if targetConfig.DNS.Enable == false {
|
||||
targetConfig.DNS.Enable = true
|
||||
}
|
||||
}
|
||||
overrideRules(&targetConfig.Rule)
|
||||
if configParams.OverrideRule {
|
||||
targetConfig.Rule = overrideRules(patchConfig.Rule, []string{})
|
||||
} else {
|
||||
targetConfig.Rule = overrideRules(targetConfig.Rule, patchConfig.Rule)
|
||||
}
|
||||
}
|
||||
|
||||
func patchConfig() {
|
||||
@@ -248,6 +274,7 @@ func patchConfig() {
|
||||
dialer.DefaultInterface.Store(general.Interface)
|
||||
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
||||
tunnel.SetMode(general.Mode)
|
||||
tunnel.UpdateRules(currentConfig.Rules, currentConfig.SubRules, currentConfig.RuleProviders)
|
||||
log.SetLevel(general.LogLevel)
|
||||
resolver.DisableIPv6 = !general.IPv6
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ type ConfigExtendedParams struct {
|
||||
SelectedMap map[string]string `json:"selected-map"`
|
||||
TestURL *string `json:"test-url"`
|
||||
OverrideDns bool `json:"override-dns"`
|
||||
OverrideRule bool `json:"override-rule"`
|
||||
}
|
||||
|
||||
type GenerateConfigParams struct {
|
||||
|
||||
11
core/go.mod
11
core/go.mod
@@ -21,7 +21,7 @@ require (
|
||||
github.com/coreos/go-iptables v0.8.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/enfein/mieru/v3 v3.11.2 // indirect
|
||||
github.com/enfein/mieru/v3 v3.13.0 // indirect
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||
@@ -50,21 +50,22 @@ require (
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
|
||||
github.com/metacubex/bart v0.19.0 // indirect
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // indirect
|
||||
github.com/metacubex/chacha v0.1.1 // indirect
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
|
||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 // indirect
|
||||
github.com/metacubex/randv2 v0.2.0 // indirect
|
||||
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 // indirect
|
||||
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8 // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.5 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 // indirect
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 // indirect
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 // indirect
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect
|
||||
github.com/metacubex/utls v1.6.6 // indirect
|
||||
github.com/metacubex/utls v1.6.8-alpha.4 // indirect
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
|
||||
github.com/miekg/dns v1.1.63 // indirect
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
|
||||
@@ -74,7 +75,7 @@ require (
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/sagernet/cors v1.2.1 // indirect
|
||||
|
||||
22
core/go.sum
22
core/go.sum
@@ -28,8 +28,8 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/enfein/mieru/v3 v3.11.2 h1:06KyGbXiiGz2nSHLJDOOkztAVY3cRr3wBMOpYxPotTo=
|
||||
github.com/enfein/mieru/v3 v3.11.2/go.mod h1:XvVfNsM78lUMSlJJKXJZ0Hn3lAB2o/ETXTbb84x5egw=
|
||||
github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98=
|
||||
github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||
@@ -97,14 +97,16 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
|
||||
github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY=
|
||||
github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
||||
github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c=
|
||||
github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a h1:cZ6oNVrsmsi3SNlnSnRio4zOgtQq+/XidwsaNgKICcg=
|
||||
github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a/go.mod h1:xBw/SYJPgUMPQ1tklV/brGn2nxhfr3BnvBzNlyi4Nic=
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 h1:B+AP/Pj2/jBDS/kCYjz/x+0BCOKfd2VODYevyeIt+Ds=
|
||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY=
|
||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||
@@ -117,16 +119,16 @@ github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJ
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
||||
github.com/metacubex/sing-tun v0.4.5 h1:kWSyQzuzHI40r50OFBczfWIDvMBMy1RIk+JsXeBPRB0=
|
||||
github.com/metacubex/sing-tun v0.4.5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 h1:B211C+i/I8CWf4I/BaAV0mmkEHrDBJ0XR9EWxjPbFEg=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
|
||||
github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo=
|
||||
github.com/metacubex/utls v1.6.8-alpha.4 h1:5EvsCHxDNneaOtAyc8CztoNSpmonLvkvuGs01lIeeEI=
|
||||
github.com/metacubex/utls v1.6.8-alpha.4/go.mod h1:MEZ5WO/VLKYs/s/dOzEK/mlXOQxc04ESeLzRgjmLYtk=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
@@ -153,8 +155,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
|
||||
@@ -61,16 +61,6 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
_autoUpdateGroupTask();
|
||||
_autoUpdateProfilesTask();
|
||||
globalState.appController = AppController(context, ref);
|
||||
globalState.measure = Measure.of(context);
|
||||
ref.listenManual(themeSettingProvider.select((state) => state.fontFamily),
|
||||
(prev, next) {
|
||||
if (prev != next) {
|
||||
globalState.measure = Measure.of(
|
||||
context,
|
||||
fontFamily: next.value,
|
||||
);
|
||||
}
|
||||
}, fireImmediately: true);
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
final currentContext = globalState.navigatorKey.currentContext;
|
||||
if (currentContext != null) {
|
||||
@@ -98,7 +88,7 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
});
|
||||
}
|
||||
|
||||
_buildPlatformWrap(Widget child) {
|
||||
_buildPlatformState(Widget child) {
|
||||
if (system.isDesktop) {
|
||||
return WindowManager(
|
||||
child: TrayManager(
|
||||
@@ -117,18 +107,7 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
);
|
||||
}
|
||||
|
||||
_buildPage(Widget page) {
|
||||
if (system.isDesktop) {
|
||||
return WindowHeaderContainer(
|
||||
child: page,
|
||||
);
|
||||
}
|
||||
return VpnManager(
|
||||
child: page,
|
||||
);
|
||||
}
|
||||
|
||||
_buildWrap(Widget child) {
|
||||
_buildState(Widget child) {
|
||||
return AppStateManager(
|
||||
child: ClashManager(
|
||||
child: ConnectivityManager(
|
||||
@@ -142,6 +121,25 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
);
|
||||
}
|
||||
|
||||
_buildPlatformApp(Widget child) {
|
||||
if (system.isDesktop) {
|
||||
return WindowHeaderContainer(
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return VpnManager(
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
_buildApp(Widget child) {
|
||||
return MessageManager(
|
||||
child: ThemeManager(
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_updateSystemColorSchemes(
|
||||
ColorScheme? lightDynamic,
|
||||
ColorScheme? darkDynamic,
|
||||
@@ -157,8 +155,8 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
|
||||
@override
|
||||
Widget build(context) {
|
||||
return _buildPlatformWrap(
|
||||
_buildWrap(
|
||||
return _buildPlatformState(
|
||||
_buildState(
|
||||
Consumer(
|
||||
builder: (_, ref, child) {
|
||||
final locale =
|
||||
@@ -168,6 +166,7 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
_updateSystemColorSchemes(lightDynamic, darkDynamic);
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
navigatorKey: globalState.navigatorKey,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
@@ -176,14 +175,9 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
GlobalWidgetsLocalizations.delegate
|
||||
],
|
||||
builder: (_, child) {
|
||||
return MessageManager(
|
||||
child: LayoutBuilder(
|
||||
builder: (_, container) {
|
||||
globalState.appController.updateViewWidth(
|
||||
container.maxWidth,
|
||||
);
|
||||
return _buildPage(child!);
|
||||
},
|
||||
return AppEnvManager(
|
||||
child: _buildPlatformApp(
|
||||
_buildApp(child!),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -194,7 +188,6 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
themeMode: themeProps.themeMode,
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
fontFamily: themeProps.fontFamily.value,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
colorScheme: _getAppColorScheme(
|
||||
brightness: Brightness.light,
|
||||
@@ -204,13 +197,12 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
fontFamily: themeProps.fontFamily.value,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
colorScheme: _getAppColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
systemColorSchemes: systemColorSchemes,
|
||||
primaryColor: themeProps.primaryColor,
|
||||
).toPrueBlack(themeProps.prueBlack),
|
||||
).toPureBlack(themeProps.pureBlack),
|
||||
),
|
||||
home: child,
|
||||
);
|
||||
|
||||
@@ -235,12 +235,12 @@ class ClashCore {
|
||||
return int.parse(value);
|
||||
}
|
||||
|
||||
Future<ClashConfig?> getProfile(String id) async {
|
||||
Future<ClashConfigSnippet?> getProfile(String id) async {
|
||||
final res = await clashInterface.getProfile(id);
|
||||
if (res.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return ClashConfig.fromJson(json.decode(res));
|
||||
return Isolate.run(() => ClashConfigSnippet.fromJson(json.decode(res)));
|
||||
}
|
||||
|
||||
resetTraffic() {
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension ColorExtension on Color {
|
||||
|
||||
Color get toLight {
|
||||
return withOpacity(0.8);
|
||||
Color get opacity80 {
|
||||
return withAlpha(204);
|
||||
}
|
||||
|
||||
Color get toLighter {
|
||||
return withOpacity(0.6);
|
||||
Color get opacity60 {
|
||||
return withAlpha(153);
|
||||
}
|
||||
|
||||
Color get toSoft {
|
||||
return withOpacity(0.15);
|
||||
Color get opacity50 {
|
||||
return withAlpha(128);
|
||||
}
|
||||
|
||||
Color get toLittle {
|
||||
return withOpacity(0.03);
|
||||
Color get opacity38 {
|
||||
return withAlpha(97);
|
||||
}
|
||||
|
||||
Color get opacity30 {
|
||||
return withAlpha(77);
|
||||
}
|
||||
|
||||
Color get opacity15 {
|
||||
return withAlpha(38);
|
||||
}
|
||||
|
||||
Color get opacity10 {
|
||||
return withAlpha(15);
|
||||
}
|
||||
|
||||
Color get opacity3 {
|
||||
return withAlpha(76);
|
||||
}
|
||||
|
||||
Color get opacity0 {
|
||||
return withAlpha(0);
|
||||
}
|
||||
|
||||
Color darken([double amount = .1]) {
|
||||
@@ -51,7 +70,7 @@ extension ColorExtension on Color {
|
||||
}
|
||||
|
||||
extension ColorSchemeExtension on ColorScheme {
|
||||
ColorScheme toPrueBlack(bool isPrueBlack) => isPrueBlack
|
||||
ColorScheme toPureBlack(bool isPrueBlack) => isPrueBlack
|
||||
? copyWith(
|
||||
surface: Colors.black,
|
||||
surfaceContainer: surfaceContainer.darken(
|
||||
|
||||
@@ -21,6 +21,11 @@ const baseInfoEdgeInsets = EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
horizontal: 16,
|
||||
);
|
||||
|
||||
double textScaleFactor = min(
|
||||
WidgetsBinding.instance.platformDispatcher.textScaleFactor,
|
||||
1.2,
|
||||
);
|
||||
const httpTimeoutDuration = Duration(milliseconds: 5000);
|
||||
const moreDuration = Duration(milliseconds: 100);
|
||||
const animateDuration = Duration(milliseconds: 100);
|
||||
@@ -46,7 +51,7 @@ const defaultExternalController = "127.0.0.1:9090";
|
||||
const maxMobileWidth = 600;
|
||||
const maxLaptopWidth = 840;
|
||||
const defaultTestUrl = "https://www.gstatic.com/generate_204";
|
||||
final filter = ImageFilter.blur(
|
||||
final commonFilter = ImageFilter.blur(
|
||||
sigmaX: 5,
|
||||
sigmaY: 5,
|
||||
tileMode: TileMode.mirror,
|
||||
@@ -76,7 +81,7 @@ const viewModeColumnsMap = {
|
||||
const defaultPrimaryColor = Colors.brown;
|
||||
|
||||
double getWidgetHeight(num lines) {
|
||||
return max(lines * 84 + (lines - 1) * 16, 0);
|
||||
return max(lines * 84 * textScaleFactor + (lines - 1) * 16, 0);
|
||||
}
|
||||
|
||||
final mainIsolate = "FlClashMainIsolate";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
class Debouncer {
|
||||
final Map<dynamic, Timer> _operations = {};
|
||||
final Map<dynamic, Timer?> _operations = {};
|
||||
|
||||
call(
|
||||
dynamic tag,
|
||||
@@ -28,14 +28,15 @@ class Debouncer {
|
||||
|
||||
cancel(dynamic tag) {
|
||||
_operations[tag]?.cancel();
|
||||
_operations[tag] = null;
|
||||
}
|
||||
}
|
||||
|
||||
class Throttler {
|
||||
final Map<dynamic, Timer> _operations = {};
|
||||
final Map<dynamic, Timer?> _operations = {};
|
||||
|
||||
call(
|
||||
String tag,
|
||||
dynamic tag,
|
||||
Function func, {
|
||||
List<dynamic>? args,
|
||||
Duration duration = const Duration(milliseconds: 600),
|
||||
@@ -60,6 +61,7 @@ class Throttler {
|
||||
|
||||
cancel(dynamic tag) {
|
||||
_operations[tag]?.cancel();
|
||||
_operations[tag] = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
|
||||
import '../state.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
|
||||
class FlClashHttpOverrides extends HttpOverrides {
|
||||
static String handleFindProxy(Uri url) {
|
||||
if ([localhost].contains(url.host)) {
|
||||
return "DIRECT";
|
||||
}
|
||||
final port = globalState.config.patchClashConfig.mixedPort;
|
||||
final isStart = globalState.appState.runTime != null;
|
||||
commonPrint.log("find $url proxy:$isStart");
|
||||
if (!isStart) return "DIRECT";
|
||||
return "PROXY localhost:$port";
|
||||
}
|
||||
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext? context) {
|
||||
final client = super.createHttpClient(context);
|
||||
client.badCertificateCallback = (_, __, ___) => true;
|
||||
client.findProxy = (url) {
|
||||
if ([localhost].contains(url.host)) {
|
||||
return "DIRECT";
|
||||
}
|
||||
final port = globalState.config.patchClashConfig.mixedPort;
|
||||
final isStart = globalState.appState.runTime != null;
|
||||
commonPrint.log("find $url proxy:$isStart");
|
||||
if (!isStart) return "DIRECT";
|
||||
return "PROXY localhost:$port";
|
||||
};
|
||||
client.findProxy = handleFindProxy;
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,3 +65,12 @@ extension DoubleListExt on List<double> {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
extension MapExt<K, V> on Map<K, V> {
|
||||
getCacheValue(K key, V defaultValue) {
|
||||
if (this[key] == null) {
|
||||
this[key] = defaultValue;
|
||||
}
|
||||
return this[key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class FixedList<T> {
|
||||
}
|
||||
|
||||
class FixedMap<K, V> {
|
||||
final int maxSize;
|
||||
int maxSize;
|
||||
final Map<K, V> _map = {};
|
||||
final Queue<K> _queue = Queue<K>();
|
||||
|
||||
@@ -45,6 +45,7 @@ class FixedMap<K, V> {
|
||||
}
|
||||
_map[key] = value;
|
||||
_queue.add(key);
|
||||
return value;
|
||||
}
|
||||
|
||||
clear() {
|
||||
@@ -52,8 +53,13 @@ class FixedMap<K, V> {
|
||||
_queue.clear();
|
||||
}
|
||||
|
||||
updateMaxSize(int size){
|
||||
maxSize = size;
|
||||
}
|
||||
|
||||
V? get(K key) => _map[key];
|
||||
|
||||
|
||||
bool containsKey(K key) => _map.containsKey(key);
|
||||
|
||||
int get length => _map.length;
|
||||
|
||||
@@ -5,13 +5,11 @@ import 'package:flutter/material.dart';
|
||||
class Measure {
|
||||
final TextScaler _textScale;
|
||||
final BuildContext context;
|
||||
final String? _fontFamily;
|
||||
|
||||
Measure.of(this.context, {String? fontFamily})
|
||||
Measure.of(this.context)
|
||||
: _textScale = TextScaler.linear(
|
||||
WidgetsBinding.instance.platformDispatcher.textScaleFactor,
|
||||
),
|
||||
_fontFamily = fontFamily ?? "";
|
||||
textScaleFactor,
|
||||
);
|
||||
|
||||
Size computeTextSize(
|
||||
Text text, {
|
||||
@@ -20,9 +18,7 @@ class Measure {
|
||||
final textPainter = TextPainter(
|
||||
text: TextSpan(
|
||||
text: text.data,
|
||||
style: text.style?.copyWith(
|
||||
fontFamily: _fontFamily,
|
||||
),
|
||||
style: text.style,
|
||||
),
|
||||
maxLines: text.maxLines,
|
||||
textScaler: _textScale,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'context.dart';
|
||||
@@ -29,8 +30,14 @@ mixin PageMixin<T extends StatefulWidget> on State<T> {
|
||||
final commonScaffoldState = context.commonScaffoldState;
|
||||
commonScaffoldState?.actions = actions;
|
||||
commonScaffoldState?.floatingActionButton = floatingActionButton;
|
||||
commonScaffoldState?.onSearch = onSearch;
|
||||
commonScaffoldState?.onKeywordsUpdate = onKeywordsUpdate;
|
||||
commonScaffoldState?.updateSearchState(
|
||||
(_) => onSearch != null
|
||||
? AppBarSearchState(
|
||||
onSearch: onSearch!,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ class Navigation {
|
||||
const NavigationItem(
|
||||
icon: Icon(Icons.space_dashboard),
|
||||
label: PageLabel.dashboard,
|
||||
keep: false,
|
||||
fragment: DashboardFragment(
|
||||
key: GlobalObjectKey(PageLabel.dashboard),
|
||||
),
|
||||
|
||||
@@ -70,7 +70,7 @@ class CommonRoute<T> extends MaterialPageRoute<T> {
|
||||
Duration get transitionDuration => const Duration(milliseconds: 500);
|
||||
|
||||
@override
|
||||
Duration get reverseTransitionDuration => const Duration(milliseconds: 250);
|
||||
Duration get reverseTransitionDuration => const Duration(milliseconds: 500);
|
||||
}
|
||||
|
||||
final Animatable<Offset> _kRightMiddleTween = Tween<Offset>(
|
||||
@@ -194,7 +194,7 @@ class _CommonPageTransitionState extends State<CommonPageTransition> {
|
||||
_primaryPositionCurve = CurvedAnimation(
|
||||
parent: widget.primaryRouteAnimation,
|
||||
curve: Curves.fastEaseInToSlowEaseOut,
|
||||
reverseCurve: Curves.easeInOut,
|
||||
reverseCurve: Curves.fastEaseInToSlowEaseOut.flipped,
|
||||
);
|
||||
_secondaryPositionCurve = CurvedAnimation(
|
||||
parent: widget.secondaryRouteAnimation,
|
||||
@@ -218,9 +218,8 @@ class _CommonPageTransitionState extends State<CommonPageTransition> {
|
||||
begin: const _CommonEdgeShadowDecoration(),
|
||||
end: _CommonEdgeShadowDecoration(
|
||||
<Color>[
|
||||
widget.context.colorScheme.inverseSurface.withOpacity(
|
||||
0.06,
|
||||
),
|
||||
widget.context.colorScheme.inverseSurface
|
||||
.withValues(alpha: 0.02),
|
||||
Colors.transparent,
|
||||
],
|
||||
),
|
||||
@@ -274,7 +273,7 @@ class _CommonEdgeShadowPainter extends BoxPainter {
|
||||
return;
|
||||
}
|
||||
|
||||
final double shadowWidth = 0.03 * configuration.size!.width;
|
||||
final double shadowWidth = 1 * configuration.size!.width;
|
||||
final double shadowHeight = configuration.size!.height;
|
||||
final double bandWidth = shadowWidth / (colors.length - 1);
|
||||
|
||||
|
||||
@@ -241,11 +241,6 @@ class Other {
|
||||
return "${appName}_${DateTime.now().show}.log";
|
||||
}
|
||||
|
||||
Size getScreenSize() {
|
||||
final view = WidgetsBinding.instance.platformDispatcher.views.first;
|
||||
return view.physicalSize / view.devicePixelRatio;
|
||||
}
|
||||
|
||||
Future<String?> getLocalIpAddress() async {
|
||||
List<NetworkInterface> interfaces = await NetworkInterface.list(
|
||||
includeLoopback: false,
|
||||
|
||||
@@ -14,15 +14,13 @@ class Protocol {
|
||||
|
||||
void register(String scheme) {
|
||||
String protocolRegKey = 'Software\\Classes\\$scheme';
|
||||
RegistryValue protocolRegValue = const RegistryValue(
|
||||
RegistryValue protocolRegValue = RegistryValue.string(
|
||||
'URL Protocol',
|
||||
RegistryValueType.string,
|
||||
'',
|
||||
);
|
||||
String protocolCmdRegKey = 'shell\\open\\command';
|
||||
RegistryValue protocolCmdRegValue = RegistryValue(
|
||||
RegistryValue protocolCmdRegValue = RegistryValue.string(
|
||||
'',
|
||||
RegistryValueType.string,
|
||||
'"${Platform.resolvedExecutable}" "%1"',
|
||||
);
|
||||
final regKey = Registry.currentUser.createKey(protocolRegKey);
|
||||
@@ -31,4 +29,4 @@ class Protocol {
|
||||
}
|
||||
}
|
||||
|
||||
final protocol = Protocol();
|
||||
final protocol = Protocol();
|
||||
|
||||
@@ -22,19 +22,19 @@ class Render {
|
||||
}
|
||||
|
||||
pause() {
|
||||
debouncer.call(
|
||||
throttler.call(
|
||||
DebounceTag.renderPause,
|
||||
_pause,
|
||||
duration: Duration(seconds: 15),
|
||||
duration: Duration(seconds: 5),
|
||||
);
|
||||
}
|
||||
|
||||
resume() {
|
||||
debouncer.cancel(DebounceTag.renderPause);
|
||||
throttler.cancel(DebounceTag.renderPause);
|
||||
_resume();
|
||||
}
|
||||
|
||||
void _pause() {
|
||||
void _pause() async {
|
||||
if (_isPaused) return;
|
||||
_isPaused = true;
|
||||
_beginFrame = _dispatcher.onBeginFrame;
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
@@ -10,6 +11,7 @@ import 'package:flutter/cupertino.dart';
|
||||
|
||||
class Request {
|
||||
late final Dio _dio;
|
||||
late final Dio _clashDio;
|
||||
String? userAgent;
|
||||
|
||||
Request() {
|
||||
@@ -20,22 +22,24 @@ class Request {
|
||||
},
|
||||
),
|
||||
);
|
||||
_clashDio = Dio();
|
||||
_clashDio.httpClientAdapter = IOHttpClientAdapter(createHttpClient: () {
|
||||
final client = HttpClient();
|
||||
client.findProxy = (Uri uri) {
|
||||
client.userAgent = globalState.ua;
|
||||
return FlClashHttpOverrides.handleFindProxy(uri);
|
||||
};
|
||||
return client;
|
||||
});
|
||||
}
|
||||
|
||||
Future<Response> getFileResponseForUrl(String url) async {
|
||||
final response = await _dio
|
||||
.get(
|
||||
url,
|
||||
options: Options(
|
||||
headers: {
|
||||
"User-Agent": globalState.ua,
|
||||
},
|
||||
responseType: ResponseType.bytes,
|
||||
),
|
||||
)
|
||||
.timeout(
|
||||
httpTimeoutDuration * 6,
|
||||
);
|
||||
final response = await _clashDio.get(
|
||||
url,
|
||||
options: Options(
|
||||
responseType: ResponseType.bytes,
|
||||
),
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -79,13 +83,19 @@ class Request {
|
||||
Future<IpInfo?> checkIp({CancelToken? cancelToken}) async {
|
||||
for (final source in _ipInfoSources.entries) {
|
||||
try {
|
||||
final response = await _dio.get<Map<String, dynamic>>(
|
||||
source.key,
|
||||
cancelToken: cancelToken,
|
||||
options: Options(
|
||||
responseType: ResponseType.json,
|
||||
),
|
||||
);
|
||||
final response = await Dio()
|
||||
.get<Map<String, dynamic>>(
|
||||
source.key,
|
||||
cancelToken: cancelToken,
|
||||
options: Options(
|
||||
responseType: ResponseType.json,
|
||||
),
|
||||
)
|
||||
.timeout(
|
||||
Duration(
|
||||
seconds: 30,
|
||||
),
|
||||
);
|
||||
if (response.statusCode != 200 || response.data == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -109,9 +119,6 @@ class Request {
|
||||
.get(
|
||||
"http://$localhost:$helperPort/ping",
|
||||
options: Options(
|
||||
headers: {
|
||||
"User-Agent": browserUa,
|
||||
},
|
||||
responseType: ResponseType.plain,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/widgets/scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BaseScrollBehavior extends MaterialScrollBehavior {
|
||||
@@ -16,8 +17,6 @@ class BaseScrollBehavior extends MaterialScrollBehavior {
|
||||
};
|
||||
}
|
||||
|
||||
class BaseScrollBehavior2 extends ScrollBehavior {}
|
||||
|
||||
class HiddenBarScrollBehavior extends BaseScrollBehavior {
|
||||
@override
|
||||
Widget buildScrollbar(
|
||||
@@ -36,8 +35,7 @@ class ShowBarScrollBehavior extends BaseScrollBehavior {
|
||||
Widget child,
|
||||
ScrollableDetails details,
|
||||
) {
|
||||
return Scrollbar(
|
||||
interactive: true,
|
||||
return CommonAutoHiddenScrollBar(
|
||||
controller: details.controller,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'color.dart';
|
||||
|
||||
extension TextStyleExtension on TextStyle {
|
||||
TextStyle get toLight => copyWith(color: color?.toLight);
|
||||
TextStyle get toLight => copyWith(color: color?.opacity80);
|
||||
|
||||
TextStyle get toLighter => copyWith(color: color?.toLighter);
|
||||
TextStyle get toLighter => copyWith(color: color?.opacity60);
|
||||
|
||||
TextStyle get toSoftBold => copyWith(fontWeight: FontWeight.w500);
|
||||
|
||||
TextStyle get toBold => copyWith(fontWeight: FontWeight.bold);
|
||||
|
||||
TextStyle get toJetBrainsMono => copyWith(
|
||||
fontFamily: FontFamily.jetBrainsMono.value,
|
||||
);
|
||||
|
||||
TextStyle adjustSize(int size) => copyWith(
|
||||
fontSize: fontSize! + size,
|
||||
);
|
||||
|
||||
39
lib/common/theme.dart
Normal file
39
lib/common/theme.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CommonTheme {
|
||||
final BuildContext context;
|
||||
final Map<String, Color> _colorMap;
|
||||
|
||||
CommonTheme.of(this.context) : _colorMap = {};
|
||||
|
||||
Color get darkenSecondaryContainer {
|
||||
return _colorMap.getCacheValue(
|
||||
"darkenSecondaryContainer",
|
||||
context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.1),
|
||||
);
|
||||
}
|
||||
|
||||
Color get darkenSecondaryContainerLighter {
|
||||
return _colorMap.getCacheValue(
|
||||
"darkenSecondaryContainerLighter",
|
||||
context.colorScheme.secondaryContainer
|
||||
.blendDarken(context, factor: 0.1)
|
||||
.opacity60,
|
||||
);
|
||||
}
|
||||
|
||||
Color get darken2SecondaryContainer {
|
||||
return _colorMap.getCacheValue(
|
||||
"darken2SecondaryContainer",
|
||||
context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.2),
|
||||
);
|
||||
}
|
||||
|
||||
Color get darken3PrimaryContainer {
|
||||
return _colorMap.getCacheValue(
|
||||
"darken3PrimaryContainer",
|
||||
context.colorScheme.primaryContainer.blendDarken(context, factor: 0.3),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,12 @@ class Window {
|
||||
await windowManager.ensureInitialized();
|
||||
WindowOptions windowOptions = WindowOptions(
|
||||
size: Size(props.width, props.height),
|
||||
minimumSize: const Size(380, 500),
|
||||
minimumSize: const Size(380, 400),
|
||||
);
|
||||
if (!Platform.isMacOS || version > 10) {
|
||||
await windowManager.setTitleBarStyle(TitleBarStyle.hidden);
|
||||
}
|
||||
if(!Platform.isMacOS){
|
||||
if (!Platform.isMacOS) {
|
||||
final left = props.left ?? 0;
|
||||
final top = props.top ?? 0;
|
||||
final right = left + props.width;
|
||||
@@ -36,7 +36,7 @@ class Window {
|
||||
} else {
|
||||
final displays = await screenRetriever.getAllDisplays();
|
||||
final isPositionValid = displays.any(
|
||||
(display) {
|
||||
(display) {
|
||||
final displayBounds = Rect.fromLTWH(
|
||||
display.visiblePosition!.dx,
|
||||
display.visiblePosition!.dy,
|
||||
@@ -69,8 +69,10 @@ class Window {
|
||||
await windowManager.setSkipTaskbar(false);
|
||||
}
|
||||
|
||||
Future<bool> isVisible() async {
|
||||
return await windowManager.isVisible();
|
||||
Future<bool> get isVisible async {
|
||||
final value = await windowManager.isVisible();
|
||||
commonPrint.log("window visible check: $value");
|
||||
return value;
|
||||
}
|
||||
|
||||
close() async {
|
||||
|
||||
@@ -10,12 +10,14 @@ import 'package:fl_clash/common/archive.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'common/common.dart';
|
||||
import 'fragments/profiles/override_profile.dart';
|
||||
import 'models/models.dart';
|
||||
|
||||
class AppController {
|
||||
@@ -28,7 +30,9 @@ class AppController {
|
||||
AppController(this.context, WidgetRef ref) : _ref = ref;
|
||||
|
||||
updateClashConfigDebounce() {
|
||||
debouncer.call(DebounceTag.updateClashConfig, updateClashConfig);
|
||||
debouncer.call(DebounceTag.updateClashConfig, () {
|
||||
updateClashConfig(true);
|
||||
});
|
||||
}
|
||||
|
||||
updateGroupsDebounce() {
|
||||
@@ -41,10 +45,12 @@ class AppController {
|
||||
});
|
||||
}
|
||||
|
||||
applyProfileDebounce() {
|
||||
debouncer.call(DebounceTag.addCheckIpNum, () {
|
||||
applyProfile();
|
||||
});
|
||||
applyProfileDebounce({
|
||||
bool silence = false,
|
||||
}) {
|
||||
debouncer.call(DebounceTag.applyProfile, (silence) {
|
||||
applyProfile(silence: silence);
|
||||
}, args: [silence]);
|
||||
}
|
||||
|
||||
savePreferencesDebounce() {
|
||||
@@ -156,18 +162,18 @@ class AppController {
|
||||
.read(profilesProvider.notifier)
|
||||
.setProfile(newProfile.copyWith(isUpdating: false));
|
||||
if (profile.id == _ref.read(currentProfileIdProvider)) {
|
||||
applyProfileDebounce();
|
||||
applyProfileDebounce(silence: true);
|
||||
}
|
||||
}
|
||||
|
||||
_setProfile(Profile profile) {
|
||||
setProfile(Profile profile) {
|
||||
_ref.read(profilesProvider.notifier).setProfile(profile);
|
||||
}
|
||||
|
||||
setProfile(Profile profile) {
|
||||
_setProfile(profile);
|
||||
setProfileAndAutoApply(Profile profile) {
|
||||
_ref.read(profilesProvider.notifier).setProfile(profile);
|
||||
if (profile.id == _ref.read(currentProfileIdProvider)) {
|
||||
applyProfileDebounce();
|
||||
applyProfileDebounce(silence: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,8 +225,8 @@ class AppController {
|
||||
return currentGroupName;
|
||||
}
|
||||
|
||||
getRealProxyName(proxyName) {
|
||||
return _ref.read(getRealTestUrlProvider(proxyName));
|
||||
ProxyCardState getProxyCardState(proxyName) {
|
||||
return _ref.read(getProxyCardStateProvider(proxyName));
|
||||
}
|
||||
|
||||
getSelectedProxyName(groupName) {
|
||||
@@ -232,12 +238,13 @@ class AppController {
|
||||
if (profile == null || profile.currentGroupName == groupName) {
|
||||
return;
|
||||
}
|
||||
_setProfile(
|
||||
setProfile(
|
||||
profile.copyWith(currentGroupName: groupName),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateClashConfig([bool? isPatch]) async {
|
||||
commonPrint.log("update clash patch: ${isPatch ?? false}");
|
||||
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
|
||||
if (commonScaffoldState?.mounted != true) return;
|
||||
await commonScaffoldState?.loadingRun(() async {
|
||||
@@ -410,6 +417,9 @@ class AppController {
|
||||
Map<String, dynamic>? data,
|
||||
bool handleError = false,
|
||||
}) async {
|
||||
if(globalState.isPre){
|
||||
return;
|
||||
}
|
||||
if (data != null) {
|
||||
final tagName = data['tag_name'];
|
||||
final body = data['body'];
|
||||
@@ -516,37 +526,12 @@ class AppController {
|
||||
_ref.read(delayDataSourceProvider.notifier).setDelay(delay);
|
||||
}
|
||||
|
||||
toPage(
|
||||
int index, {
|
||||
bool hasAnimate = false,
|
||||
}) {
|
||||
final navigations = _ref.read(currentNavigationsStateProvider).value;
|
||||
if (index > navigations.length - 1) {
|
||||
return;
|
||||
}
|
||||
_ref.read(currentPageLabelProvider.notifier).value =
|
||||
navigations[index].label;
|
||||
final isAnimateToPage = _ref.read(appSettingProvider).isAnimateToPage;
|
||||
final isMobile =
|
||||
_ref.read(viewWidthProvider.notifier).viewMode == ViewMode.mobile;
|
||||
if (isAnimateToPage && isMobile || hasAnimate) {
|
||||
globalState.pageController?.animateToPage(
|
||||
index,
|
||||
duration: kTabScrollDuration,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
} else {
|
||||
globalState.pageController?.jumpToPage(index);
|
||||
}
|
||||
toPage(PageLabel pageLabel) {
|
||||
_ref.read(currentPageLabelProvider.notifier).value = pageLabel;
|
||||
}
|
||||
|
||||
toProfiles() {
|
||||
final index = _ref.read(currentNavigationsStateProvider).value.indexWhere(
|
||||
(element) => element.label == PageLabel.profiles,
|
||||
);
|
||||
if (index != -1) {
|
||||
toPage(index);
|
||||
}
|
||||
toPage(PageLabel.profiles);
|
||||
}
|
||||
|
||||
initLink() {
|
||||
@@ -583,17 +568,8 @@ class AppController {
|
||||
Future<bool> showDisclaimer() async {
|
||||
return await globalState.showCommonDialog<bool>(
|
||||
dismissible: false,
|
||||
child: AlertDialog(
|
||||
title: Text(appLocalizations.disclaimer),
|
||||
content: Container(
|
||||
width: dialogCommonWidth,
|
||||
constraints: const BoxConstraints(maxHeight: 200),
|
||||
child: SingleChildScrollView(
|
||||
child: SelectableText(
|
||||
appLocalizations.disclaimerDesc,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: CommonDialog(
|
||||
title: appLocalizations.disclaimer,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
@@ -611,6 +587,9 @@ class AppController {
|
||||
child: Text(appLocalizations.agree),
|
||||
)
|
||||
],
|
||||
child: SelectableText(
|
||||
appLocalizations.disclaimerDesc,
|
||||
),
|
||||
),
|
||||
) ??
|
||||
false;
|
||||
@@ -676,9 +655,9 @@ class AppController {
|
||||
addProfileFormURL(url);
|
||||
}
|
||||
|
||||
updateViewWidth(double width) {
|
||||
updateViewSize(Size size) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_ref.read(viewWidthProvider.notifier).value = width;
|
||||
_ref.read(viewSizeProvider.notifier).value = size;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -737,10 +716,18 @@ class AppController {
|
||||
final providersPath = await appPath.getProvidersPath(profileId);
|
||||
return await Isolate.run(() async {
|
||||
if (profilePath != null) {
|
||||
await File(profilePath).delete(recursive: true);
|
||||
final profileFile = File(profilePath);
|
||||
final isExists = await profileFile.exists();
|
||||
if (isExists) {
|
||||
profileFile.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
if (providersPath != null) {
|
||||
await File(providersPath).delete(recursive: true);
|
||||
final providersFileDir = File(providersPath);
|
||||
final isExists = await providersFileDir.exists();
|
||||
if (isExists) {
|
||||
providersFileDir.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -754,13 +741,13 @@ class AppController {
|
||||
updateSystemProxy() {
|
||||
_ref.read(networkSettingProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
systemProxy: state.systemProxy,
|
||||
systemProxy: !state.systemProxy,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
updateStart() {
|
||||
updateStatus(_ref.read(runTimeProvider.notifier).isStart);
|
||||
updateStatus(!_ref.read(runTimeProvider.notifier).isStart);
|
||||
}
|
||||
|
||||
updateCurrentSelectedMap(String groupName, String proxyName) {
|
||||
@@ -809,7 +796,7 @@ class AppController {
|
||||
}
|
||||
|
||||
updateVisible() async {
|
||||
final visible = await window?.isVisible();
|
||||
final visible = await window?.isVisible;
|
||||
if (visible != null && !visible) {
|
||||
window?.show();
|
||||
} else {
|
||||
@@ -832,6 +819,38 @@ class AppController {
|
||||
);
|
||||
}
|
||||
|
||||
handleAddOrUpdate(WidgetRef ref, [Rule? rule]) async {
|
||||
final res = await globalState.showCommonDialog<Rule>(
|
||||
child: AddRuleDialog(
|
||||
rule: rule,
|
||||
snippet: ref.read(
|
||||
profileOverrideStateProvider.select(
|
||||
(state) => state.snippet!,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) {
|
||||
final model = state.copyWith.overrideData!(
|
||||
rule: state.overrideData!.rule.updateRules(
|
||||
(rules) {
|
||||
final index = rules.indexWhere((item) => item.id == res.id);
|
||||
if (index == -1) {
|
||||
return List.from([res, ...rules]);
|
||||
}
|
||||
return List.from(rules)..[index] = res;
|
||||
},
|
||||
),
|
||||
);
|
||||
return model;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> exportLogs() async {
|
||||
final logsRaw = _ref.read(logsProvider).list.map(
|
||||
(item) => item.toString(),
|
||||
|
||||
@@ -219,14 +219,13 @@ enum ProxiesIconStyle {
|
||||
}
|
||||
|
||||
enum FontFamily {
|
||||
system(),
|
||||
miSans("MiSans"),
|
||||
twEmoji("Twemoji"),
|
||||
jetBrainsMono("JetBrainsMono"),
|
||||
icon("Icons");
|
||||
|
||||
final String? value;
|
||||
final String value;
|
||||
|
||||
const FontFamily([this.value]);
|
||||
const FontFamily(this.value);
|
||||
}
|
||||
|
||||
enum RouteMode {
|
||||
@@ -386,3 +385,65 @@ enum PageLabel {
|
||||
resources,
|
||||
connections,
|
||||
}
|
||||
|
||||
enum RuleAction {
|
||||
DOMAIN("DOMAIN"),
|
||||
DOMAIN_SUFFIX("DOMAIN-SUFFIX"),
|
||||
DOMAIN_KEYWORD("DOMAIN-KEYWORD"),
|
||||
DOMAIN_REGEX("DOMAIN-REGEX"),
|
||||
GEOSITE("GEOSITE"),
|
||||
IP_CIDR("IP-CIDR"),
|
||||
IP_CIDR6("IP-CIDR6"),
|
||||
IP_SUFFIX("IP-SUFFIX"),
|
||||
IP_ASN("IP-ASN"),
|
||||
GEOIP("GEOIP"),
|
||||
SRC_GEOIP("SRC-GEOIP"),
|
||||
SRC_IP_ASN("SRC-IP-ASN"),
|
||||
SRC_IP_CIDR("SRC-IP-CIDR"),
|
||||
SRC_IP_SUFFIX("SRC-IP-SUFFIX"),
|
||||
DST_PORT("DST-PORT"),
|
||||
SRC_PORT("SRC-PORT"),
|
||||
IN_PORT("IN-PORT"),
|
||||
IN_TYPE("IN-TYPE"),
|
||||
IN_USER("IN-USER"),
|
||||
IN_NAME("IN-NAME"),
|
||||
PROCESS_PATH("PROCESS-PATH"),
|
||||
PROCESS_PATH_REGEX("PROCESS-PATH-REGEX"),
|
||||
PROCESS_NAME("PROCESS-NAME"),
|
||||
PROCESS_NAME_REGEX("PROCESS-NAME-REGEX"),
|
||||
UID("UID"),
|
||||
NETWORK("NETWORK"),
|
||||
DSCP("DSCP"),
|
||||
RULE_SET("RULE-SET"),
|
||||
AND("AND"),
|
||||
OR("OR"),
|
||||
NOT("NOT"),
|
||||
SUB_RULE("SUB-RULE"),
|
||||
MATCH("MATCH");
|
||||
|
||||
final String value;
|
||||
|
||||
const RuleAction(this.value);
|
||||
}
|
||||
|
||||
extension RuleActionExt on RuleAction {
|
||||
bool get hasParams => [
|
||||
RuleAction.GEOIP,
|
||||
RuleAction.IP_ASN,
|
||||
RuleAction.SRC_IP_ASN,
|
||||
RuleAction.IP_CIDR,
|
||||
RuleAction.IP_CIDR6,
|
||||
RuleAction.IP_SUFFIX,
|
||||
RuleAction.RULE_SET,
|
||||
].contains(this);
|
||||
}
|
||||
|
||||
enum OverrideRuleType {
|
||||
override,
|
||||
added,
|
||||
}
|
||||
|
||||
enum RuleTarget {
|
||||
DIRECT,
|
||||
REJECT,
|
||||
}
|
||||
|
||||
@@ -150,9 +150,17 @@ class _AccessFragmentState extends ConsumerState<AccessFragment> {
|
||||
return IconButton(
|
||||
onPressed: () async {
|
||||
final res = await showSheet<int>(
|
||||
title: appLocalizations.proxiesSetting,
|
||||
context: context,
|
||||
body: AccessControlPanel(),
|
||||
props: SheetProps(
|
||||
isScrollControlled: true,
|
||||
),
|
||||
builder: (_, type) {
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: AccessControlPanel(),
|
||||
title: appLocalizations.proxiesSetting,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (res == 1) {
|
||||
_intelligentSelected();
|
||||
@@ -763,17 +771,19 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
..._buildModeSetting(),
|
||||
..._buildSortSetting(),
|
||||
..._buildSourceSetting(),
|
||||
..._buildActionSetting(),
|
||||
],
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
..._buildModeSetting(),
|
||||
..._buildSortSetting(),
|
||||
..._buildSourceSetting(),
|
||||
..._buildActionSetting(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -276,8 +276,8 @@ class ApplicationSettingFragment extends StatelessWidget {
|
||||
AutoRunItem(),
|
||||
if (Platform.isAndroid) ...[
|
||||
HiddenItem(),
|
||||
AnimateTabItem(),
|
||||
],
|
||||
AnimateTabItem(),
|
||||
OpenLogsItem(),
|
||||
CloseConnectionsItem(),
|
||||
UsageItem(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/providers/config.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/dialog.dart';
|
||||
import 'package:fl_clash/widgets/fade_box.dart';
|
||||
import 'package:fl_clash/widgets/list.dart';
|
||||
import 'package:fl_clash/widgets/text.dart';
|
||||
@@ -174,7 +175,7 @@ class BackupAndRecovery extends ConsumerWidget {
|
||||
future: client!.pingCompleter.future,
|
||||
builder: (_, snapshot) {
|
||||
return Center(
|
||||
child: FadeBox(
|
||||
child: FadeThroughBox(
|
||||
child: snapshot.connectionState ==
|
||||
ConnectionState.waiting
|
||||
? const SizedBox(
|
||||
@@ -275,30 +276,27 @@ class _RecoveryOptionsDialogState extends State<RecoveryOptionsDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(appLocalizations.recovery),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
return CommonDialog(
|
||||
title: appLocalizations.recovery,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 16,
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 250,
|
||||
child: Wrap(
|
||||
children: [
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleOnTab(RecoveryOption.onlyProfiles);
|
||||
},
|
||||
title: Text(appLocalizations.recoveryProfiles),
|
||||
),
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleOnTab(RecoveryOption.all);
|
||||
},
|
||||
title: Text(appLocalizations.recoveryAll),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Wrap(
|
||||
children: [
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleOnTab(RecoveryOption.onlyProfiles);
|
||||
},
|
||||
title: Text(appLocalizations.recoveryProfiles),
|
||||
),
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleOnTab(RecoveryOption.all);
|
||||
},
|
||||
title: Text(appLocalizations.recoveryAll),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -351,78 +349,8 @@ class _WebDAVFormDialogState extends ConsumerState<WebDAVFormDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(appLocalizations.webDAVConfiguration),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: SizedBox(
|
||||
width: dialogCommonWidth,
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: uriController,
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.link),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: appLocalizations.address,
|
||||
helperText: appLocalizations.addressHelp,
|
||||
),
|
||||
validator: (String? value) {
|
||||
if (value == null || value.isEmpty || !value.isUrl) {
|
||||
return appLocalizations.addressTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
controller: userController,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.account_circle),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: appLocalizations.account,
|
||||
),
|
||||
validator: (String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return appLocalizations.accountTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _obscureController,
|
||||
builder: (_, obscure, __) {
|
||||
return TextFormField(
|
||||
controller: passwordController,
|
||||
obscureText: obscure,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.password),
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
obscure ? Icons.visibility : Icons.visibility_off,
|
||||
),
|
||||
onPressed: () {
|
||||
_obscureController.value = !obscure;
|
||||
},
|
||||
),
|
||||
labelText: appLocalizations.password,
|
||||
),
|
||||
validator: (String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return appLocalizations.passwordTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
return CommonDialog(
|
||||
title: appLocalizations.webDAVConfiguration,
|
||||
actions: [
|
||||
if (widget.dav != null)
|
||||
TextButton(
|
||||
@@ -434,6 +362,73 @@ class _WebDAVFormDialogState extends ConsumerState<WebDAVFormDialog> {
|
||||
child: Text(appLocalizations.save),
|
||||
)
|
||||
],
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: uriController,
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.link),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: appLocalizations.address,
|
||||
helperText: appLocalizations.addressHelp,
|
||||
),
|
||||
validator: (String? value) {
|
||||
if (value == null || value.isEmpty || !value.isUrl) {
|
||||
return appLocalizations.addressTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
controller: userController,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.account_circle),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: appLocalizations.account,
|
||||
),
|
||||
validator: (String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return appLocalizations.accountTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _obscureController,
|
||||
builder: (_, obscure, __) {
|
||||
return TextFormField(
|
||||
controller: passwordController,
|
||||
obscureText: obscure,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.password),
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
obscure ? Icons.visibility : Icons.visibility_off,
|
||||
),
|
||||
onPressed: () {
|
||||
_obscureController.value = !obscure;
|
||||
},
|
||||
),
|
||||
labelText: appLocalizations.password,
|
||||
),
|
||||
validator: (String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return appLocalizations.passwordTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,13 @@ import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/fragments/config/dns.dart';
|
||||
import 'package:fl_clash/fragments/config/general.dart';
|
||||
import 'package:fl_clash/fragments/config/network.dart';
|
||||
import 'package:fl_clash/models/clash_config.dart';
|
||||
import 'package:fl_clash/providers/config.dart' show patchClashConfigProvider;
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../state.dart';
|
||||
|
||||
class ConfigFragment extends StatefulWidget {
|
||||
const ConfigFragment({super.key});
|
||||
@@ -16,18 +21,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> items = [
|
||||
ListItem.open(
|
||||
title: Text(appLocalizations.network),
|
||||
subtitle: Text(appLocalizations.networkDesc),
|
||||
leading: const Icon(Icons.vpn_key),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.network,
|
||||
isScaffold: true,
|
||||
isBlur: false,
|
||||
extendPageWidth: 360,
|
||||
widget: const NetworkListView(),
|
||||
),
|
||||
),
|
||||
ListItem.open(
|
||||
title: Text(appLocalizations.general),
|
||||
subtitle: Text(appLocalizations.generalDesc),
|
||||
@@ -37,20 +30,52 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
widget: generateListView(
|
||||
generalItems,
|
||||
),
|
||||
isBlur: false,
|
||||
extendPageWidth: 360,
|
||||
blur: false,
|
||||
),
|
||||
),
|
||||
ListItem.open(
|
||||
title: Text(appLocalizations.network),
|
||||
subtitle: Text(appLocalizations.networkDesc),
|
||||
leading: const Icon(Icons.vpn_key),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.network,
|
||||
blur: false,
|
||||
widget: const NetworkListView(),
|
||||
),
|
||||
),
|
||||
ListItem.open(
|
||||
title: const Text("DNS"),
|
||||
subtitle: Text(appLocalizations.dnsDesc),
|
||||
leading: const Icon(Icons.dns),
|
||||
delegate: const OpenDelegate(
|
||||
delegate: OpenDelegate(
|
||||
title: "DNS",
|
||||
widget: DnsListView(),
|
||||
isScaffold: true,
|
||||
isBlur: false,
|
||||
extendPageWidth: 360,
|
||||
action: Consumer(builder: (_, ref, __) {
|
||||
return IconButton(
|
||||
onPressed: () async {
|
||||
final res = await globalState.showMessage(
|
||||
title: appLocalizations.reset,
|
||||
message: TextSpan(
|
||||
text: appLocalizations.resetTip,
|
||||
),
|
||||
);
|
||||
if (res != true) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
dns: defaultDns,
|
||||
),
|
||||
);
|
||||
},
|
||||
tooltip: appLocalizations.reset,
|
||||
icon: const Icon(
|
||||
Icons.replay,
|
||||
),
|
||||
);
|
||||
}),
|
||||
widget: const DnsListView(),
|
||||
blur: false,
|
||||
),
|
||||
)
|
||||
];
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/providers/config.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@@ -48,6 +46,32 @@ class StatusItem extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ListenItem extends ConsumerWidget {
|
||||
const ListenItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final listen =
|
||||
ref.watch(patchClashConfigProvider.select((state) => state.dns.listen));
|
||||
return ListItem.input(
|
||||
title: Text(appLocalizations.listen),
|
||||
subtitle: Text(listen),
|
||||
delegate: InputDelegate(
|
||||
title: appLocalizations.listen,
|
||||
value: listen,
|
||||
onChanged: (String? value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
ref
|
||||
.read(patchClashConfigProvider.notifier)
|
||||
.updateState((state) => state.copyWith.dns(listen: value));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PreferH3Item extends ConsumerWidget {
|
||||
const PreferH3Item({super.key});
|
||||
|
||||
@@ -179,7 +203,7 @@ class FakeIpFilterItem extends StatelessWidget {
|
||||
return ListItem.open(
|
||||
title: Text(appLocalizations.fakeipFilter),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
blur: false,
|
||||
title: appLocalizations.fakeipFilter,
|
||||
widget: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
@@ -187,7 +211,7 @@ class FakeIpFilterItem extends StatelessWidget {
|
||||
patchClashConfigProvider
|
||||
.select((state) => state.dns.fakeIpFilter),
|
||||
);
|
||||
return ListPage(
|
||||
return ListInputPage(
|
||||
title: appLocalizations.fakeipFilter,
|
||||
items: fakeIpFilter,
|
||||
titleBuilder: (item) => Text(item),
|
||||
@@ -201,7 +225,6 @@ class FakeIpFilterItem extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -216,14 +239,14 @@ class DefaultNameserverItem extends StatelessWidget {
|
||||
title: Text(appLocalizations.defaultNameserver),
|
||||
subtitle: Text(appLocalizations.defaultNameserverDesc),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
blur: false,
|
||||
title: appLocalizations.defaultNameserver,
|
||||
widget: Consumer(builder: (_, ref, __) {
|
||||
final defaultNameserver = ref.watch(
|
||||
patchClashConfigProvider
|
||||
.select((state) => state.dns.defaultNameserver),
|
||||
);
|
||||
return ListPage(
|
||||
return ListInputPage(
|
||||
title: appLocalizations.defaultNameserver,
|
||||
items: defaultNameserver,
|
||||
titleBuilder: (item) => Text(item),
|
||||
@@ -236,7 +259,6 @@ class DefaultNameserverItem extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -252,13 +274,13 @@ class NameserverItem extends StatelessWidget {
|
||||
subtitle: Text(appLocalizations.nameserverDesc),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.nameserver,
|
||||
isBlur: false,
|
||||
blur: false,
|
||||
widget: Consumer(builder: (_, ref, __) {
|
||||
final nameserver = ref.watch(
|
||||
patchClashConfigProvider.select((state) => state.dns.nameserver),
|
||||
);
|
||||
return ListPage(
|
||||
title: "域名服务器",
|
||||
return ListInputPage(
|
||||
title: appLocalizations.nameserver,
|
||||
items: nameserver,
|
||||
titleBuilder: (item) => Text(item),
|
||||
onChange: (items) {
|
||||
@@ -270,7 +292,6 @@ class NameserverItem extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -331,28 +352,27 @@ class NameserverPolicyItem extends StatelessWidget {
|
||||
title: Text(appLocalizations.nameserverPolicy),
|
||||
subtitle: Text(appLocalizations.nameserverPolicyDesc),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
blur: false,
|
||||
title: appLocalizations.nameserverPolicy,
|
||||
widget: Consumer(builder: (_, ref, __) {
|
||||
final nameserverPolicy = ref.watch(
|
||||
patchClashConfigProvider
|
||||
.select((state) => state.dns.nameserverPolicy),
|
||||
);
|
||||
return ListPage(
|
||||
return MapInputPage(
|
||||
title: appLocalizations.nameserverPolicy,
|
||||
items: nameserverPolicy.entries,
|
||||
map: nameserverPolicy,
|
||||
titleBuilder: (item) => Text(item.key),
|
||||
subtitleBuilder: (item) => Text(item.value),
|
||||
onChange: (items) {
|
||||
onChange: (value) {
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith.dns(
|
||||
nameserverPolicy: Map.fromEntries(items),
|
||||
nameserverPolicy: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -367,7 +387,7 @@ class ProxyServerNameserverItem extends StatelessWidget {
|
||||
title: Text(appLocalizations.proxyNameserver),
|
||||
subtitle: Text(appLocalizations.proxyNameserverDesc),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
blur: false,
|
||||
title: appLocalizations.proxyNameserver,
|
||||
widget: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
@@ -375,7 +395,7 @@ class ProxyServerNameserverItem extends StatelessWidget {
|
||||
patchClashConfigProvider
|
||||
.select((state) => state.dns.proxyServerNameserver),
|
||||
);
|
||||
return ListPage(
|
||||
return ListInputPage(
|
||||
title: appLocalizations.proxyNameserver,
|
||||
items: proxyServerNameserver,
|
||||
titleBuilder: (item) => Text(item),
|
||||
@@ -389,7 +409,6 @@ class ProxyServerNameserverItem extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -404,13 +423,13 @@ class FallbackItem extends StatelessWidget {
|
||||
title: Text(appLocalizations.fallback),
|
||||
subtitle: Text(appLocalizations.fallbackDesc),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
blur: false,
|
||||
title: appLocalizations.fallback,
|
||||
widget: Consumer(builder: (_, ref, __) {
|
||||
final fallback = ref.watch(
|
||||
patchClashConfigProvider.select((state) => state.dns.fallback),
|
||||
);
|
||||
return ListPage(
|
||||
return ListInputPage(
|
||||
title: appLocalizations.fallback,
|
||||
items: fallback,
|
||||
titleBuilder: (item) => Text(item),
|
||||
@@ -423,7 +442,6 @@ class FallbackItem extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -492,14 +510,14 @@ class GeositeItem extends StatelessWidget {
|
||||
return ListItem.open(
|
||||
title: const Text("Geosite"),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
blur: false,
|
||||
title: "Geosite",
|
||||
widget: Consumer(builder: (_, ref, __) {
|
||||
final geosite = ref.watch(
|
||||
patchClashConfigProvider
|
||||
.select((state) => state.dns.fallbackFilter.geosite),
|
||||
);
|
||||
return ListPage(
|
||||
return ListInputPage(
|
||||
title: "Geosite",
|
||||
items: geosite,
|
||||
titleBuilder: (item) => Text(item),
|
||||
@@ -512,7 +530,6 @@ class GeositeItem extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -526,14 +543,14 @@ class IpcidrItem extends StatelessWidget {
|
||||
return ListItem.open(
|
||||
title: Text(appLocalizations.ipcidr),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
blur: false,
|
||||
title: appLocalizations.ipcidr,
|
||||
widget: Consumer(builder: (_, ref, ___) {
|
||||
final ipcidr = ref.watch(
|
||||
patchClashConfigProvider
|
||||
.select((state) => state.dns.fallbackFilter.ipcidr),
|
||||
);
|
||||
return ListPage(
|
||||
return ListInputPage(
|
||||
title: appLocalizations.ipcidr,
|
||||
items: ipcidr,
|
||||
titleBuilder: (item) => Text(item),
|
||||
@@ -546,7 +563,6 @@ class IpcidrItem extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -560,14 +576,14 @@ class DomainItem extends StatelessWidget {
|
||||
return ListItem.open(
|
||||
title: Text(appLocalizations.domain),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
blur: false,
|
||||
title: appLocalizations.domain,
|
||||
widget: Consumer(builder: (_, ref, __) {
|
||||
final domain = ref.watch(
|
||||
patchClashConfigProvider
|
||||
.select((state) => state.dns.fallbackFilter.domain),
|
||||
);
|
||||
return ListPage(
|
||||
return ListInputPage(
|
||||
title: appLocalizations.domain,
|
||||
items: domain,
|
||||
titleBuilder: (item) => Text(item),
|
||||
@@ -580,7 +596,6 @@ class DomainItem extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -596,6 +611,7 @@ class DnsOptions extends StatelessWidget {
|
||||
title: appLocalizations.options,
|
||||
items: [
|
||||
const StatusItem(),
|
||||
const ListenItem(),
|
||||
const UseHostsItem(),
|
||||
const UseSystemHostsItem(),
|
||||
const IPv6Item(),
|
||||
@@ -644,39 +660,8 @@ const dnsItems = <Widget>[
|
||||
class DnsListView extends ConsumerWidget {
|
||||
const DnsListView({super.key});
|
||||
|
||||
_initActions(BuildContext context, WidgetRef ref) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
context.commonScaffoldState?.actions = [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
final res = await globalState.showMessage(
|
||||
title: appLocalizations.reset,
|
||||
message: TextSpan(
|
||||
text: appLocalizations.resetTip,
|
||||
),
|
||||
);
|
||||
if (res != true) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
dns: defaultDns,
|
||||
),
|
||||
);
|
||||
},
|
||||
tooltip: appLocalizations.reset,
|
||||
icon: const Icon(
|
||||
Icons.replay,
|
||||
),
|
||||
)
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
_initActions(context, ref);
|
||||
return generateListView(
|
||||
dnsItems,
|
||||
);
|
||||
|
||||
@@ -199,28 +199,27 @@ class HostsItem extends StatelessWidget {
|
||||
title: const Text("Hosts"),
|
||||
subtitle: Text(appLocalizations.hostsDesc),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
blur: false,
|
||||
title: "Hosts",
|
||||
widget: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
final hosts = ref
|
||||
.watch(patchClashConfigProvider.select((state) => state.hosts));
|
||||
return ListPage(
|
||||
return MapInputPage(
|
||||
title: "Hosts",
|
||||
items: hosts.entries,
|
||||
map: hosts,
|
||||
titleBuilder: (item) => Text(item.key),
|
||||
subtitleBuilder: (item) => Text(item.value),
|
||||
onChange: (items) {
|
||||
onChange: (value) {
|
||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
hosts: Map.fromEntries(items),
|
||||
hosts: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -224,15 +224,14 @@ class BypassDomainItem extends StatelessWidget {
|
||||
title: Text(appLocalizations.bypassDomain),
|
||||
subtitle: Text(appLocalizations.bypassDomainDesc),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
isScaffold: true,
|
||||
blur: false,
|
||||
title: appLocalizations.bypassDomain,
|
||||
widget: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
_initActions(context, ref);
|
||||
final bypassDomain = ref.watch(
|
||||
networkSettingProvider.select((state) => state.bypassDomain));
|
||||
return ListPage(
|
||||
return ListInputPage(
|
||||
title: appLocalizations.bypassDomain,
|
||||
items: bypassDomain,
|
||||
titleBuilder: (item) => Text(item),
|
||||
@@ -246,7 +245,6 @@ class BypassDomainItem extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -298,14 +296,14 @@ class RouteAddressItem extends ConsumerWidget {
|
||||
title: Text(appLocalizations.routeAddress),
|
||||
subtitle: Text(appLocalizations.routeAddressDesc),
|
||||
delegate: OpenDelegate(
|
||||
isBlur: false,
|
||||
isScaffold: true,
|
||||
blur: false,
|
||||
maxWidth: 360,
|
||||
title: appLocalizations.routeAddress,
|
||||
widget: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
final routeAddress = ref.watch(patchClashConfigProvider
|
||||
.select((state) => state.tun.routeAddress));
|
||||
return ListPage(
|
||||
return ListInputPage(
|
||||
title: appLocalizations.routeAddress,
|
||||
items: routeAddress,
|
||||
titleBuilder: (item) => Text(item),
|
||||
@@ -319,7 +317,6 @@ class RouteAddressItem extends ConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ class _ConnectionsFragmentState extends ConsumerState<ConnectionsFragment>
|
||||
return ConnectionItem(
|
||||
key: Key(connection.id),
|
||||
connection: connection,
|
||||
onClick: (value) {
|
||||
onClickKeyword: (value) {
|
||||
context.commonScaffoldState?.addKeyword(value);
|
||||
},
|
||||
trailing: IconButton(
|
||||
|
||||
@@ -9,40 +9,15 @@ import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class FindProcessBuilder extends StatelessWidget {
|
||||
final Widget Function(bool value) builder;
|
||||
|
||||
const FindProcessBuilder({
|
||||
super.key,
|
||||
required this.builder,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer(
|
||||
builder: (_, ref, __) {
|
||||
final value = ref.watch(
|
||||
patchClashConfigProvider.select(
|
||||
(state) =>
|
||||
state.findProcessMode == FindProcessMode.always &&
|
||||
Platform.isAndroid,
|
||||
),
|
||||
);
|
||||
return builder(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectionItem extends StatelessWidget {
|
||||
class ConnectionItem extends ConsumerWidget {
|
||||
final Connection connection;
|
||||
final Function(String)? onClick;
|
||||
final Function(String)? onClickKeyword;
|
||||
final Widget? trailing;
|
||||
|
||||
const ConnectionItem({
|
||||
super.key,
|
||||
required this.connection,
|
||||
this.onClick,
|
||||
this.onClickKeyword,
|
||||
this.trailing,
|
||||
});
|
||||
|
||||
@@ -59,7 +34,14 @@ class ConnectionItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, ref) {
|
||||
final value = ref.watch(
|
||||
patchClashConfigProvider.select(
|
||||
(state) =>
|
||||
state.findProcessMode == FindProcessMode.always &&
|
||||
Platform.isAndroid,
|
||||
),
|
||||
);
|
||||
final title = Text(
|
||||
connection.desc,
|
||||
style: context.textTheme.bodyLarge,
|
||||
@@ -86,70 +68,143 @@ class ConnectionItem extends StatelessWidget {
|
||||
CommonChip(
|
||||
label: chain,
|
||||
onPressed: () {
|
||||
if (onClick == null) return;
|
||||
onClick!(chain);
|
||||
if (onClickKeyword == null) return;
|
||||
onClickKeyword!(chain);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
if (!Platform.isAndroid) {
|
||||
return ListItem(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
||||
title: title,
|
||||
subtitle: subTitle,
|
||||
trailing: trailing,
|
||||
);
|
||||
}
|
||||
return FindProcessBuilder(
|
||||
builder: (bool value) {
|
||||
final leading = value
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
if (onClick == null) return;
|
||||
final process = connection.metadata.process;
|
||||
if (process.isEmpty) return;
|
||||
onClick!(process);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: FutureBuilder<ImageProvider?>(
|
||||
future: _getPackageIcon(connection),
|
||||
builder: (_, snapshot) {
|
||||
if (!snapshot.hasData && snapshot.data == null) {
|
||||
return Container();
|
||||
} else {
|
||||
return Image(
|
||||
image: snapshot.data!,
|
||||
gaplessPlayback: true,
|
||||
width: 48,
|
||||
height: 48,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
: null;
|
||||
return CommonPopupBox(
|
||||
targetBuilder: (open) {
|
||||
return ListItem(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
||||
leading: leading,
|
||||
leading: value
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
if (onClickKeyword == null) return;
|
||||
final process = connection.metadata.process;
|
||||
if (process.isEmpty) return;
|
||||
onClickKeyword!(process);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: FutureBuilder<ImageProvider?>(
|
||||
future: _getPackageIcon(connection),
|
||||
builder: (_, snapshot) {
|
||||
if (!snapshot.hasData && snapshot.data == null) {
|
||||
return Container();
|
||||
} else {
|
||||
return Image(
|
||||
image: snapshot.data!,
|
||||
gaplessPlayback: true,
|
||||
width: 48,
|
||||
height: 48,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
title: title,
|
||||
subtitle: subTitle,
|
||||
trailing: trailing,
|
||||
);
|
||||
// return InkWell(
|
||||
// child: GestureDetector(
|
||||
// onLongPressStart: (details) {
|
||||
// if (!system.isDesktop) {
|
||||
// return;
|
||||
// }
|
||||
// open(
|
||||
// offset: details.localPosition.translate(
|
||||
// 0,
|
||||
// -12,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// onSecondaryTapDown: (details) {
|
||||
// if (!system.isDesktop) {
|
||||
// return;
|
||||
// }
|
||||
// open(
|
||||
// offset: details.localPosition.translate(
|
||||
// 0,
|
||||
// -12,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// child: ListItem(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// horizontal: 16,
|
||||
// vertical: 4,
|
||||
// ),
|
||||
// tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
||||
// leading: value
|
||||
// ? GestureDetector(
|
||||
// onTap: () {
|
||||
// if (onClickKeyword == null) return;
|
||||
// final process = connection.metadata.process;
|
||||
// if (process.isEmpty) return;
|
||||
// onClickKeyword!(process);
|
||||
// },
|
||||
// child: Container(
|
||||
// margin: const EdgeInsets.only(top: 4),
|
||||
// width: 48,
|
||||
// height: 48,
|
||||
// child: FutureBuilder<ImageProvider?>(
|
||||
// future: _getPackageIcon(connection),
|
||||
// builder: (_, snapshot) {
|
||||
// if (!snapshot.hasData && snapshot.data == null) {
|
||||
// return Container();
|
||||
// } else {
|
||||
// return Image(
|
||||
// image: snapshot.data!,
|
||||
// gaplessPlayback: true,
|
||||
// width: 48,
|
||||
// height: 48,
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// : null,
|
||||
// title: title,
|
||||
// subtitle: subTitle,
|
||||
// trailing: trailing,
|
||||
// ),
|
||||
// ),
|
||||
// onTap: () {},
|
||||
// );
|
||||
},
|
||||
popup: CommonPopupMenu(
|
||||
minWidth: 160,
|
||||
items: [
|
||||
PopupMenuItemData(
|
||||
label: "编辑规则",
|
||||
onPressed: () {
|
||||
// _handleShowEditExtendPage(context);
|
||||
},
|
||||
),
|
||||
PopupMenuItemData(
|
||||
label: "设置直连",
|
||||
onPressed: () {},
|
||||
),
|
||||
PopupMenuItemData(
|
||||
label: "一键屏蔽",
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
@@ -20,6 +22,7 @@ class RequestsFragment extends ConsumerStatefulWidget {
|
||||
|
||||
class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
with PageMixin {
|
||||
final GlobalKey<CacheItemExtentListViewState> _key = GlobalKey();
|
||||
final _requestsStateNotifier =
|
||||
ValueNotifier<ConnectionsState>(const ConnectionsState());
|
||||
List<Connection> _requests = [];
|
||||
@@ -28,8 +31,6 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
initialScrollOffset: _preOffset != 0 ? _preOffset : double.maxFinite,
|
||||
);
|
||||
|
||||
final FixedMap<String, double?> _cacheDynamicHeightMap = FixedMap(1000);
|
||||
|
||||
double _currentMaxWidth = 0;
|
||||
|
||||
@override
|
||||
@@ -78,10 +79,6 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
}
|
||||
|
||||
double _calcCacheHeight(Connection item) {
|
||||
final cacheHeight = _cacheDynamicHeightMap.get(item.id);
|
||||
if (cacheHeight != null) {
|
||||
return cacheHeight;
|
||||
}
|
||||
final size = globalState.measure.computeTextSize(
|
||||
Text(
|
||||
item.desc,
|
||||
@@ -102,14 +99,13 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
final lines = (chainSize.height / baseHeight).round();
|
||||
final computerHeight =
|
||||
size.height + chainSize.height + 24 + 24 * (lines - 1);
|
||||
_cacheDynamicHeightMap.put(item.id, computerHeight);
|
||||
return computerHeight;
|
||||
}
|
||||
|
||||
_handleTryClearCache(double maxWidth) {
|
||||
if (_currentMaxWidth != maxWidth) {
|
||||
_currentMaxWidth = maxWidth;
|
||||
_cacheDynamicHeightMap.clear();
|
||||
_key.currentState?.clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +114,6 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
_requestsStateNotifier.dispose();
|
||||
_scrollController.dispose();
|
||||
_currentMaxWidth = 0;
|
||||
_cacheDynamicHeightMap.clear();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -143,9 +138,19 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (_, constraints) {
|
||||
return FindProcessBuilder(builder: (value) {
|
||||
_handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0));
|
||||
return ValueListenableBuilder<ConnectionsState>(
|
||||
return Consumer(
|
||||
builder: (_, ref, child) {
|
||||
final value = ref.watch(
|
||||
patchClashConfigProvider.select(
|
||||
(state) =>
|
||||
state.findProcessMode == FindProcessMode.always &&
|
||||
Platform.isAndroid,
|
||||
),
|
||||
);
|
||||
_handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0));
|
||||
return child!;
|
||||
},
|
||||
child: ValueListenableBuilder<ConnectionsState>(
|
||||
valueListenable: _requestsStateNotifier,
|
||||
builder: (_, state, __) {
|
||||
final connections = state.list;
|
||||
@@ -159,7 +164,7 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
(connection) => ConnectionItem(
|
||||
key: Key(connection.id),
|
||||
connection: connection,
|
||||
onClick: (value) {
|
||||
onClickKeyword: (value) {
|
||||
context.commonScaffoldState?.addKeyword(value);
|
||||
},
|
||||
),
|
||||
@@ -179,12 +184,13 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
},
|
||||
child: CommonScrollBar(
|
||||
controller: _scrollController,
|
||||
child: ListView.builder(
|
||||
child: CacheItemExtentListView(
|
||||
key: _key,
|
||||
reverse: true,
|
||||
shrinkWrap: true,
|
||||
physics: NextClampingScrollPhysics(),
|
||||
controller: _scrollController,
|
||||
itemExtentBuilder: (index, __) {
|
||||
itemExtentBuilder: (index) {
|
||||
final widget = items[index];
|
||||
if (widget.runtimeType == Divider) {
|
||||
return 0;
|
||||
@@ -199,13 +205,21 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
return items[index];
|
||||
},
|
||||
itemCount: items.length,
|
||||
keyBuilder: (int index) {
|
||||
final widget = items[index];
|
||||
if (widget.runtimeType == Divider) {
|
||||
return "divider";
|
||||
}
|
||||
final connection = connections[(index / 2).floor()];
|
||||
return connection.id;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class IntranetIP extends StatelessWidget {
|
||||
child: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
final localIp = ref.watch(localIpProvider);
|
||||
return FadeBox(
|
||||
return FadeThroughBox(
|
||||
child: localIp != null
|
||||
? TooltipText(
|
||||
text: Text(
|
||||
|
||||
@@ -39,7 +39,7 @@ class _MemoryInfoState extends State<MemoryInfo> {
|
||||
_memoryInfoStateNotifier.value = TrafficValue(
|
||||
value: clashLib != null ? rss : await clashCore.getMemory() + rss,
|
||||
);
|
||||
timer = Timer(Duration(seconds: 5), () async {
|
||||
timer = Timer(Duration(seconds: 2), () async {
|
||||
_updateMemory();
|
||||
});
|
||||
});
|
||||
@@ -47,13 +47,8 @@ class _MemoryInfoState extends State<MemoryInfo> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final darkenLighter = context.colorScheme.secondaryContainer
|
||||
.blendDarken(context, factor: 0.1)
|
||||
.toLighter;
|
||||
final darken = context.colorScheme.secondaryContainer
|
||||
.blendDarken(context, factor: 0.1);
|
||||
return SizedBox(
|
||||
height: getWidgetHeight(2),
|
||||
height: getWidgetHeight(1),
|
||||
child: CommonCard(
|
||||
info: Info(
|
||||
iconData: Icons.memory,
|
||||
@@ -62,12 +57,12 @@ class _MemoryInfoState extends State<MemoryInfo> {
|
||||
onPressed: () {
|
||||
clashCore.requestGc();
|
||||
},
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: _memoryInfoStateNotifier,
|
||||
builder: (_, trafficValue, __) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
child: Column(
|
||||
children: [
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _memoryInfoStateNotifier,
|
||||
builder: (_, trafficValue, __) {
|
||||
return Padding(
|
||||
padding: baseInfoEdgeInsets.copyWith(
|
||||
bottom: 0,
|
||||
top: 12,
|
||||
@@ -76,43 +71,94 @@ class _MemoryInfoState extends State<MemoryInfo> {
|
||||
children: [
|
||||
Text(
|
||||
trafficValue.showValue,
|
||||
style: context.textTheme.titleLarge?.toLight,
|
||||
style:
|
||||
context.textTheme.bodyMedium?.toLight.adjustSize(1),
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Text(
|
||||
trafficValue.showUnit,
|
||||
style: context.textTheme.titleLarge?.toLight,
|
||||
style:
|
||||
context.textTheme.bodyMedium?.toLight.adjustSize(1),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: WaveView(
|
||||
waveAmplitude: 12.0,
|
||||
waveFrequency: 0.35,
|
||||
waveColor: darkenLighter,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: WaveView(
|
||||
waveAmplitude: 12.0,
|
||||
waveFrequency: 0.9,
|
||||
waveColor: darken,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// class AnimatedCounter extends StatefulWidget {
|
||||
// final double value;
|
||||
// final TextStyle? style;
|
||||
//
|
||||
// const AnimatedCounter({
|
||||
// super.key,
|
||||
// required this.value,
|
||||
// this.style,
|
||||
// });
|
||||
//
|
||||
// @override
|
||||
// State<AnimatedCounter> createState() => _AnimatedCounterState();
|
||||
// }
|
||||
//
|
||||
// class _AnimatedCounterState extends State<AnimatedCounter> {
|
||||
// late double _previousValue;
|
||||
// late double _currentValue;
|
||||
//
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// _previousValue = widget.value;
|
||||
// _currentValue = widget.value;
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// void didUpdateWidget(AnimatedCounter oldWidget) {
|
||||
// super.didUpdateWidget(oldWidget);
|
||||
// if (oldWidget.value != widget.value) {
|
||||
// // if (_previousValue == _currentValue) {
|
||||
// // _previousValue = widget.value;
|
||||
// // _currentValue = widget.value;
|
||||
// // return;
|
||||
// // }
|
||||
// _currentValue = widget.value;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// void dispose() {
|
||||
// super.dispose();
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Text(
|
||||
// _currentValue.fixed(decimals: 1),
|
||||
// style: widget.style,
|
||||
// );
|
||||
// return TweenAnimationBuilder(
|
||||
// tween: Tween(
|
||||
// begin: _previousValue,
|
||||
// end: _currentValue,
|
||||
// ),
|
||||
// onEnd: () {
|
||||
// _previousValue = _currentValue;
|
||||
// },
|
||||
// duration: Duration(seconds: 6),
|
||||
// curve: Curves.easeOut,
|
||||
// builder: (_, value, ___) {
|
||||
// return Text(
|
||||
// value.fixed(decimals: 1),
|
||||
// style: widget.style,
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -12,7 +12,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final _networkDetectionState = ValueNotifier<NetworkDetectionState>(
|
||||
const NetworkDetectionState(
|
||||
isTesting: true,
|
||||
isTesting: false,
|
||||
isLoading: true,
|
||||
ipInfo: null,
|
||||
),
|
||||
);
|
||||
@@ -28,7 +29,6 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
||||
bool? _preIsStart;
|
||||
Timer? _setTimeoutTimer;
|
||||
CancelToken? cancelToken;
|
||||
Completer? checkedCompleter;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -37,11 +37,14 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
||||
_startCheck();
|
||||
}
|
||||
});
|
||||
if (!_networkDetectionState.value.isTesting &&
|
||||
_networkDetectionState.value.isLoading) {
|
||||
_startCheck();
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
_startCheck() async {
|
||||
await checkedCompleter?.future;
|
||||
if (cancelToken != null) {
|
||||
cancelToken!.cancel();
|
||||
cancelToken = null;
|
||||
@@ -59,10 +62,12 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
||||
final isStart = appState.runTime != null;
|
||||
if (_preIsStart == false &&
|
||||
_preIsStart == isStart &&
|
||||
_networkDetectionState.value.ipInfo != null) return;
|
||||
_networkDetectionState.value.ipInfo != null) {
|
||||
return;
|
||||
}
|
||||
_clearSetTimeoutTimer();
|
||||
_networkDetectionState.value = _networkDetectionState.value.copyWith(
|
||||
isTesting: true,
|
||||
isLoading: true,
|
||||
ipInfo: null,
|
||||
);
|
||||
_preIsStart = isStart;
|
||||
@@ -72,16 +77,16 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
||||
}
|
||||
cancelToken = CancelToken();
|
||||
try {
|
||||
_networkDetectionState.value = _networkDetectionState.value.copyWith(
|
||||
isTesting: true,
|
||||
);
|
||||
final ipInfo = await request.checkIp(cancelToken: cancelToken);
|
||||
_networkDetectionState.value = _networkDetectionState.value.copyWith(
|
||||
isTesting: false,
|
||||
);
|
||||
if (ipInfo != null) {
|
||||
checkedCompleter = Completer();
|
||||
checkedCompleter?.complete(
|
||||
Future.delayed(
|
||||
Duration(milliseconds: 3000),
|
||||
),
|
||||
);
|
||||
_networkDetectionState.value = _networkDetectionState.value.copyWith(
|
||||
isTesting: false,
|
||||
isLoading: false,
|
||||
ipInfo: ipInfo,
|
||||
);
|
||||
return;
|
||||
@@ -89,14 +94,14 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
||||
_clearSetTimeoutTimer();
|
||||
_setTimeoutTimer = Timer(const Duration(milliseconds: 300), () {
|
||||
_networkDetectionState.value = _networkDetectionState.value.copyWith(
|
||||
isTesting: false,
|
||||
isLoading: false,
|
||||
ipInfo: null,
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.toString() == "cancelled") {
|
||||
_networkDetectionState.value = _networkDetectionState.value.copyWith(
|
||||
isTesting: true,
|
||||
isLoading: true,
|
||||
ipInfo: null,
|
||||
);
|
||||
}
|
||||
@@ -134,7 +139,7 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
||||
valueListenable: _networkDetectionState,
|
||||
builder: (_, state, __) {
|
||||
final ipInfo = state.ipInfo;
|
||||
final isTesting = state.isTesting;
|
||||
final isLoading = state.isLoading;
|
||||
return CommonCard(
|
||||
onPressed: () {},
|
||||
child: Column(
|
||||
@@ -216,7 +221,7 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
||||
),
|
||||
child: SizedBox(
|
||||
height: globalState.measure.bodyMediumHeight + 2,
|
||||
child: FadeBox(
|
||||
child: FadeThroughBox(
|
||||
child: ipInfo != null
|
||||
? TooltipText(
|
||||
text: Text(
|
||||
@@ -227,8 +232,8 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)
|
||||
: FadeBox(
|
||||
child: isTesting == false && ipInfo == null
|
||||
: FadeThroughBox(
|
||||
child: isLoading == false && ipInfo == null
|
||||
? Text(
|
||||
"timeout",
|
||||
style: context.textTheme.bodyMedium
|
||||
|
||||
@@ -41,7 +41,7 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = context.colorScheme.onSurfaceVariant.toLight;
|
||||
final color = context.colorScheme.onSurfaceVariant.opacity80;
|
||||
return SizedBox(
|
||||
height: getWidgetHeight(2),
|
||||
child: CommonCard(
|
||||
|
||||
@@ -38,7 +38,7 @@ class OutboundMode extends StatelessWidget {
|
||||
for (final item in Mode.values)
|
||||
Flexible(
|
||||
child: ListItem.radio(
|
||||
prue: true,
|
||||
dense: true,
|
||||
horizontalTitleGap: 4,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12,
|
||||
|
||||
@@ -16,13 +16,20 @@ class TUNButton extends StatelessWidget {
|
||||
onPressed: () {
|
||||
showSheet(
|
||||
context: context,
|
||||
body: generateListView(generateSection(
|
||||
items: [
|
||||
if (system.isDesktop) const TUNItem(),
|
||||
const TunStackItem(),
|
||||
],
|
||||
)),
|
||||
title: appLocalizations.tun,
|
||||
builder: (_, type) {
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: generateListView(
|
||||
generateSection(
|
||||
items: [
|
||||
if (system.isDesktop) const TUNItem(),
|
||||
const TunStackItem(),
|
||||
],
|
||||
),
|
||||
),
|
||||
title: appLocalizations.tun,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
info: Info(
|
||||
@@ -89,15 +96,20 @@ class SystemProxyButton extends StatelessWidget {
|
||||
onPressed: () {
|
||||
showSheet(
|
||||
context: context,
|
||||
body: generateListView(
|
||||
generateSection(
|
||||
items: [
|
||||
SystemProxyItem(),
|
||||
BypassDomainItem(),
|
||||
],
|
||||
),
|
||||
),
|
||||
title: appLocalizations.systemProxy,
|
||||
builder: (_, type) {
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: generateListView(
|
||||
generateSection(
|
||||
items: [
|
||||
SystemProxyItem(),
|
||||
BypassDomainItem(),
|
||||
],
|
||||
),
|
||||
),
|
||||
title: appLocalizations.systemProxy,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
info: Info(
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
class TrafficUsage extends StatelessWidget {
|
||||
const TrafficUsage({super.key});
|
||||
|
||||
Widget getTrafficDataItem(
|
||||
Widget _buildTrafficDataItem(
|
||||
BuildContext context,
|
||||
Icon icon,
|
||||
TrafficValue trafficValue,
|
||||
@@ -51,10 +51,8 @@ class TrafficUsage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final primaryColor =
|
||||
context.colorScheme.surfaceContainer.blendDarken(context, factor: 0.2);
|
||||
final secondaryColor =
|
||||
context.colorScheme.primaryContainer.blendDarken(context, factor: 0.3);
|
||||
final primaryColor = globalState.theme.darken3PrimaryContainer;
|
||||
final secondaryColor = globalState.theme.darken2SecondaryContainer;
|
||||
return SizedBox(
|
||||
height: getWidgetHeight(2),
|
||||
child: CommonCard(
|
||||
@@ -189,7 +187,7 @@ class TrafficUsage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
getTrafficDataItem(
|
||||
_buildTrafficDataItem(
|
||||
context,
|
||||
Icon(
|
||||
Icons.arrow_upward,
|
||||
@@ -201,7 +199,7 @@ class TrafficUsage extends StatelessWidget {
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
getTrafficDataItem(
|
||||
_buildTrafficDataItem(
|
||||
context,
|
||||
Icon(
|
||||
Icons.arrow_downward,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/card.dart';
|
||||
import 'package:fl_clash/widgets/dialog.dart';
|
||||
import 'package:fl_clash/widgets/list.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -156,9 +157,28 @@ class _HotKeyRecorderState extends State<HotKeyRecorder> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(IntlExt.actionMessage((widget.hotKeyAction.action.name))),
|
||||
content: ValueListenableBuilder(
|
||||
return CommonDialog(
|
||||
title: IntlExt.actionMessage(widget.hotKeyAction.action.name),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_handleRemove();
|
||||
},
|
||||
child: Text(appLocalizations.remove),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_handleConfirm();
|
||||
},
|
||||
child: Text(
|
||||
appLocalizations.confirm,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: hotKeyActionNotifier,
|
||||
builder: (_, hotKeyAction, ___) {
|
||||
final key = hotKeyAction.key;
|
||||
@@ -191,25 +211,6 @@ class _HotKeyRecorderState extends State<HotKeyRecorder> {
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_handleRemove();
|
||||
},
|
||||
child: Text(appLocalizations.remove),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_handleConfirm();
|
||||
},
|
||||
child: Text(
|
||||
appLocalizations.confirm,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
final _scrollController = ScrollController(
|
||||
initialScrollOffset: _preOffset != 0 ? _preOffset : double.maxFinite,
|
||||
);
|
||||
final FixedMap<String, double?> _cacheDynamicHeightMap = FixedMap(1000);
|
||||
double _currentMaxWidth = 0;
|
||||
final GlobalKey<CacheItemExtentListViewState> _key = GlobalKey();
|
||||
|
||||
List<Log> _logs = [];
|
||||
|
||||
@@ -90,14 +90,13 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
void dispose() {
|
||||
_logsStateNotifier.dispose();
|
||||
_scrollController.dispose();
|
||||
_cacheDynamicHeightMap.clear();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_handleTryClearCache(double maxWidth) {
|
||||
if (_currentMaxWidth != maxWidth) {
|
||||
_currentMaxWidth = maxWidth;
|
||||
_cacheDynamicHeightMap.clear();
|
||||
_key.currentState?.clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,27 +115,19 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
);
|
||||
}
|
||||
|
||||
double _calcCacheHeight(String text) {
|
||||
final cacheHeight = _cacheDynamicHeightMap.get(text);
|
||||
if (cacheHeight != null) {
|
||||
return cacheHeight;
|
||||
}
|
||||
final size = globalState.measure.computeTextSize(
|
||||
Text(
|
||||
text,
|
||||
style: globalState.appController.context.textTheme.bodyLarge,
|
||||
),
|
||||
maxWidth: _currentMaxWidth,
|
||||
);
|
||||
_cacheDynamicHeightMap.put(text, size.height);
|
||||
return size.height;
|
||||
}
|
||||
|
||||
double _getItemHeight(Log log) {
|
||||
final measure = globalState.measure;
|
||||
final bodySmallHeight = measure.bodySmallHeight;
|
||||
final bodyMediumHeight = measure.bodyMediumHeight;
|
||||
final height = _calcCacheHeight(log.payload ?? "");
|
||||
final height = globalState.measure
|
||||
.computeTextSize(
|
||||
Text(
|
||||
log.payload ?? "",
|
||||
style: globalState.appController.context.textTheme.bodyLarge,
|
||||
),
|
||||
maxWidth: _currentMaxWidth,
|
||||
)
|
||||
.height;
|
||||
return height + bodySmallHeight + 8 + bodyMediumHeight + 40;
|
||||
}
|
||||
|
||||
@@ -196,7 +187,8 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
},
|
||||
child: CommonScrollBar(
|
||||
controller: _scrollController,
|
||||
child: ListView.builder(
|
||||
child: CacheItemExtentListView(
|
||||
key: _key,
|
||||
reverse: true,
|
||||
shrinkWrap: true,
|
||||
physics: NextClampingScrollPhysics(),
|
||||
@@ -204,7 +196,7 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
itemBuilder: (_, index) {
|
||||
return items[index];
|
||||
},
|
||||
itemExtentBuilder: (index, __) {
|
||||
itemExtentBuilder: (index) {
|
||||
final item = items[index];
|
||||
if (item.runtimeType == Divider) {
|
||||
return 0;
|
||||
@@ -213,6 +205,14 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
return _getItemHeight(log);
|
||||
},
|
||||
itemCount: items.length,
|
||||
keyBuilder: (int index) {
|
||||
final item = items[index];
|
||||
if (item.runtimeType == Divider) {
|
||||
return "divider";
|
||||
}
|
||||
final log = logs[(index / 2).floor()];
|
||||
return log.payload ?? "";
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -272,11 +272,3 @@ class LogItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NoGlowScrollBehavior extends ScrollBehavior {
|
||||
@override
|
||||
Widget buildOverscrollIndicator(
|
||||
BuildContext context, Widget child, ScrollableDetails details) {
|
||||
return child; // 禁用过度滚动效果
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,19 +50,19 @@ class AddProfile extends StatelessWidget {
|
||||
return ListView(
|
||||
children: [
|
||||
ListItem(
|
||||
leading: const Icon(Icons.qr_code),
|
||||
leading: const Icon(Icons.qr_code_sharp),
|
||||
title: Text(appLocalizations.qrcode),
|
||||
subtitle: Text(appLocalizations.qrcodeDesc),
|
||||
onTap: _toScan,
|
||||
),
|
||||
ListItem(
|
||||
leading: const Icon(Icons.upload_file),
|
||||
leading: const Icon(Icons.upload_file_sharp),
|
||||
title: Text(appLocalizations.file),
|
||||
subtitle: Text(appLocalizations.fileDesc),
|
||||
onTap: _handleAddProfileFormFile,
|
||||
),
|
||||
ListItem(
|
||||
leading: const Icon(Icons.cloud_download),
|
||||
leading: const Icon(Icons.cloud_download_sharp),
|
||||
title: Text(appLocalizations.url),
|
||||
subtitle: Text(appLocalizations.urlDesc),
|
||||
onTap: _toAdd,
|
||||
@@ -90,9 +90,15 @@ class _URLFormDialogState extends State<URLFormDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(appLocalizations.importFromURL),
|
||||
content: SizedBox(
|
||||
return CommonDialog(
|
||||
title: appLocalizations.importFromURL,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _handleAddProfileFormURL,
|
||||
child: Text(appLocalizations.submit),
|
||||
)
|
||||
],
|
||||
child: SizedBox(
|
||||
width: 300,
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
@@ -109,12 +115,6 @@ class _URLFormDialogState extends State<URLFormDialog> {
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _handleAddProfileFormURL,
|
||||
child: Text(appLocalizations.submit),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/widgets/scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomProfile extends StatefulWidget {
|
||||
final String profileId;
|
||||
|
||||
const CustomProfile({
|
||||
super.key,
|
||||
required this.profileId,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CustomProfile> createState() => _CustomProfileState();
|
||||
}
|
||||
|
||||
class _CustomProfileState extends State<CustomProfile> {
|
||||
final _currentClashConfigNotifier = ValueNotifier<ClashConfig?>(null);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initCurrentClashConfig();
|
||||
}
|
||||
|
||||
_initCurrentClashConfig() async {
|
||||
// final currentProfileId = globalState.config.currentProfileId;
|
||||
// if (currentProfileId == null) {
|
||||
// return;
|
||||
// }
|
||||
// _currentClashConfigNotifier.value =
|
||||
// await clashCore.getProfile(currentProfileId);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonScaffold(
|
||||
body: ValueListenableBuilder(
|
||||
valueListenable: _currentClashConfigNotifier,
|
||||
builder: (_, clashConfig, ___) {
|
||||
if (clashConfig == null) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
children: [],
|
||||
);
|
||||
},
|
||||
),
|
||||
title: "自定义",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -80,9 +80,9 @@ class _EditProfileState extends State<EditProfile> {
|
||||
);
|
||||
}
|
||||
}
|
||||
appController.setProfile(await profile.saveFile(fileData!));
|
||||
appController.setProfileAndAutoApply(await profile.saveFile(fileData!));
|
||||
} else if (!hasUpdate) {
|
||||
appController.setProfile(profile);
|
||||
appController.setProfileAndAutoApply(profile);
|
||||
} else {
|
||||
globalState.homeScaffoldKey.currentState?.loadingRun(
|
||||
() async {
|
||||
@@ -282,7 +282,7 @@ class _EditProfileState extends State<EditProfile> {
|
||||
ValueListenableBuilder<FileInfo?>(
|
||||
valueListenable: fileInfoNotifier,
|
||||
builder: (_, fileInfo, __) {
|
||||
return FadeBox(
|
||||
return FadeThroughBox(
|
||||
child: fileInfo == null
|
||||
? Container()
|
||||
: ListItem(
|
||||
@@ -324,15 +324,13 @@ class _EditProfileState extends State<EditProfile> {
|
||||
},
|
||||
),
|
||||
];
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (didPop, __) {
|
||||
if (didPop) return;
|
||||
return CommonPopScope(
|
||||
onPop: () {
|
||||
if (fileData == null) {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
_handleBack();
|
||||
return false;
|
||||
},
|
||||
child: FloatLayout(
|
||||
floatingWidget: FloatWrapper(
|
||||
|
||||
957
lib/fragments/profiles/override_profile.dart
Normal file
957
lib/fragments/profiles/override_profile.dart
Normal file
@@ -0,0 +1,957 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fl_clash/clash/core.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class OverrideProfile extends StatefulWidget {
|
||||
final String profileId;
|
||||
|
||||
const OverrideProfile({
|
||||
super.key,
|
||||
required this.profileId,
|
||||
});
|
||||
|
||||
@override
|
||||
State<OverrideProfile> createState() => _OverrideProfileState();
|
||||
}
|
||||
|
||||
class _OverrideProfileState extends State<OverrideProfile> {
|
||||
final GlobalKey<CacheItemExtentListViewState> _ruleListKey = GlobalKey();
|
||||
final _controller = ScrollController();
|
||||
double _currentMaxWidth = 0;
|
||||
|
||||
_initState(WidgetRef ref) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Future.delayed(Duration(milliseconds: 300), () async {
|
||||
final snippet = await clashCore.getProfile(widget.profileId);
|
||||
final overrideData = ref.read(
|
||||
getProfileOverrideDataProvider(widget.profileId),
|
||||
);
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
snippet: snippet,
|
||||
overrideData: overrideData,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_handleSave(WidgetRef ref, OverrideData overrideData) {
|
||||
ref.read(profilesProvider.notifier).updateProfile(
|
||||
widget.profileId,
|
||||
(state) => state.copyWith(
|
||||
overrideData: overrideData,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_handleDelete(WidgetRef ref) async {
|
||||
final res = await globalState.showMessage(
|
||||
title: appLocalizations.tip,
|
||||
message: TextSpan(text: appLocalizations.deleteRuleTip),
|
||||
);
|
||||
if (res != true) {
|
||||
return;
|
||||
}
|
||||
final selectedRules = ref.read(
|
||||
profileOverrideStateProvider.select(
|
||||
(state) => state.selectedRules,
|
||||
),
|
||||
);
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) {
|
||||
final overrideRule = state.overrideData!.rule.updateRules(
|
||||
(rules) => List.from(
|
||||
rules.where(
|
||||
(item) => !selectedRules.contains(item.id),
|
||||
),
|
||||
),
|
||||
);
|
||||
return state.copyWith.overrideData!(
|
||||
rule: overrideRule,
|
||||
);
|
||||
},
|
||||
);
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) => state.copyWith(isEdit: false, selectedRules: {}),
|
||||
);
|
||||
}
|
||||
|
||||
_handleTryClearCache(double maxWidth) {
|
||||
if (_currentMaxWidth != maxWidth) {
|
||||
_currentMaxWidth = maxWidth;
|
||||
_ruleListKey.currentState?.clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
_buildContent() {
|
||||
return Consumer(
|
||||
builder: (_, ref, child) {
|
||||
final isInit = ref.watch(
|
||||
profileOverrideStateProvider.select(
|
||||
(state) => state.snippet != null && state.overrideData != null,
|
||||
),
|
||||
);
|
||||
if (!isInit) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
return FadeBox(
|
||||
child: !isInit
|
||||
? Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: child!,
|
||||
);
|
||||
},
|
||||
child: LayoutBuilder(
|
||||
builder: (_, constraints) {
|
||||
_handleTryClearCache(constraints.maxWidth - 104);
|
||||
return CommonAutoHiddenScrollBar(
|
||||
controller: _controller,
|
||||
child: CustomScrollView(
|
||||
controller: _controller,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: OverrideSwitch(),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 8,
|
||||
right: 8,
|
||||
),
|
||||
child: RuleTitle(
|
||||
profileId: widget.profileId,
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 0),
|
||||
sliver: RuleContent(
|
||||
maxWidth: _currentMaxWidth,
|
||||
ruleListKey: _ruleListKey,
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
profileOverrideStateProvider.overrideWith(() => ProfileOverrideState()),
|
||||
],
|
||||
child: Consumer(
|
||||
builder: (_, ref, child) {
|
||||
_initState(ref);
|
||||
return child!;
|
||||
},
|
||||
child: Consumer(
|
||||
builder: (_, ref, ___) {
|
||||
final vm2 = ref.watch(
|
||||
profileOverrideStateProvider.select(
|
||||
(state) => VM2(
|
||||
a: state.isEdit,
|
||||
b: state.selectedRules.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
final isEdit = vm2.a;
|
||||
final editCount = vm2.b;
|
||||
return CommonScaffold(
|
||||
title: appLocalizations.override,
|
||||
body: _buildContent(),
|
||||
actions: [
|
||||
if (!isEdit)
|
||||
Consumer(
|
||||
builder: (_, ref, child) {
|
||||
final overrideData = ref.watch(
|
||||
getProfileOverrideDataProvider(widget.profileId));
|
||||
final newOverrideData = ref.watch(
|
||||
profileOverrideStateProvider.select(
|
||||
(state) => state.overrideData,
|
||||
),
|
||||
);
|
||||
final equals = overrideData == newOverrideData;
|
||||
if (equals || newOverrideData == null) {
|
||||
return SizedBox();
|
||||
}
|
||||
return CommonPopScope(
|
||||
onPop: () async {
|
||||
if (equals) {
|
||||
return true;
|
||||
}
|
||||
final res = await globalState.showMessage(
|
||||
message: TextSpan(
|
||||
text: appLocalizations.saveChanges,
|
||||
),
|
||||
confirmText: appLocalizations.save,
|
||||
);
|
||||
if (!context.mounted || res != true) {
|
||||
return true;
|
||||
}
|
||||
_handleSave(ref, newOverrideData);
|
||||
return true;
|
||||
},
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
final res = await globalState.showMessage(
|
||||
message: TextSpan(
|
||||
text: appLocalizations.saveTip,
|
||||
),
|
||||
confirmText: appLocalizations.tip,
|
||||
);
|
||||
if (res != true) {
|
||||
return;
|
||||
}
|
||||
_handleSave(ref, newOverrideData);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.save,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (editCount == 1)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
final rule = ref.read(profileOverrideStateProvider.select(
|
||||
(state) {
|
||||
return state.overrideData?.rule.rules.firstWhere(
|
||||
(item) => item.id == state.selectedRules.first,
|
||||
);
|
||||
},
|
||||
));
|
||||
if (rule == null) {
|
||||
return;
|
||||
}
|
||||
globalState.appController.handleAddOrUpdate(
|
||||
ref,
|
||||
rule,
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.edit,
|
||||
),
|
||||
),
|
||||
if (editCount > 0)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_handleDelete(ref);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.delete,
|
||||
),
|
||||
)
|
||||
],
|
||||
appBarEditState: AppBarEditState(
|
||||
isEdit: isEdit,
|
||||
editCount: editCount,
|
||||
onExit: () {
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
isEdit: false,
|
||||
selectedRules: {},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OverrideSwitch extends ConsumerWidget {
|
||||
const OverrideSwitch({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final enable = ref.watch(
|
||||
profileOverrideStateProvider.select(
|
||||
(state) => state.overrideData?.enable,
|
||||
),
|
||||
);
|
||||
return CommonCard(
|
||||
onPressed: () {},
|
||||
type: CommonCardType.filled,
|
||||
radius: 18,
|
||||
child: ListItem.switchItem(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
),
|
||||
title: Text(appLocalizations.enableOverride),
|
||||
delegate: SwitchDelegate(
|
||||
value: enable ?? false,
|
||||
onChanged: (value) {
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) => state.copyWith.overrideData!(
|
||||
enable: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RuleTitle extends ConsumerWidget {
|
||||
final String profileId;
|
||||
|
||||
const RuleTitle({
|
||||
super.key,
|
||||
required this.profileId,
|
||||
});
|
||||
|
||||
_handleChangeType(WidgetRef ref, isOverrideRule) {
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) => state.copyWith.overrideData!.rule(
|
||||
type: isOverrideRule
|
||||
? OverrideRuleType.added
|
||||
: OverrideRuleType.override,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final vm3 = ref.watch(
|
||||
profileOverrideStateProvider.select(
|
||||
(state) {
|
||||
final overrideRule = state.overrideData?.rule;
|
||||
return VM3(
|
||||
a: state.isEdit,
|
||||
b: state.selectedRules.containsAll(
|
||||
overrideRule?.rules.map((item) => item.id).toSet() ?? {},
|
||||
),
|
||||
c: overrideRule?.type == OverrideRuleType.override,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
final isEdit = vm3.a;
|
||||
final isSelectAll = vm3.b;
|
||||
final isOverrideRule = vm3.c;
|
||||
return FilledButtonTheme(
|
||||
data: FilledButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStatePropertyAll(EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
)),
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
),
|
||||
child: IconButtonTheme(
|
||||
data: IconButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStatePropertyAll(EdgeInsets.zero),
|
||||
visualDensity: VisualDensity.compact,
|
||||
iconSize: WidgetStatePropertyAll(20),
|
||||
),
|
||||
),
|
||||
child: ListHeader(
|
||||
title: appLocalizations.rule,
|
||||
subTitle: isOverrideRule
|
||||
? appLocalizations.overrideOriginRules
|
||||
: appLocalizations.addedOriginRules,
|
||||
space: 8,
|
||||
actions: [
|
||||
if (!isEdit)
|
||||
IconButton.filledTonal(
|
||||
icon: Icon(
|
||||
isOverrideRule ? Icons.edit_document : Icons.note_add,
|
||||
),
|
||||
onPressed: () {
|
||||
_handleChangeType(
|
||||
ref,
|
||||
isOverrideRule,
|
||||
);
|
||||
},
|
||||
),
|
||||
!isEdit
|
||||
? FilledButton.tonal(
|
||||
onPressed: () {
|
||||
globalState.appController.handleAddOrUpdate(ref);
|
||||
},
|
||||
child: Text(appLocalizations.add),
|
||||
)
|
||||
: isSelectAll
|
||||
? FilledButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(profileOverrideStateProvider.notifier)
|
||||
.updateState(
|
||||
(state) => state.copyWith(
|
||||
selectedRules: {},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(appLocalizations.selectAll),
|
||||
)
|
||||
: FilledButton.tonal(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(profileOverrideStateProvider.notifier)
|
||||
.updateState(
|
||||
(state) => state.copyWith(
|
||||
selectedRules: state.overrideData?.rule.rules
|
||||
.map((item) => item.id)
|
||||
.toSet() ??
|
||||
{},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(appLocalizations.selectAll),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RuleContent extends ConsumerWidget {
|
||||
final Key ruleListKey;
|
||||
final double maxWidth;
|
||||
|
||||
const RuleContent({
|
||||
super.key,
|
||||
required this.ruleListKey,
|
||||
required this.maxWidth,
|
||||
});
|
||||
|
||||
Widget _proxyDecorator(
|
||||
Widget child,
|
||||
int index,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (_, Widget? child) {
|
||||
final double animValue = Curves.easeInOut.transform(animation.value);
|
||||
final double scale = lerpDouble(1, 1.02, animValue)!;
|
||||
return Transform.scale(
|
||||
scale: scale,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItem(Rule rule, int index) {
|
||||
return Consumer(
|
||||
builder: (context, ref, ___) {
|
||||
final vm2 = ref.watch(profileOverrideStateProvider.select(
|
||||
(item) => VM2(
|
||||
a: item.isEdit,
|
||||
b: item.selectedRules.contains(rule.id),
|
||||
),
|
||||
));
|
||||
final isEdit = vm2.a;
|
||||
final isSelected = vm2.b;
|
||||
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? context.colorScheme.secondaryContainer.opacity80
|
||||
: context.colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: ListTile(
|
||||
minTileHeight: 0,
|
||||
minVerticalPadding: 0,
|
||||
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
trailing: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: !isEdit
|
||||
? ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: const Icon(Icons.drag_handle),
|
||||
)
|
||||
: CommonCheckBox(
|
||||
value: isSelected,
|
||||
isCircle: true,
|
||||
onChanged: (_) {
|
||||
_handleSelect(ref, rule);
|
||||
},
|
||||
),
|
||||
),
|
||||
title: Text(rule.value),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_handleSelect(WidgetRef ref, ruleId) {
|
||||
if (!ref.read(profileOverrideStateProvider).isEdit) {
|
||||
return;
|
||||
}
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) {
|
||||
final newSelectedRules = Set<String>.from(state.selectedRules);
|
||||
if (newSelectedRules.contains(ruleId)) {
|
||||
newSelectedRules.remove(ruleId);
|
||||
} else {
|
||||
newSelectedRules.add(ruleId);
|
||||
}
|
||||
return state.copyWith(
|
||||
selectedRules: newSelectedRules,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final vm2 = ref.watch(
|
||||
profileOverrideStateProvider.select(
|
||||
(state) {
|
||||
final overrideRule = state.overrideData?.rule;
|
||||
return VM2(
|
||||
a: overrideRule?.rules ?? [],
|
||||
b: overrideRule?.type ?? OverrideRuleType.added,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
final rules = vm2.a;
|
||||
final type = vm2.b;
|
||||
if (rules.isEmpty) {
|
||||
return SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 300,
|
||||
child: Center(
|
||||
child: type == OverrideRuleType.added
|
||||
? Text(
|
||||
appLocalizations.noData,
|
||||
)
|
||||
: FilledButton(
|
||||
onPressed: () {
|
||||
final rules = ref.read(
|
||||
profileOverrideStateProvider.select(
|
||||
(state) => state.snippet?.rule ?? [],
|
||||
),
|
||||
);
|
||||
ref
|
||||
.read(profileOverrideStateProvider.notifier)
|
||||
.updateState(
|
||||
(state) {
|
||||
return state.copyWith.overrideData!.rule(
|
||||
overrideRules: rules,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Text(appLocalizations.getOriginRules),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return CacheItemExtentSliverReorderableList(
|
||||
key: ruleListKey,
|
||||
itemBuilder: (context, index) {
|
||||
final rule = rules[index];
|
||||
return GestureDetector(
|
||||
key: ObjectKey(rule),
|
||||
child: _buildItem(
|
||||
rule,
|
||||
index,
|
||||
),
|
||||
onTap: () {
|
||||
_handleSelect(ref, rule.id);
|
||||
},
|
||||
onLongPress: () {
|
||||
if (ref.read(profileOverrideStateProvider).isEdit) {
|
||||
return;
|
||||
}
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
isEdit: true,
|
||||
selectedRules: {
|
||||
rule.id,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
proxyDecorator: _proxyDecorator,
|
||||
itemCount: rules.length,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final newRules = List<Rule>.from(rules);
|
||||
final item = newRules.removeAt(oldIndex);
|
||||
newRules.insert(newIndex, item);
|
||||
ref.read(profileOverrideStateProvider.notifier).updateState(
|
||||
(state) => state.copyWith.overrideData!(
|
||||
rule: state.overrideData!.rule.updateRules((_) => newRules),
|
||||
),
|
||||
);
|
||||
},
|
||||
keyBuilder: (int index) {
|
||||
return rules[index].value;
|
||||
},
|
||||
itemExtentBuilder: (index) {
|
||||
final rule = rules[index];
|
||||
return 40 +
|
||||
globalState.measure
|
||||
.computeTextSize(
|
||||
Text(
|
||||
rule.value,
|
||||
style: context.textTheme.bodyMedium?.toJetBrainsMono,
|
||||
),
|
||||
maxWidth: maxWidth,
|
||||
)
|
||||
.height;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddRuleDialog extends StatefulWidget {
|
||||
final ClashConfigSnippet snippet;
|
||||
final Rule? rule;
|
||||
|
||||
const AddRuleDialog({
|
||||
super.key,
|
||||
required this.snippet,
|
||||
this.rule,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AddRuleDialog> createState() => _AddRuleDialogState();
|
||||
}
|
||||
|
||||
class _AddRuleDialogState extends State<AddRuleDialog> {
|
||||
late RuleAction _ruleAction;
|
||||
final _ruleTargetController = TextEditingController();
|
||||
final _contentController = TextEditingController();
|
||||
final _ruleProviderController = TextEditingController();
|
||||
final _subRuleController = TextEditingController();
|
||||
bool _noResolve = false;
|
||||
bool _src = false;
|
||||
List<DropdownMenuEntry> _targetItems = [];
|
||||
List<DropdownMenuEntry> _ruleProviderItems = [];
|
||||
List<DropdownMenuEntry> _subRuleItems = [];
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_initState();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
_initState() {
|
||||
_targetItems = [
|
||||
...widget.snippet.proxyGroups.map(
|
||||
(item) => DropdownMenuEntry(
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
),
|
||||
),
|
||||
...RuleTarget.values.map(
|
||||
(item) => DropdownMenuEntry(
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
),
|
||||
),
|
||||
];
|
||||
_ruleProviderItems = [
|
||||
...widget.snippet.ruleProvider.map(
|
||||
(item) => DropdownMenuEntry(
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
),
|
||||
),
|
||||
];
|
||||
_subRuleItems = [
|
||||
...widget.snippet.subRules.map(
|
||||
(item) => DropdownMenuEntry(
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
),
|
||||
),
|
||||
];
|
||||
if (widget.rule != null) {
|
||||
final parsedRule = ParsedRule.parseString(widget.rule!.value);
|
||||
_ruleAction = parsedRule.ruleAction;
|
||||
_contentController.text = parsedRule.content ?? "";
|
||||
_ruleTargetController.text = parsedRule.ruleTarget ?? "";
|
||||
_ruleProviderController.text = parsedRule.ruleProvider ?? "";
|
||||
_subRuleController.text = parsedRule.subRule ?? "";
|
||||
_noResolve = parsedRule.noResolve;
|
||||
_src = parsedRule.src;
|
||||
return;
|
||||
}
|
||||
_ruleAction = RuleAction.values.first;
|
||||
if (_targetItems.isNotEmpty) {
|
||||
_ruleTargetController.text = _targetItems.first.value;
|
||||
}
|
||||
if (_ruleProviderItems.isNotEmpty) {
|
||||
_ruleProviderController.text = _ruleProviderItems.first.value;
|
||||
}
|
||||
if (_subRuleItems.isNotEmpty) {
|
||||
_subRuleController.text = _subRuleItems.first.value;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AddRuleDialog oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.rule != widget.rule) {
|
||||
_initState();
|
||||
}
|
||||
}
|
||||
|
||||
_handleSubmit() {
|
||||
final res = _formKey.currentState?.validate();
|
||||
if (res == false) {
|
||||
return;
|
||||
}
|
||||
final parsedRule = ParsedRule(
|
||||
ruleAction: _ruleAction,
|
||||
content: _contentController.text,
|
||||
ruleProvider: _ruleProviderController.text,
|
||||
ruleTarget: _ruleTargetController.text,
|
||||
subRule: _subRuleController.text,
|
||||
noResolve: _noResolve,
|
||||
src: _src,
|
||||
);
|
||||
final rule = widget.rule != null
|
||||
? widget.rule!.copyWith(value: parsedRule.value)
|
||||
: Rule.value(
|
||||
parsedRule.value,
|
||||
);
|
||||
Navigator.of(context).pop(rule);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonDialog(
|
||||
title: appLocalizations.addRule,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _handleSubmit,
|
||||
child: Text(
|
||||
appLocalizations.confirm,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: DropdownMenuTheme(
|
||||
data: DropdownMenuThemeData(
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(),
|
||||
labelStyle: context.textTheme.bodyLarge
|
||||
?.copyWith(overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: LayoutBuilder(
|
||||
builder: (_, constraints) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FilledButton.tonal(
|
||||
onPressed: () async {
|
||||
_ruleAction =
|
||||
await globalState.showCommonDialog<RuleAction>(
|
||||
child: OptionsDialog<RuleAction>(
|
||||
title: appLocalizations.ruleName,
|
||||
options: RuleAction.values,
|
||||
textBuilder: (item) => item.value,
|
||||
value: _ruleAction,
|
||||
),
|
||||
) ??
|
||||
_ruleAction;
|
||||
setState(() {});
|
||||
},
|
||||
child: Text(_ruleAction.name),
|
||||
),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
_ruleAction == RuleAction.RULE_SET
|
||||
? FormField(
|
||||
validator: (_) {
|
||||
if (_ruleProviderController.text.isEmpty) {
|
||||
return appLocalizations.ruleProviderEmptyTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
builder: (field) {
|
||||
return DropdownMenu(
|
||||
expandedInsets: EdgeInsets.zero,
|
||||
controller: _ruleProviderController,
|
||||
label: Text(appLocalizations.ruleProviders),
|
||||
menuHeight: 250,
|
||||
errorText: field.errorText,
|
||||
dropdownMenuEntries: _ruleProviderItems,
|
||||
);
|
||||
},
|
||||
)
|
||||
: TextFormField(
|
||||
controller: _contentController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: appLocalizations.content,
|
||||
),
|
||||
validator: (_) {
|
||||
if (_contentController.text.isEmpty) {
|
||||
return appLocalizations.contentEmptyTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
_ruleAction == RuleAction.SUB_RULE
|
||||
? FormField(
|
||||
validator: (_) {
|
||||
if (_subRuleController.text.isEmpty) {
|
||||
return appLocalizations.subRuleEmptyTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
builder: (filed) {
|
||||
return DropdownMenu(
|
||||
width: 200,
|
||||
controller: _subRuleController,
|
||||
label: Text(appLocalizations.subRule),
|
||||
menuHeight: 250,
|
||||
dropdownMenuEntries: _subRuleItems,
|
||||
);
|
||||
},
|
||||
)
|
||||
: FormField<String>(
|
||||
validator: (_) {
|
||||
if (_ruleTargetController.text.isEmpty) {
|
||||
return appLocalizations.ruleTargetEmptyTip;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
builder: (filed) {
|
||||
return DropdownMenu(
|
||||
controller: _ruleTargetController,
|
||||
initialSelection: filed.value,
|
||||
label: Text(appLocalizations.ruleTarget),
|
||||
width: 200,
|
||||
menuHeight: 250,
|
||||
enableFilter: true,
|
||||
dropdownMenuEntries: _targetItems,
|
||||
errorText: filed.errorText,
|
||||
);
|
||||
},
|
||||
),
|
||||
if (_ruleAction.hasParams) ...[
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
CommonCard(
|
||||
radius: 8,
|
||||
isSelected: _src,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 8),
|
||||
child: Text(
|
||||
appLocalizations.sourceIp,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_src = !_src;
|
||||
});
|
||||
},
|
||||
),
|
||||
CommonCard(
|
||||
radius: 8,
|
||||
isSelected: _noResolve,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 8),
|
||||
child: Text(
|
||||
appLocalizations.noResolve,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_noResolve = !_noResolve;
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'dart:ui';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/profiles/edit_profile.dart';
|
||||
import 'package:fl_clash/fragments/profiles/override_profile.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
@@ -23,12 +24,17 @@ class _ProfilesFragmentState extends State<ProfilesFragment> with PageMixin {
|
||||
Function? applyConfigDebounce;
|
||||
|
||||
_handleShowAddExtendPage() {
|
||||
showExtendPage(
|
||||
showExtend(
|
||||
globalState.navigatorKey.currentState!.context,
|
||||
body: AddProfile(
|
||||
context: globalState.navigatorKey.currentState!.context,
|
||||
),
|
||||
title: "${appLocalizations.add}${appLocalizations.profile}",
|
||||
builder: (_, type) {
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: AddProfile(
|
||||
context: globalState.navigatorKey.currentState!.context,
|
||||
),
|
||||
title: "${appLocalizations.add}${appLocalizations.profile}",
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -80,12 +86,13 @@ class _ProfilesFragmentState extends State<ProfilesFragment> with PageMixin {
|
||||
onPressed: () {
|
||||
final profiles = globalState.config.profiles;
|
||||
showSheet(
|
||||
title: appLocalizations.profilesSort,
|
||||
context: context,
|
||||
body: SizedBox(
|
||||
height: 400,
|
||||
child: ReorderableProfiles(profiles: profiles),
|
||||
),
|
||||
builder: (_, type) {
|
||||
return ReorderableProfilesSheet(
|
||||
type: type,
|
||||
profiles: profiles,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.sort),
|
||||
@@ -181,10 +188,6 @@ class ProfileItem extends StatelessWidget {
|
||||
await globalState.appController.deleteProfile(profile.id);
|
||||
}
|
||||
|
||||
_handleUpdateProfile() async {
|
||||
await globalState.safeRun<void>(updateProfile);
|
||||
}
|
||||
|
||||
Future updateProfile() async {
|
||||
final appController = globalState.appController;
|
||||
if (profile.type == ProfileType.file) return;
|
||||
@@ -208,13 +211,18 @@ class ProfileItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
_handleShowEditExtendPage(BuildContext context) {
|
||||
showExtendPage(
|
||||
showExtend(
|
||||
context,
|
||||
body: EditProfile(
|
||||
profile: profile,
|
||||
context: context,
|
||||
),
|
||||
title: "${appLocalizations.edit}${appLocalizations.profile}",
|
||||
builder: (_, type) {
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: EditProfile(
|
||||
profile: profile,
|
||||
context: context,
|
||||
),
|
||||
title: "${appLocalizations.edit}${appLocalizations.profile}",
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -277,18 +285,17 @@ class ProfileItem extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// _handlePushCustomPage(BuildContext context, String id) {
|
||||
// BaseNavigator.push(
|
||||
// context,
|
||||
// CustomProfile(
|
||||
// profileId: id,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
_handlePushGenProfilePage(BuildContext context, String id) {
|
||||
BaseNavigator.push(
|
||||
context,
|
||||
OverrideProfile(
|
||||
profileId: id,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final key = GlobalKey<CommonPopupBoxState>();
|
||||
return CommonCard(
|
||||
isSelected: profile.id == groupValue,
|
||||
onPressed: () {
|
||||
@@ -301,17 +308,16 @@ class ProfileItem extends StatelessWidget {
|
||||
trailing: SizedBox(
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: FadeBox(
|
||||
child: FadeThroughBox(
|
||||
child: profile.isUpdating
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: CommonPopupBox(
|
||||
key: key,
|
||||
popup: CommonPopupMenu(
|
||||
items: [
|
||||
ActionItemData(
|
||||
PopupMenuItemData(
|
||||
icon: Icons.edit_outlined,
|
||||
label: appLocalizations.edit,
|
||||
onPressed: () {
|
||||
@@ -319,52 +325,47 @@ class ProfileItem extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
if (profile.type == ProfileType.url) ...[
|
||||
ActionItemData(
|
||||
PopupMenuItemData(
|
||||
icon: Icons.sync_alt_sharp,
|
||||
label: appLocalizations.sync,
|
||||
onPressed: () {
|
||||
_handleUpdateProfile();
|
||||
updateProfile();
|
||||
},
|
||||
),
|
||||
// ActionItemData(
|
||||
// icon: Icons.copy,
|
||||
// label: appLocalizations.copyLink,
|
||||
// onPressed: () {
|
||||
// _handleCopyLink(context);
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
// ActionItemData(
|
||||
// icon: Icons.extension_outlined,
|
||||
// label: "自定义",
|
||||
// onPressed: () {
|
||||
// _handlePushCustomPage(context, profile.id);
|
||||
// },
|
||||
// ),
|
||||
ActionItemData(
|
||||
PopupMenuItemData(
|
||||
icon: Icons.extension_outlined,
|
||||
label: appLocalizations.override,
|
||||
onPressed: () {
|
||||
_handlePushGenProfilePage(context, profile.id);
|
||||
},
|
||||
),
|
||||
PopupMenuItemData(
|
||||
icon: Icons.file_copy_outlined,
|
||||
label: appLocalizations.exportFile,
|
||||
onPressed: () {
|
||||
_handleExportFile(context);
|
||||
},
|
||||
),
|
||||
ActionItemData(
|
||||
PopupMenuItemData(
|
||||
icon: Icons.delete_outlined,
|
||||
iconSize: 20,
|
||||
label: appLocalizations.delete,
|
||||
onPressed: () {
|
||||
_handleDeleteProfile(context);
|
||||
},
|
||||
type: ActionType.danger,
|
||||
type: PopupMenuItemType.danger,
|
||||
),
|
||||
],
|
||||
),
|
||||
target: IconButton(
|
||||
onPressed: () {
|
||||
key.currentState?.pop();
|
||||
},
|
||||
icon: Icon(Icons.more_vert),
|
||||
),
|
||||
targetBuilder: (open) {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
open();
|
||||
},
|
||||
icon: Icon(Icons.more_vert),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -400,19 +401,22 @@ class ProfileItem extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ReorderableProfiles extends StatefulWidget {
|
||||
class ReorderableProfilesSheet extends StatefulWidget {
|
||||
final List<Profile> profiles;
|
||||
final SheetType type;
|
||||
|
||||
const ReorderableProfiles({
|
||||
const ReorderableProfilesSheet({
|
||||
super.key,
|
||||
required this.profiles,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ReorderableProfiles> createState() => _ReorderableProfilesState();
|
||||
State<ReorderableProfilesSheet> createState() =>
|
||||
_ReorderableProfilesSheetState();
|
||||
}
|
||||
|
||||
class _ReorderableProfilesState extends State<ReorderableProfiles> {
|
||||
class _ReorderableProfilesSheetState extends State<ReorderableProfilesSheet> {
|
||||
late List<Profile> profiles;
|
||||
|
||||
@override
|
||||
@@ -456,74 +460,61 @@ class _ReorderableProfilesState extends State<ReorderableProfiles> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: ReorderableListView.builder(
|
||||
buildDefaultDragHandles: false,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
proxyDecorator: proxyDecorator,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
setState(() {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final profile = profiles.removeAt(oldIndex);
|
||||
profiles.insert(newIndex, profile);
|
||||
});
|
||||
},
|
||||
itemBuilder: (_, index) {
|
||||
final profile = profiles[index];
|
||||
return Container(
|
||||
key: Key(profile.id),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: CommonCard(
|
||||
type: CommonCardType.filled,
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
right: 16,
|
||||
left: 16,
|
||||
),
|
||||
title: Text(profile.label ?? profile.id),
|
||||
trailing: ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: const Icon(Icons.drag_handle),
|
||||
),
|
||||
return AdaptiveSheetScaffold(
|
||||
type: widget.type,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
globalState.appController.setProfiles(profiles);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.save,
|
||||
),
|
||||
)
|
||||
],
|
||||
body: Padding(
|
||||
padding: EdgeInsets.only(bottom: 32),
|
||||
child: ReorderableListView.builder(
|
||||
buildDefaultDragHandles: false,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
proxyDecorator: proxyDecorator,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
setState(() {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final profile = profiles.removeAt(oldIndex);
|
||||
profiles.insert(newIndex, profile);
|
||||
});
|
||||
},
|
||||
itemBuilder: (_, index) {
|
||||
final profile = profiles[index];
|
||||
return Container(
|
||||
key: Key(profile.id),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: CommonCard(
|
||||
type: CommonCardType.filled,
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
right: 16,
|
||||
left: 16,
|
||||
),
|
||||
title: Text(profile.label ?? profile.id),
|
||||
trailing: ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: const Icon(Icons.drag_handle),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: profiles.length,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
horizontal: 24,
|
||||
),
|
||||
child: FilledButton.tonal(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
globalState.appController.setProfiles(profiles);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(
|
||||
const EdgeInsets.symmetric(vertical: 8),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
appLocalizations.confirm,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: profiles.length,
|
||||
),
|
||||
],
|
||||
),
|
||||
title: appLocalizations.profilesSort,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ class ProxyCard extends StatelessWidget {
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color:
|
||||
context.textTheme.bodySmall?.color?.toLight,
|
||||
context.textTheme.bodySmall?.color?.opacity80,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -221,7 +221,7 @@ class _ProxyDesc extends ConsumerWidget {
|
||||
desc,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: context.textTheme.bodySmall?.color?.toLight,
|
||||
color: context.textTheme.bodySmall?.color?.opacity80,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,44 +22,52 @@ double getItemHeight(ProxyCardType proxyCardType) {
|
||||
|
||||
proxyDelayTest(Proxy proxy, [String? testUrl]) async {
|
||||
final appController = globalState.appController;
|
||||
final proxyName = appController.getRealProxyName(proxy.name);
|
||||
final url = appController.getRealTestUrl(testUrl);
|
||||
final state = appController.getProxyCardState(proxy.name);
|
||||
final url = state.testUrl.getSafeValue(
|
||||
appController.getRealTestUrl(testUrl),
|
||||
);
|
||||
if (state.proxyName.isEmpty) {
|
||||
return;
|
||||
}
|
||||
appController.setDelay(
|
||||
Delay(
|
||||
url: url,
|
||||
name: proxyName,
|
||||
name: state.proxyName,
|
||||
value: 0,
|
||||
),
|
||||
);
|
||||
appController.setDelay(
|
||||
await clashCore.getDelay(
|
||||
url,
|
||||
proxyName,
|
||||
state.proxyName,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
delayTest(List<Proxy> proxies, [String? testUrl]) async {
|
||||
final appController = globalState.appController;
|
||||
final proxyNames = proxies
|
||||
.map((proxy) => appController.getRealProxyName(proxy.name))
|
||||
.toSet()
|
||||
.toList();
|
||||
|
||||
final url = appController.getRealTestUrl(testUrl);
|
||||
final proxyNames = proxies.map((proxy) => proxy.name).toSet().toList();
|
||||
|
||||
final delayProxies = proxyNames.map<Future>((proxyName) async {
|
||||
final state = appController.getProxyCardState(proxyName);
|
||||
final url = state.testUrl.getSafeValue(
|
||||
appController.getRealTestUrl(testUrl),
|
||||
);
|
||||
final name = state.proxyName;
|
||||
if (name.isEmpty) {
|
||||
return;
|
||||
}
|
||||
appController.setDelay(
|
||||
Delay(
|
||||
url: url,
|
||||
name: proxyName,
|
||||
name: name,
|
||||
value: 0,
|
||||
),
|
||||
);
|
||||
appController.setDelay(
|
||||
await clashCore.getDelay(
|
||||
url,
|
||||
proxyName,
|
||||
name,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
@@ -467,10 +467,6 @@ class _ListHeaderState extends State<ListHeader>
|
||||
return CommonCard(
|
||||
enterAnimated: widget.enterAnimated,
|
||||
key: widget.key,
|
||||
borderSide: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
context.colorScheme.surfaceContainer,
|
||||
),
|
||||
radius: 14,
|
||||
type: CommonCardType.filled,
|
||||
child: Padding(
|
||||
@@ -556,6 +552,7 @@ class _ListHeaderState extends State<ListHeader>
|
||||
children: [
|
||||
if (isExpand) ...[
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.standard,
|
||||
onPressed: () {
|
||||
widget.onScrollToSelected(groupName);
|
||||
},
|
||||
@@ -565,12 +562,13 @@ class _ListHeaderState extends State<ListHeader>
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _delayTest,
|
||||
visualDensity: VisualDensity.standard,
|
||||
icon: const Icon(
|
||||
Icons.network_ping,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
width: 6,
|
||||
),
|
||||
],
|
||||
AnimatedBuilder(
|
||||
|
||||
@@ -13,8 +13,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
typedef UpdatingMap = Map<String, bool>;
|
||||
|
||||
class ProvidersView extends ConsumerStatefulWidget {
|
||||
final SheetType type;
|
||||
|
||||
const ProvidersView({
|
||||
super.key,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -22,25 +25,6 @@ class ProvidersView extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _ProvidersViewState extends ConsumerState<ProvidersView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) {
|
||||
globalState.appController.updateProviders();
|
||||
context.commonScaffoldState?.actions = [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_updateProviders();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.sync,
|
||||
),
|
||||
)
|
||||
];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_updateProviders() async {
|
||||
final providers = ref.read(providersProvider);
|
||||
@@ -102,10 +86,24 @@ class _ProvidersViewState extends ConsumerState<ProvidersView> {
|
||||
title: appLocalizations.ruleProviders,
|
||||
items: ruleProviders,
|
||||
);
|
||||
return generateListView([
|
||||
...proxySection,
|
||||
...ruleSection,
|
||||
]);
|
||||
return AdaptiveSheetScaffold(
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_updateProviders();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.sync,
|
||||
),
|
||||
)
|
||||
],
|
||||
type: widget.type,
|
||||
body: generateListView([
|
||||
...proxySection,
|
||||
...ruleSection,
|
||||
]),
|
||||
title: appLocalizations.providers,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +220,7 @@ class ProviderItem extends StatelessWidget {
|
||||
trailing: SizedBox(
|
||||
height: 48,
|
||||
width: 48,
|
||||
child: FadeBox(
|
||||
child: FadeThroughBox(
|
||||
child: provider.isUpdating
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
|
||||
@@ -28,12 +28,13 @@ class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
|
||||
if (_hasProviders)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showExtendPage(
|
||||
isScaffold: true,
|
||||
extendPageWidth: 360,
|
||||
showExtend(
|
||||
context,
|
||||
body: const ProvidersView(),
|
||||
title: appLocalizations.providers,
|
||||
builder: (_, type) {
|
||||
return ProvidersView(
|
||||
type: type,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
@@ -51,11 +52,15 @@ class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
|
||||
)
|
||||
: IconButton(
|
||||
onPressed: () {
|
||||
showExtendPage(
|
||||
showExtend(
|
||||
context,
|
||||
extendPageWidth: 360,
|
||||
title: appLocalizations.iconConfiguration,
|
||||
body: _IconConfigView(),
|
||||
builder: (_, type) {
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: const _IconConfigView(),
|
||||
title: appLocalizations.iconConfiguration,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
@@ -65,9 +70,17 @@ class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showSheet(
|
||||
title: appLocalizations.proxiesSetting,
|
||||
context: context,
|
||||
body: const ProxiesSetting(),
|
||||
props: SheetProps(
|
||||
isScrollControlled: true,
|
||||
),
|
||||
builder: (_, type) {
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: const ProxiesSetting(),
|
||||
title: appLocalizations.proxiesSetting,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
@@ -128,13 +141,11 @@ class _IconConfigView extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final iconMap =
|
||||
ref.watch(proxiesStyleSettingProvider.select((state) => state.iconMap));
|
||||
final entries = iconMap.entries.toList();
|
||||
return ListPage(
|
||||
return MapInputPage(
|
||||
title: appLocalizations.iconConfiguration,
|
||||
items: entries,
|
||||
map: iconMap,
|
||||
keyLabel: appLocalizations.regExp,
|
||||
valueLabel: appLocalizations.icon,
|
||||
keyBuilder: (item) => Key(item.key),
|
||||
titleBuilder: (item) => Text(item.key),
|
||||
leadingBuilder: (item) => Container(
|
||||
decoration: BoxDecoration(
|
||||
@@ -151,10 +162,10 @@ class _IconConfigView extends ConsumerWidget {
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
onChange: (entries) {
|
||||
onChange: (value) {
|
||||
ref.read(proxiesStyleSettingProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
iconMap: Map.fromEntries(entries),
|
||||
iconMap: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -248,8 +248,8 @@ class ProxiesSetting extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 32),
|
||||
return SingleChildScrollView(
|
||||
padding: EdgeInsets.only(bottom: 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -268,6 +268,7 @@ class ProxiesSetting extends StatelessWidget {
|
||||
return Container();
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
..._buildGroupStyleSetting(),
|
||||
|
||||
@@ -49,7 +49,7 @@ class ProxiesTabFragmentState extends ConsumerState<ProxiesTabFragment>
|
||||
_buildMoreButton() {
|
||||
return Consumer(
|
||||
builder: (_, ref, ___) {
|
||||
final isMobileView = ref.watch(viewWidthProvider.notifier).isMobileView;
|
||||
final isMobileView = ref.watch(isMobileViewProvider);
|
||||
return IconButton(
|
||||
onPressed: _showMoreMenu,
|
||||
icon: isMobileView
|
||||
@@ -67,48 +67,54 @@ class ProxiesTabFragmentState extends ConsumerState<ProxiesTabFragment>
|
||||
_showMoreMenu() {
|
||||
showSheet(
|
||||
context: context,
|
||||
width: 380,
|
||||
isScrollControlled: false,
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
final state = ref.watch(proxiesSelectorStateProvider);
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
children: [
|
||||
for (final groupName in state.groupNames)
|
||||
SettingTextCard(
|
||||
groupName,
|
||||
onPressed: () {
|
||||
final index = state.groupNames.indexWhere(
|
||||
(item) => item == groupName,
|
||||
);
|
||||
if (index == -1) return;
|
||||
_tabController?.animateTo(index);
|
||||
globalState.appController
|
||||
.updateCurrentGroupName(groupName);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
isSelected: groupName == state.currentGroupName,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
props: SheetProps(
|
||||
isScrollControlled: false,
|
||||
),
|
||||
title: appLocalizations.proxyGroup,
|
||||
builder: (_, type) {
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
final state = ref.watch(proxiesSelectorStateProvider);
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
children: [
|
||||
for (final groupName in state.groupNames)
|
||||
SettingTextCard(
|
||||
groupName,
|
||||
onPressed: () {
|
||||
final index = state.groupNames.indexWhere(
|
||||
(item) => item == groupName,
|
||||
);
|
||||
if (index == -1) return;
|
||||
_tabController?.animateTo(index);
|
||||
globalState.appController
|
||||
.updateCurrentGroupName(groupName);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
isSelected: groupName == state.currentGroupName,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
title: appLocalizations.proxyGroup,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_tabControllerListener([int? index]) {
|
||||
int? groupIndex = index;
|
||||
if(groupIndex == -1){
|
||||
if (groupIndex == -1) {
|
||||
return;
|
||||
}
|
||||
final appController = globalState.appController;
|
||||
@@ -238,7 +244,7 @@ class ProxiesTabFragmentState extends ConsumerState<ProxiesTabFragment>
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: [
|
||||
context.colorScheme.surface.withOpacity(0.1),
|
||||
context.colorScheme.surface.opacity10,
|
||||
context.colorScheme.surface,
|
||||
],
|
||||
stops: const [
|
||||
@@ -319,32 +325,35 @@ class ProxyGroupViewState extends ConsumerState<ProxyGroupView> {
|
||||
);
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: GridView.builder(
|
||||
child: CommonAutoHiddenScrollBar(
|
||||
controller: _controller,
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 96,
|
||||
child: GridView.builder(
|
||||
controller: _controller,
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 96,
|
||||
),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: columns,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisExtent: getItemHeight(proxyCardType),
|
||||
),
|
||||
itemCount: sortedProxies.length,
|
||||
itemBuilder: (_, index) {
|
||||
final proxy = sortedProxies[index];
|
||||
return ProxyCard(
|
||||
testUrl: state.testUrl,
|
||||
groupType: state.groupType,
|
||||
type: proxyCardType,
|
||||
key: ValueKey('$groupName.${proxy.name}'),
|
||||
proxy: proxy,
|
||||
groupName: groupName,
|
||||
);
|
||||
},
|
||||
),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: columns,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisExtent: getItemHeight(proxyCardType),
|
||||
),
|
||||
itemCount: sortedProxies.length,
|
||||
itemBuilder: (_, index) {
|
||||
final proxy = sortedProxies[index];
|
||||
return ProxyCard(
|
||||
testUrl: state.testUrl,
|
||||
groupType: state.groupType,
|
||||
type: proxyCardType,
|
||||
key: ValueKey('$groupName.${proxy.name}'),
|
||||
proxy: proxy,
|
||||
groupName: groupName,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
||||
builder: (_, snapshot) {
|
||||
return SizedBox(
|
||||
height: 24,
|
||||
child: FadeBox(
|
||||
child: FadeThroughBox(
|
||||
key: Key("fade_box_${geoItem.label}"),
|
||||
child: snapshot.data == null
|
||||
? const SizedBox(
|
||||
@@ -248,7 +248,7 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: isUpdating,
|
||||
builder: (_, isUpdating, ___) {
|
||||
return FadeBox(
|
||||
return FadeThroughBox(
|
||||
child: isUpdating
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
@@ -299,24 +299,8 @@ class _UpdateGeoUrlFormDialogState extends State<UpdateGeoUrlFormDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(widget.title),
|
||||
content: SizedBox(
|
||||
width: 300,
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
TextField(
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
controller: urlController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
return CommonDialog(
|
||||
title: widget.title,
|
||||
actions: [
|
||||
if (widget.defaultValue != null &&
|
||||
urlController.value.text != widget.defaultValue) ...[
|
||||
@@ -333,6 +317,19 @@ class _UpdateGeoUrlFormDialogState extends State<UpdateGeoUrlFormDialog> {
|
||||
child: Text(appLocalizations.submit),
|
||||
)
|
||||
],
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
TextField(
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
controller: urlController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ class _ThemeColorsBoxState extends ConsumerState<ThemeColorsBox> {
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
_FontFamilyItem(),
|
||||
// _FontFamilyItem(),
|
||||
_ThemeModeItem(),
|
||||
_PrimaryColorItem(),
|
||||
_PrueBlackItem(),
|
||||
@@ -110,74 +110,74 @@ class _ThemeColorsBoxState extends ConsumerState<ThemeColorsBox> {
|
||||
}
|
||||
}
|
||||
|
||||
class _FontFamilyItem extends ConsumerWidget {
|
||||
const _FontFamilyItem();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final fontFamily =
|
||||
ref.watch(themeSettingProvider.select((state) => state.fontFamily));
|
||||
List<FontFamilyItem> fontFamilyItems = [
|
||||
FontFamilyItem(
|
||||
label: appLocalizations.systemFont,
|
||||
fontFamily: FontFamily.system,
|
||||
),
|
||||
const FontFamilyItem(
|
||||
label: "MiSans",
|
||||
fontFamily: FontFamily.miSans,
|
||||
),
|
||||
];
|
||||
return ItemCard(
|
||||
info: Info(
|
||||
label: appLocalizations.fontFamily,
|
||||
iconData: Icons.text_fields,
|
||||
),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
),
|
||||
height: 48,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (_, index) {
|
||||
final fontFamilyItem = fontFamilyItems[index];
|
||||
return CommonCard(
|
||||
isSelected: fontFamilyItem.fontFamily == fontFamily,
|
||||
onPressed: () {
|
||||
ref.read(themeSettingProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
fontFamily: fontFamilyItem.fontFamily,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
fontFamilyItem.label,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) {
|
||||
return const SizedBox(
|
||||
width: 16,
|
||||
);
|
||||
},
|
||||
itemCount: fontFamilyItems.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// class _FontFamilyItem extends ConsumerWidget {
|
||||
// const _FontFamilyItem();
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context, WidgetRef ref) {
|
||||
// final fontFamily =
|
||||
// ref.watch(themeSettingProvider.select((state) => state.fontFamily));
|
||||
// List<FontFamilyItem> fontFamilyItems = [
|
||||
// FontFamilyItem(
|
||||
// label: appLocalizations.systemFont,
|
||||
// fontFamily: FontFamily.system,
|
||||
// ),
|
||||
// const FontFamilyItem(
|
||||
// label: "roboto",
|
||||
// fontFamily: FontFamily.roboto,
|
||||
// ),
|
||||
// ];
|
||||
// return ItemCard(
|
||||
// info: Info(
|
||||
// label: appLocalizations.fontFamily,
|
||||
// iconData: Icons.text_fields,
|
||||
// ),
|
||||
// child: Container(
|
||||
// margin: const EdgeInsets.only(
|
||||
// left: 16,
|
||||
// right: 16,
|
||||
// ),
|
||||
// height: 48,
|
||||
// child: ListView.separated(
|
||||
// scrollDirection: Axis.horizontal,
|
||||
// itemBuilder: (_, index) {
|
||||
// final fontFamilyItem = fontFamilyItems[index];
|
||||
// return CommonCard(
|
||||
// isSelected: fontFamilyItem.fontFamily == fontFamily,
|
||||
// onPressed: () {
|
||||
// ref.read(themeSettingProvider.notifier).updateState(
|
||||
// (state) => state.copyWith(
|
||||
// fontFamily: fontFamilyItem.fontFamily,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
// child: Row(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// mainAxisAlignment: MainAxisAlignment.start,
|
||||
// children: [
|
||||
// Flexible(
|
||||
// child: Text(
|
||||
// fontFamilyItem.label,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// separatorBuilder: (_, __) {
|
||||
// return const SizedBox(
|
||||
// width: 16,
|
||||
// );
|
||||
// },
|
||||
// itemCount: fontFamilyItems.length,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
class _ThemeModeItem extends ConsumerWidget {
|
||||
const _ThemeModeItem();
|
||||
@@ -291,12 +291,12 @@ class _PrimaryColorItem extends ConsumerWidget {
|
||||
itemBuilder: (_, index) {
|
||||
final color = primaryColors[index];
|
||||
return ColorSchemeBox(
|
||||
isSelected: color?.value == primaryColor,
|
||||
isSelected: color?.toARGB32() == primaryColor,
|
||||
primaryColor: color,
|
||||
onPressed: () {
|
||||
ref.read(themeSettingProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
primaryColor: color?.value,
|
||||
primaryColor: color?.toARGB32(),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -320,7 +320,7 @@ class _PrueBlackItem extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final prueBlack =
|
||||
ref.watch(themeSettingProvider.select((state) => state.prueBlack));
|
||||
ref.watch(themeSettingProvider.select((state) => state.pureBlack));
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: ListItem.switchItem(
|
||||
@@ -328,13 +328,13 @@ class _PrueBlackItem extends ConsumerWidget {
|
||||
Icons.contrast,
|
||||
color: context.colorScheme.primary,
|
||||
),
|
||||
title: Text(appLocalizations.prueBlackMode),
|
||||
title: Text(appLocalizations.pureBlackMode),
|
||||
delegate: SwitchDelegate(
|
||||
value: prueBlack,
|
||||
onChanged: (value) {
|
||||
ref.read(themeSettingProvider.notifier).updateState(
|
||||
(state) => state.copyWith(
|
||||
prueBlack: value,
|
||||
pureBlack: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -35,7 +35,6 @@ class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
|
||||
delegate: OpenDelegate(
|
||||
title: Intl.message(navigationItem.label.name),
|
||||
widget: navigationItem.fragment,
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -65,7 +64,7 @@ class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _getSettingList() {
|
||||
_getSettingList() {
|
||||
return generateSection(
|
||||
title: appLocalizations.settings,
|
||||
items: [
|
||||
@@ -75,7 +74,7 @@ class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
|
||||
if (system.isDesktop) _HotkeyItem(),
|
||||
if (Platform.isWindows) _LoopbackItem(),
|
||||
if (Platform.isAndroid) _AccessItem(),
|
||||
_OverrideItem(),
|
||||
_ConfigItem(),
|
||||
_SettingItem(),
|
||||
],
|
||||
);
|
||||
@@ -155,7 +154,6 @@ class _ThemeItem extends StatelessWidget {
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.theme,
|
||||
widget: const ThemeFragment(),
|
||||
extendPageWidth: 360,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -231,15 +229,15 @@ class _AccessItem extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _OverrideItem extends StatelessWidget {
|
||||
const _OverrideItem();
|
||||
class _ConfigItem extends StatelessWidget {
|
||||
const _ConfigItem();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem.open(
|
||||
leading: const Icon(Icons.edit),
|
||||
title: Text(appLocalizations.override),
|
||||
subtitle: Text(appLocalizations.overrideDesc),
|
||||
title: Text(appLocalizations.basicConfig),
|
||||
subtitle: Text(appLocalizations.basicConfigDesc),
|
||||
delegate: OpenDelegate(
|
||||
title: appLocalizations.override,
|
||||
widget: const ConfigFragment(),
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
"other": "Other",
|
||||
"about": "About",
|
||||
"en": "English",
|
||||
"ja": "Japanese",
|
||||
"ru": "Russian",
|
||||
"zh_CN": "Simplified Chinese",
|
||||
"theme": "Theme",
|
||||
"themeDesc": "Set dark mode,adjust the color",
|
||||
@@ -121,7 +123,6 @@
|
||||
"project": "Project",
|
||||
"core": "Core",
|
||||
"tabAnimation": "Tab animation",
|
||||
"tabAnimationDesc": "When enabled, the home tab will add a toggle animation",
|
||||
"desc": "A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.",
|
||||
"startVpn": "Starting VPN...",
|
||||
"stopVpn": "Stopping VPN...",
|
||||
@@ -179,7 +180,6 @@
|
||||
"requests": "Requests",
|
||||
"requestsDesc": "View recently request records",
|
||||
"findProcessMode": "Find process",
|
||||
"findProcessModeDesc": "There is a risk of flashback after opening",
|
||||
"init": "Init",
|
||||
"infiniteTime": "Long term effective",
|
||||
"expirationTime": "Expiration time",
|
||||
@@ -215,12 +215,12 @@
|
||||
"go": "Go",
|
||||
"externalLink": "External link",
|
||||
"otherContributors": "Other contributors",
|
||||
"autoCloseConnections": "Auto lose connections",
|
||||
"autoCloseConnections": "Auto close connections",
|
||||
"autoCloseConnectionsDesc": "Auto close connections after change node",
|
||||
"onlyStatisticsProxy": "Only statistics proxy",
|
||||
"onlyStatisticsProxyDesc": "When turned on, only statistics proxy traffic",
|
||||
"deleteProfileTip": "Sure you want to delete the current profile?",
|
||||
"prueBlackMode": "Prue black mode",
|
||||
"pureBlackMode": "Pure black mode",
|
||||
"keepAliveIntervalDesc": "Tcp keep alive interval",
|
||||
"entries": " entries",
|
||||
"local": "Local",
|
||||
@@ -247,7 +247,6 @@
|
||||
"stop": "Stop",
|
||||
"appDesc": "Processing app related settings",
|
||||
"vpnDesc": "Modify VPN related settings",
|
||||
"generalDesc": "Overwrite general settings",
|
||||
"dnsDesc": "Update DNS related settings",
|
||||
"key": "Key",
|
||||
"value": "Value",
|
||||
@@ -343,5 +342,35 @@
|
||||
"copyLink": "Copy link",
|
||||
"exportFile": "Export file",
|
||||
"cacheCorrupt": "The cache is corrupt. Do you want to clear it?",
|
||||
"detectionTip": "Relying on third-party api is for reference only"
|
||||
"detectionTip": "Relying on third-party api is for reference only",
|
||||
"listen": "Listen",
|
||||
"keyExists": "The current key already exists",
|
||||
"valueExists": "The current value already exists",
|
||||
"undo": "undo",
|
||||
"redo": "redo",
|
||||
"none": "none",
|
||||
"basicConfig": "Basic configuration",
|
||||
"basicConfigDesc": "Modify the basic configuration globally",
|
||||
"selectedCountTitle": "{count} items have been selected",
|
||||
"addRule": "Add rule",
|
||||
"ruleProviderEmptyTip": "Rule provider cannot be empty",
|
||||
"ruleName": "Rule name",
|
||||
"content": "Content",
|
||||
"contentEmptyTip": "Content cannot be empty",
|
||||
"subRule": "Sub rule",
|
||||
"subRuleEmptyTip": "Sub rule content cannot be empty",
|
||||
"ruleTarget": "Rule target",
|
||||
"ruleTargetEmptyTip": "Rule target cannot be empty",
|
||||
"sourceIp": "Source IP",
|
||||
"noResolve": "No resolve IP",
|
||||
"getOriginRules": "Get original rules",
|
||||
"overrideOriginRules": "Override the original rule",
|
||||
"addedOriginRules": "Attach on the original rules",
|
||||
"enableOverride": "Enable override",
|
||||
"deleteRuleTip": "Are you sure you want to delete the selected rule?",
|
||||
"saveChanges": "Do you want to save the changes?",
|
||||
"generalDesc": "Modify general settings",
|
||||
"findProcessModeDesc": "There is a certain performance loss after opening",
|
||||
"tabAnimationDesc": "Effective only in mobile view",
|
||||
"saveTip": "Are you sure you want to save?"
|
||||
}
|
||||
376
lib/l10n/arb/intl_ja.arb
Normal file
376
lib/l10n/arb/intl_ja.arb
Normal file
@@ -0,0 +1,376 @@
|
||||
{
|
||||
"rule": "ルール",
|
||||
"global": "グローバル",
|
||||
"direct": "ダイレクト",
|
||||
"dashboard": "ダッシュボード",
|
||||
"proxies": "プロキシ",
|
||||
"profile": "プロファイル",
|
||||
"profiles": "プロファイル一覧",
|
||||
"tools": "ツール",
|
||||
"logs": "ログ",
|
||||
"logsDesc": "ログキャプチャ記録",
|
||||
"resources": "リソース",
|
||||
"resourcesDesc": "外部リソース関連情報",
|
||||
"trafficUsage": "トラフィック使用量",
|
||||
"coreInfo": "コア情報",
|
||||
"nullCoreInfoDesc": "コア情報を取得できません",
|
||||
"networkSpeed": "ネットワーク速度",
|
||||
"outboundMode": "アウトバウンドモード",
|
||||
"networkDetection": "ネットワーク検出",
|
||||
"upload": "アップロード",
|
||||
"download": "ダウンロード",
|
||||
"noProxy": "プロキシなし",
|
||||
"noProxyDesc": "プロファイルを作成するか、有効なプロファイルを追加してください",
|
||||
"nullProfileDesc": "プロファイルがありません。追加してください",
|
||||
"nullLogsDesc": "ログがありません",
|
||||
"settings": "設定",
|
||||
"language": "言語",
|
||||
"defaultText": "デフォルト",
|
||||
"more": "詳細",
|
||||
"other": "その他",
|
||||
"about": "について",
|
||||
"en": "英語",
|
||||
"ja": "日本語",
|
||||
"ru": "ロシア語",
|
||||
"zh_CN": "簡体字中国語",
|
||||
"theme": "テーマ",
|
||||
"themeDesc": "ダークモードの設定、色の調整",
|
||||
"override": "上書き",
|
||||
"overrideDesc": "プロキシ関連設定を上書き",
|
||||
"allowLan": "LANを許可",
|
||||
"allowLanDesc": "LAN経由でのプロキシアクセスを許可",
|
||||
"tun": "TUN",
|
||||
"tunDesc": "管理者モードでのみ有効",
|
||||
"minimizeOnExit": "終了時に最小化",
|
||||
"minimizeOnExitDesc": "システムの終了イベントを変更",
|
||||
"autoLaunch": "自動起動",
|
||||
"autoLaunchDesc": "システムの自動起動に従う",
|
||||
"silentLaunch": "バックグラウンド起動",
|
||||
"silentLaunchDesc": "バックグラウンドで起動",
|
||||
"autoRun": "自動実行",
|
||||
"autoRunDesc": "アプリ起動時に自動実行",
|
||||
"logcat": "ログキャット",
|
||||
"logcatDesc": "無効化するとログエントリを非表示",
|
||||
"autoCheckUpdate": "自動更新チェック",
|
||||
"autoCheckUpdateDesc": "起動時に更新を自動チェック",
|
||||
"accessControl": "アクセス制御",
|
||||
"accessControlDesc": "アプリケーションのプロキシアクセスを設定",
|
||||
"application": "アプリケーション",
|
||||
"applicationDesc": "アプリ関連設定を変更",
|
||||
"edit": "編集",
|
||||
"confirm": "確認",
|
||||
"update": "更新",
|
||||
"add": "追加",
|
||||
"save": "保存",
|
||||
"delete": "削除",
|
||||
"years": "年",
|
||||
"months": "月",
|
||||
"hours": "時間",
|
||||
"days": "日",
|
||||
"minutes": "分",
|
||||
"seconds": "秒",
|
||||
"ago": "前",
|
||||
"just": "たった今",
|
||||
"qrcode": "QRコード",
|
||||
"qrcodeDesc": "QRコードをスキャンしてプロファイルを取得",
|
||||
"url": "URL",
|
||||
"urlDesc": "URL経由でプロファイルを取得",
|
||||
"file": "ファイル",
|
||||
"fileDesc": "プロファイルを直接アップロード",
|
||||
"name": "名前",
|
||||
"profileNameNullValidationDesc": "プロファイル名を入力してください",
|
||||
"profileUrlNullValidationDesc": "プロファイルURLを入力してください",
|
||||
"profileUrlInvalidValidationDesc": "有効なプロファイルURLを入力してください",
|
||||
"autoUpdate": "自動更新",
|
||||
"autoUpdateInterval": "自動更新間隔(分)",
|
||||
"profileAutoUpdateIntervalNullValidationDesc": "自動更新間隔を入力してください",
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc": "有効な間隔形式を入力してください",
|
||||
"themeMode": "テーマモード",
|
||||
"themeColor": "テーマカラー",
|
||||
"preview": "プレビュー",
|
||||
"auto": "自動",
|
||||
"light": "ライト",
|
||||
"dark": "ダーク",
|
||||
"importFromURL": "URLからインポート",
|
||||
"submit": "送信",
|
||||
"doYouWantToPass": "通過させますか?",
|
||||
"create": "作成",
|
||||
"defaultSort": "デフォルト順",
|
||||
"delaySort": "遅延順",
|
||||
"nameSort": "名前順",
|
||||
"pleaseUploadFile": "ファイルをアップロードしてください",
|
||||
"pleaseUploadValidQrcode": "有効なQRコードをアップロードしてください",
|
||||
"blacklistMode": "ブラックリストモード",
|
||||
"whitelistMode": "ホワイトリストモード",
|
||||
"filterSystemApp": "システムアプリを除外",
|
||||
"cancelFilterSystemApp": "システムアプリの除外を解除",
|
||||
"selectAll": "すべて選択",
|
||||
"cancelSelectAll": "全選択解除",
|
||||
"appAccessControl": "アプリアクセス制御",
|
||||
"accessControlAllowDesc": "選択したアプリのみVPNを許可",
|
||||
"accessControlNotAllowDesc": "選択したアプリをVPNから除外",
|
||||
"selected": "選択済み",
|
||||
"unableToUpdateCurrentProfileDesc": "現在のプロファイルを更新できません",
|
||||
"noMoreInfoDesc": "追加情報なし",
|
||||
"profileParseErrorDesc": "プロファイル解析エラー",
|
||||
"proxyPort": "プロキシポート",
|
||||
"proxyPortDesc": "Clashのリスニングポートを設定",
|
||||
"port": "ポート",
|
||||
"logLevel": "ログレベル",
|
||||
"show": "表示",
|
||||
"exit": "終了",
|
||||
"systemProxy": "システムプロキシ",
|
||||
"project": "プロジェクト",
|
||||
"core": "コア",
|
||||
"tabAnimation": "タブアニメーション",
|
||||
"desc": "ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。",
|
||||
"startVpn": "VPNを開始中...",
|
||||
"stopVpn": "VPNを停止中...",
|
||||
"discovery": "新しいバージョンを発見",
|
||||
"compatible": "互換モード",
|
||||
"compatibleDesc": "有効化すると一部機能を失いますが、Clashの完全サポートを獲得",
|
||||
"notSelectedTip": "現在のプロキシグループは選択できません",
|
||||
"tip": "ヒント",
|
||||
"backupAndRecovery": "バックアップと復元",
|
||||
"backupAndRecoveryDesc": "WebDAVまたはファイルでデータを同期",
|
||||
"account": "アカウント",
|
||||
"backup": "バックアップ",
|
||||
"recovery": "復元",
|
||||
"recoveryProfiles": "プロファイルのみ復元",
|
||||
"recoveryAll": "全データ復元",
|
||||
"recoverySuccess": "復元成功",
|
||||
"backupSuccess": "バックアップ成功",
|
||||
"noInfo": "情報なし",
|
||||
"pleaseBindWebDAV": "WebDAVをバインドしてください",
|
||||
"bind": "バインド",
|
||||
"connectivity": "接続性:",
|
||||
"webDAVConfiguration": "WebDAV設定",
|
||||
"address": "アドレス",
|
||||
"addressHelp": "WebDAVサーバーアドレス",
|
||||
"addressTip": "有効なWebDAVアドレスを入力",
|
||||
"password": "パスワード",
|
||||
"passwordTip": "パスワードは必須です",
|
||||
"accountTip": "アカウントは必須です",
|
||||
"checkUpdate": "更新を確認",
|
||||
"discoverNewVersion": "新バージョンを発見",
|
||||
"checkUpdateError": "アプリは最新版です",
|
||||
"goDownload": "ダウンロードへ",
|
||||
"unknown": "不明",
|
||||
"geoData": "地域データ",
|
||||
"externalResources": "外部リソース",
|
||||
"checking": "確認中...",
|
||||
"country": "国",
|
||||
"checkError": "確認エラー",
|
||||
"search": "検索",
|
||||
"allowBypass": "アプリがVPNをバイパスすることを許可",
|
||||
"allowBypassDesc": "有効化すると一部アプリがVPNをバイパス",
|
||||
"externalController": "外部コントローラー",
|
||||
"externalControllerDesc": "有効化するとClashコアをポート9090で制御可能",
|
||||
"ipv6Desc": "有効化するとIPv6トラフィックを受信可能",
|
||||
"app": "アプリ",
|
||||
"general": "一般",
|
||||
"vpnSystemProxyDesc": "HTTPプロキシをVpnServiceに接続",
|
||||
"systemProxyDesc": "HTTPプロキシをVpnServiceに接続",
|
||||
"unifiedDelay": "統一遅延",
|
||||
"unifiedDelayDesc": "ハンドシェイクなどの余分な遅延を削除",
|
||||
"tcpConcurrent": "TCP並列処理",
|
||||
"tcpConcurrentDesc": "TCP並列処理を許可",
|
||||
"geodataLoader": "Geo低メモリモード",
|
||||
"geodataLoaderDesc": "有効化するとGeo低メモリローダーを使用",
|
||||
"requests": "リクエスト",
|
||||
"requestsDesc": "最近のリクエスト記録を表示",
|
||||
"findProcessMode": "プロセス検出",
|
||||
"init": "初期化",
|
||||
"infiniteTime": "長期有効",
|
||||
"expirationTime": "有効期限",
|
||||
"connections": "接続",
|
||||
"connectionsDesc": "現在の接続データを表示",
|
||||
"nullRequestsDesc": "リクエストなし",
|
||||
"nullConnectionsDesc": "接続なし",
|
||||
"intranetIP": "イントラネットIP",
|
||||
"view": "表示",
|
||||
"cut": "切り取り",
|
||||
"copy": "コピー",
|
||||
"paste": "貼り付け",
|
||||
"testUrl": "URLテスト",
|
||||
"sync": "同期",
|
||||
"exclude": "最近のタスクから非表示",
|
||||
"excludeDesc": "アプリがバックグラウンド時に最近のタスクから非表示",
|
||||
"oneColumn": "1列",
|
||||
"twoColumns": "2列",
|
||||
"threeColumns": "3列",
|
||||
"fourColumns": "4列",
|
||||
"expand": "標準",
|
||||
"shrink": "縮小",
|
||||
"min": "最小化",
|
||||
"tab": "タブ",
|
||||
"list": "リスト",
|
||||
"delay": "遅延",
|
||||
"style": "スタイル",
|
||||
"size": "サイズ",
|
||||
"sort": "並び替え",
|
||||
"columns": "列",
|
||||
"proxiesSetting": "プロキシ設定",
|
||||
"proxyGroup": "プロキシグループ",
|
||||
"go": "移動",
|
||||
"externalLink": "外部リンク",
|
||||
"otherContributors": "その他の貢献者",
|
||||
"autoCloseConnections": "接続を自動閉じる",
|
||||
"autoCloseConnectionsDesc": "ノード変更後に接続を自動閉じる",
|
||||
"onlyStatisticsProxy": "プロキシのみ統計",
|
||||
"onlyStatisticsProxyDesc": "有効化するとプロキシトラフィックのみ統計",
|
||||
"deleteProfileTip": "現在のプロファイルを削除しますか?",
|
||||
"pureBlackMode": "純黒モード",
|
||||
"keepAliveIntervalDesc": "TCPキープアライブ間隔",
|
||||
"entries": " エントリ",
|
||||
"local": "ローカル",
|
||||
"remote": "リモート",
|
||||
"remoteBackupDesc": "WebDAVにデータをバックアップ",
|
||||
"remoteRecoveryDesc": "WebDAVからデータを復元",
|
||||
"localBackupDesc": "ローカルにデータをバックアップ",
|
||||
"localRecoveryDesc": "ファイルからデータを復元",
|
||||
"mode": "モード",
|
||||
"time": "時間",
|
||||
"source": "ソース",
|
||||
"allApps": "全アプリ",
|
||||
"onlyOtherApps": "サードパーティアプリのみ",
|
||||
"action": "アクション",
|
||||
"intelligentSelected": "インテリジェント選択",
|
||||
"clipboardImport": "クリップボードからインポート",
|
||||
"clipboardExport": "クリップボードにエクスポート",
|
||||
"layout": "レイアウト",
|
||||
"tight": "密",
|
||||
"standard": "標準",
|
||||
"loose": "疎",
|
||||
"profilesSort": "プロファイルの並び替え",
|
||||
"start": "開始",
|
||||
"stop": "停止",
|
||||
"appDesc": "アプリ関連設定の処理",
|
||||
"vpnDesc": "VPN関連設定の変更",
|
||||
"dnsDesc": "DNS関連設定の更新",
|
||||
"key": "キー",
|
||||
"value": "値",
|
||||
"notEmpty": "空欄不可",
|
||||
"hostsDesc": "ホストを追加",
|
||||
"vpnTip": "変更はVPN再起動後に有効",
|
||||
"vpnEnableDesc": "VpnService経由で全システムトラフィックをルーティング",
|
||||
"options": "オプション",
|
||||
"loopback": "ループバック解除ツール",
|
||||
"loopbackDesc": "UWPループバック解除用",
|
||||
"providers": "プロバイダー",
|
||||
"proxyProviders": "プロキシプロバイダー",
|
||||
"ruleProviders": "ルールプロバイダー",
|
||||
"overrideDns": "DNS上書き",
|
||||
"overrideDnsDesc": "有効化するとプロファイルのDNS設定を上書き",
|
||||
"status": "ステータス",
|
||||
"statusDesc": "無効時はシステムDNSを使用",
|
||||
"preferH3Desc": "DOHのHTTP/3を優先使用",
|
||||
"respectRules": "ルール尊重",
|
||||
"respectRulesDesc": "DNS接続がルールに従う(proxy-server-nameserverの設定が必要)",
|
||||
"dnsMode": "DNSモード",
|
||||
"fakeipRange": "Fakeip範囲",
|
||||
"fakeipFilter": "Fakeipフィルター",
|
||||
"defaultNameserver": "デフォルトネームサーバー",
|
||||
"defaultNameserverDesc": "DNSサーバーの解決用",
|
||||
"nameserver": "ネームサーバー",
|
||||
"nameserverDesc": "ドメイン解決用",
|
||||
"useHosts": "ホストを使用",
|
||||
"useSystemHosts": "システムホストを使用",
|
||||
"nameserverPolicy": "ネームサーバーポリシー",
|
||||
"nameserverPolicyDesc": "対応するネームサーバーポリシーを指定",
|
||||
"proxyNameserver": "プロキシネームサーバー",
|
||||
"proxyNameserverDesc": "プロキシノード解決用ドメイン",
|
||||
"fallback": "フォールバック",
|
||||
"fallbackDesc": "通常はオフショアDNSを使用",
|
||||
"fallbackFilter": "フォールバックフィルター",
|
||||
"geoipCode": "GeoIPコード",
|
||||
"ipcidr": "IPCIDR",
|
||||
"domain": "ドメイン",
|
||||
"reset": "リセット",
|
||||
"action_view": "表示/非表示",
|
||||
"action_start": "開始/停止",
|
||||
"action_mode": "モード切替",
|
||||
"action_proxy": "システムプロキシ",
|
||||
"action_tun": "TUN",
|
||||
"disclaimer": "免責事項",
|
||||
"disclaimerDesc": "本ソフトウェアは学習交流や科学研究などの非営利目的でのみ使用されます。商用利用は厳禁です。いかなる商用活動も本ソフトウェアとは無関係です。",
|
||||
"agree": "同意",
|
||||
"hotkeyManagement": "ホットキー管理",
|
||||
"hotkeyManagementDesc": "キーボードでアプリを制御",
|
||||
"pressKeyboard": "キーボードを押してください",
|
||||
"inputCorrectHotkey": "正しいホットキーを入力",
|
||||
"hotkeyConflict": "ホットキー競合",
|
||||
"remove": "削除",
|
||||
"noHotKey": "ホットキーなし",
|
||||
"noNetwork": "ネットワークなし",
|
||||
"ipv6InboundDesc": "IPv6インバウンドを許可",
|
||||
"exportLogs": "ログをエクスポート",
|
||||
"exportSuccess": "エクスポート成功",
|
||||
"iconStyle": "アイコンスタイル",
|
||||
"onlyIcon": "アイコンのみ",
|
||||
"noIcon": "なし",
|
||||
"stackMode": "スタックモード",
|
||||
"network": "ネットワーク",
|
||||
"networkDesc": "ネットワーク関連設定の変更",
|
||||
"bypassDomain": "バイパスドメイン",
|
||||
"bypassDomainDesc": "システムプロキシ有効時のみ適用",
|
||||
"resetTip": "リセットを確定",
|
||||
"regExp": "正規表現",
|
||||
"icon": "アイコン",
|
||||
"iconConfiguration": "アイコン設定",
|
||||
"noData": "データなし",
|
||||
"adminAutoLaunch": "管理者自動起動",
|
||||
"adminAutoLaunchDesc": "管理者モードで起動",
|
||||
"fontFamily": "フォントファミリー",
|
||||
"systemFont": "システムフォント",
|
||||
"toggle": "トグル",
|
||||
"system": "システム",
|
||||
"routeMode": "ルートモード",
|
||||
"routeMode_bypassPrivate": "プライベートルートをバイパス",
|
||||
"routeMode_config": "設定を使用",
|
||||
"routeAddress": "ルートアドレス",
|
||||
"routeAddressDesc": "ルートアドレスを設定",
|
||||
"pleaseInputAdminPassword": "管理者パスワードを入力",
|
||||
"copyEnvVar": "環境変数をコピー",
|
||||
"memoryInfo": "メモリ情報",
|
||||
"cancel": "キャンセル",
|
||||
"fileIsUpdate": "ファイルが変更されました。保存しますか?",
|
||||
"profileHasUpdate": "プロファイルが変更されました。自動更新を無効化しますか?",
|
||||
"hasCacheChange": "変更をキャッシュしますか?",
|
||||
"nullProxies": "プロキシなし",
|
||||
"copySuccess": "コピー成功",
|
||||
"copyLink": "リンクをコピー",
|
||||
"exportFile": "ファイルをエクスポート",
|
||||
"cacheCorrupt": "キャッシュが破損しています。クリアしますか?",
|
||||
"detectionTip": "サードパーティAPIに依存(参考値)",
|
||||
"listen": "リスン",
|
||||
"keyExists": "現在のキーは既に存在します",
|
||||
"valueExists": "現在の値は既に存在します",
|
||||
"undo": "元に戻す",
|
||||
"redo": "やり直す",
|
||||
"none": "なし",
|
||||
"basicConfig": "基本設定",
|
||||
"basicConfigDesc": "基本設定をグローバルに変更",
|
||||
"selectedCountTitle": "{count} 項目が選択されています",
|
||||
"addRule": "ルールを追加",
|
||||
"ruleProviderEmptyTip": "ルールプロバイダーは必須です",
|
||||
"ruleName": "ルール名",
|
||||
"content": "内容",
|
||||
"contentEmptyTip": "内容は必須です",
|
||||
"subRule": "サブルール",
|
||||
"subRuleEmptyTip": "サブルールの内容は必須です",
|
||||
"ruleTarget": "ルール対象",
|
||||
"ruleTargetEmptyTip": "ルール対象は必須です",
|
||||
"sourceIp": "送信元IP",
|
||||
"noResolve": "IPを解決しない",
|
||||
"getOriginRules": "元のルールを取得",
|
||||
"overrideOriginRules": "元のルールを上書き",
|
||||
"addedOriginRules": "元のルールに追加",
|
||||
"enableOverride": "上書きを有効化",
|
||||
"deleteRuleTip": "選択したルールを削除しますか?",
|
||||
"saveChanges": "変更を保存しますか?",
|
||||
"generalDesc": "一般設定を変更",
|
||||
"findProcessModeDesc": "有効化するとパフォーマンスが若干低下します",
|
||||
"tabAnimationDesc": "モバイル表示でのみ有効",
|
||||
"saveTip": "保存してもよろしいですか?"
|
||||
}
|
||||
376
lib/l10n/arb/intl_ru.arb
Normal file
376
lib/l10n/arb/intl_ru.arb
Normal file
@@ -0,0 +1,376 @@
|
||||
{
|
||||
"rule": "Правило",
|
||||
"global": "Глобальный",
|
||||
"direct": "Прямой",
|
||||
"dashboard": "Панель управления",
|
||||
"proxies": "Прокси",
|
||||
"profile": "Профиль",
|
||||
"profiles": "Профили",
|
||||
"tools": "Инструменты",
|
||||
"logs": "Логи",
|
||||
"logsDesc": "Записи захвата логов",
|
||||
"resources": "Ресурсы",
|
||||
"resourcesDesc": "Информация, связанная с внешними ресурсами",
|
||||
"trafficUsage": "Использование трафика",
|
||||
"coreInfo": "Информация о ядре",
|
||||
"nullCoreInfoDesc": "Не удалось получить информацию о ядре",
|
||||
"networkSpeed": "Скорость сети",
|
||||
"outboundMode": "Режим исходящего трафика",
|
||||
"networkDetection": "Обнаружение сети",
|
||||
"upload": "Загрузка",
|
||||
"download": "Скачивание",
|
||||
"noProxy": "Нет прокси",
|
||||
"noProxyDesc": "Пожалуйста, создайте профиль или добавьте действительный профиль",
|
||||
"nullProfileDesc": "Нет профиля, пожалуйста, добавьте профиль",
|
||||
"nullLogsDesc": "Нет логов",
|
||||
"settings": "Настройки",
|
||||
"language": "Язык",
|
||||
"defaultText": "По умолчанию",
|
||||
"more": "Еще",
|
||||
"other": "Другое",
|
||||
"about": "О программе",
|
||||
"en": "Английский",
|
||||
"ja": "Японский",
|
||||
"ru": "Русский",
|
||||
"zh_CN": "Упрощенный китайский",
|
||||
"theme": "Тема",
|
||||
"themeDesc": "Установить темный режим, настроить цвет",
|
||||
"override": "Переопределить",
|
||||
"overrideDesc": "Переопределить конфигурацию, связанную с прокси",
|
||||
"allowLan": "Разрешить LAN",
|
||||
"allowLanDesc": "Разрешить доступ к прокси через локальную сеть",
|
||||
"tun": "TUN",
|
||||
"tunDesc": "действительно только в режиме администратора",
|
||||
"minimizeOnExit": "Свернуть при выходе",
|
||||
"minimizeOnExitDesc": "Изменить стандартное событие выхода из системы",
|
||||
"autoLaunch": "Автозапуск",
|
||||
"autoLaunchDesc": "Следовать автозапуску системы",
|
||||
"silentLaunch": "Тихий запуск",
|
||||
"silentLaunchDesc": "Запуск в фоновом режиме",
|
||||
"autoRun": "Автозапуск",
|
||||
"autoRunDesc": "Автоматический запуск при открытии приложения",
|
||||
"logcat": "Logcat",
|
||||
"logcatDesc": "Отключение скроет запись логов",
|
||||
"autoCheckUpdate": "Автопроверка обновлений",
|
||||
"autoCheckUpdateDesc": "Автоматически проверять обновления при запуске приложения",
|
||||
"accessControl": "Контроль доступа",
|
||||
"accessControlDesc": "Настройка доступа приложений к прокси",
|
||||
"application": "Приложение",
|
||||
"applicationDesc": "Изменение настроек, связанных с приложением",
|
||||
"edit": "Редактировать",
|
||||
"confirm": "Подтвердить",
|
||||
"update": "Обновить",
|
||||
"add": "Добавить",
|
||||
"save": "Сохранить",
|
||||
"delete": "Удалить",
|
||||
"years": "Лет",
|
||||
"months": "Месяцев",
|
||||
"hours": "Часов",
|
||||
"days": "Дней",
|
||||
"minutes": "Минут",
|
||||
"seconds": "Секунд",
|
||||
"ago": " назад",
|
||||
"just": "Только что",
|
||||
"qrcode": "QR-код",
|
||||
"qrcodeDesc": "Сканируйте QR-код для получения профиля",
|
||||
"url": "URL",
|
||||
"urlDesc": "Получить профиль через URL",
|
||||
"file": "Файл",
|
||||
"fileDesc": "Прямая загрузка профиля",
|
||||
"name": "Имя",
|
||||
"profileNameNullValidationDesc": "Пожалуйста, введите имя профиля",
|
||||
"profileUrlNullValidationDesc": "Пожалуйста, введите URL профиля",
|
||||
"profileUrlInvalidValidationDesc": "Пожалуйста, введите действительный URL профиля",
|
||||
"autoUpdate": "Автообновление",
|
||||
"autoUpdateInterval": "Интервал автообновления (минуты)",
|
||||
"profileAutoUpdateIntervalNullValidationDesc": "Пожалуйста, введите интервал времени для автообновления",
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc": "Пожалуйста, введите действительный формат интервала времени",
|
||||
"themeMode": "Режим темы",
|
||||
"themeColor": "Цвет темы",
|
||||
"preview": "Предпросмотр",
|
||||
"auto": "Авто",
|
||||
"light": "Светлый",
|
||||
"dark": "Темный",
|
||||
"importFromURL": "Импорт из URL",
|
||||
"submit": "Отправить",
|
||||
"doYouWantToPass": "Вы хотите пропустить",
|
||||
"create": "Создать",
|
||||
"defaultSort": "Сортировка по умолчанию",
|
||||
"delaySort": "Сортировка по задержке",
|
||||
"nameSort": "Сортировка по имени",
|
||||
"pleaseUploadFile": "Пожалуйста, загрузите файл",
|
||||
"pleaseUploadValidQrcode": "Пожалуйста, загрузите действительный QR-код",
|
||||
"blacklistMode": "Режим черного списка",
|
||||
"whitelistMode": "Режим белого списка",
|
||||
"filterSystemApp": "Фильтровать системные приложения",
|
||||
"cancelFilterSystemApp": "Отменить фильтрацию системных приложений",
|
||||
"selectAll": "Выбрать все",
|
||||
"cancelSelectAll": "Отменить выбор всего",
|
||||
"appAccessControl": "Контроль доступа приложений",
|
||||
"accessControlAllowDesc": "Разрешить только выбранным приложениям доступ к VPN",
|
||||
"accessControlNotAllowDesc": "Выбранные приложения будут исключены из VPN",
|
||||
"selected": "Выбрано",
|
||||
"unableToUpdateCurrentProfileDesc": "невозможно обновить текущий профиль",
|
||||
"noMoreInfoDesc": "Нет дополнительной информации",
|
||||
"profileParseErrorDesc": "ошибка разбора профиля",
|
||||
"proxyPort": "Порт прокси",
|
||||
"proxyPortDesc": "Установить порт прослушивания Clash",
|
||||
"port": "Порт",
|
||||
"logLevel": "Уровень логов",
|
||||
"show": "Показать",
|
||||
"exit": "Выход",
|
||||
"systemProxy": "Системный прокси",
|
||||
"project": "Проект",
|
||||
"core": "Ядро",
|
||||
"tabAnimation": "Анимация вкладок",
|
||||
"desc": "Многоплатформенный прокси-клиент на основе ClashMeta, простой и удобный в использовании, с открытым исходным кодом и без рекламы.",
|
||||
"startVpn": "Запуск VPN...",
|
||||
"stopVpn": "Остановка VPN...",
|
||||
"discovery": "Обнаружена новая версия",
|
||||
"compatible": "Режим совместимости",
|
||||
"compatibleDesc": "Включение приведет к потере части функциональности приложения, но обеспечит полную поддержку Clash.",
|
||||
"notSelectedTip": "Текущая группа прокси не может быть выбрана.",
|
||||
"tip": "подсказка",
|
||||
"backupAndRecovery": "Резервное копирование и восстановление",
|
||||
"backupAndRecoveryDesc": "Синхронизация данных через WebDAV или файл",
|
||||
"account": "Аккаунт",
|
||||
"backup": "Резервное копирование",
|
||||
"recovery": "Восстановление",
|
||||
"recoveryProfiles": "Только восстановление профилей",
|
||||
"recoveryAll": "Восстановить все данные",
|
||||
"recoverySuccess": "Восстановление успешно",
|
||||
"backupSuccess": "Резервное копирование успешно",
|
||||
"noInfo": "Нет информации",
|
||||
"pleaseBindWebDAV": "Пожалуйста, привяжите WebDAV",
|
||||
"bind": "Привязать",
|
||||
"connectivity": "Связь:",
|
||||
"webDAVConfiguration": "Конфигурация WebDAV",
|
||||
"address": "Адрес",
|
||||
"addressHelp": "Адрес сервера WebDAV",
|
||||
"addressTip": "Пожалуйста, введите действительный адрес WebDAV",
|
||||
"password": "Пароль",
|
||||
"passwordTip": "Пароль не может быть пустым",
|
||||
"accountTip": "Аккаунт не может быть пустым",
|
||||
"checkUpdate": "Проверить обновления",
|
||||
"discoverNewVersion": "Обнаружена новая версия",
|
||||
"checkUpdateError": "Текущее приложение уже является последней версией",
|
||||
"goDownload": "Перейти к загрузке",
|
||||
"unknown": "Неизвестно",
|
||||
"geoData": "Геоданные",
|
||||
"externalResources": "Внешние ресурсы",
|
||||
"checking": "Проверка...",
|
||||
"country": "Страна",
|
||||
"checkError": "Ошибка проверки",
|
||||
"search": "Поиск",
|
||||
"allowBypass": "Разрешить приложениям обходить VPN",
|
||||
"allowBypassDesc": "Некоторые приложения могут обходить VPN при включении",
|
||||
"externalController": "Внешний контроллер",
|
||||
"externalControllerDesc": "При включении ядро Clash можно контролировать на порту 9090",
|
||||
"ipv6Desc": "При включении будет возможно получать IPv6 трафик",
|
||||
"app": "Приложение",
|
||||
"general": "Общие",
|
||||
"vpnSystemProxyDesc": "Прикрепить HTTP-прокси к VpnService",
|
||||
"systemProxyDesc": "Прикрепить HTTP-прокси к VpnService",
|
||||
"unifiedDelay": "Унифицированная задержка",
|
||||
"unifiedDelayDesc": "Убрать дополнительные задержки, такие как рукопожатие",
|
||||
"tcpConcurrent": "TCP параллелизм",
|
||||
"tcpConcurrentDesc": "Включение позволит использовать параллелизм TCP",
|
||||
"geodataLoader": "Режим низкого потребления памяти для геоданных",
|
||||
"geodataLoaderDesc": "Включение будет использовать загрузчик геоданных с низким потреблением памяти",
|
||||
"requests": "Запросы",
|
||||
"requestsDesc": "Просмотр последних записей запросов",
|
||||
"findProcessMode": "Режим поиска процесса",
|
||||
"init": "Инициализация",
|
||||
"infiniteTime": "Долгосрочное действие",
|
||||
"expirationTime": "Время истечения",
|
||||
"connections": "Соединения",
|
||||
"connectionsDesc": "Просмотр текущих данных о соединениях",
|
||||
"nullRequestsDesc": "Нет запросов",
|
||||
"nullConnectionsDesc": "Нет соединений",
|
||||
"intranetIP": "Внутренний IP",
|
||||
"view": "Просмотр",
|
||||
"cut": "Вырезать",
|
||||
"copy": "Копировать",
|
||||
"paste": "Вставить",
|
||||
"testUrl": "Тест URL",
|
||||
"sync": "Синхронизация",
|
||||
"exclude": "Скрыть из последних задач",
|
||||
"excludeDesc": "Когда приложение находится в фоновом режиме, оно скрыто из последних задач",
|
||||
"oneColumn": "Один столбец",
|
||||
"twoColumns": "Два столбца",
|
||||
"threeColumns": "Три столбца",
|
||||
"fourColumns": "Четыре столбца",
|
||||
"expand": "Стандартный",
|
||||
"shrink": "Сжать",
|
||||
"min": "Мин",
|
||||
"tab": "Вкладка",
|
||||
"list": "Список",
|
||||
"delay": "Задержка",
|
||||
"style": "Стиль",
|
||||
"size": "Размер",
|
||||
"sort": "Сортировка",
|
||||
"columns": "Столбцы",
|
||||
"proxiesSetting": "Настройка прокси",
|
||||
"proxyGroup": "Группа прокси",
|
||||
"go": "Перейти",
|
||||
"externalLink": "Внешняя ссылка",
|
||||
"otherContributors": "Другие участники",
|
||||
"autoCloseConnections": "Автоматическое закрытие соединений",
|
||||
"autoCloseConnectionsDesc": "Автоматически закрывать соединения после смены узла",
|
||||
"onlyStatisticsProxy": "Только статистика прокси",
|
||||
"onlyStatisticsProxyDesc": "При включении будет учитываться только трафик прокси",
|
||||
"deleteProfileTip": "Вы уверены, что хотите удалить текущий профиль?",
|
||||
"pureBlackMode": "Чисто черный режим",
|
||||
"keepAliveIntervalDesc": "Интервал поддержания TCP-соединения",
|
||||
"entries": " записей",
|
||||
"local": "Локальный",
|
||||
"remote": "Удаленный",
|
||||
"remoteBackupDesc": "Резервное копирование локальных данных на WebDAV",
|
||||
"remoteRecoveryDesc": "Восстановление данных с WebDAV",
|
||||
"localBackupDesc": "Резервное копирование локальных данных на локальный диск",
|
||||
"localRecoveryDesc": "Восстановление данных из файла",
|
||||
"mode": "Режим",
|
||||
"time": "Время",
|
||||
"source": "Источник",
|
||||
"allApps": "Все приложения",
|
||||
"onlyOtherApps": "Только сторонние приложения",
|
||||
"action": "Действие",
|
||||
"intelligentSelected": "Интеллектуальный выбор",
|
||||
"clipboardImport": "Импорт из буфера обмена",
|
||||
"clipboardExport": "Экспорт в буфер обмена",
|
||||
"layout": "Макет",
|
||||
"tight": "Плотный",
|
||||
"standard": "Стандартный",
|
||||
"loose": "Свободный",
|
||||
"profilesSort": "Сортировка профилей",
|
||||
"start": "Старт",
|
||||
"stop": "Стоп",
|
||||
"appDesc": "Обработка настроек, связанных с приложением",
|
||||
"vpnDesc": "Изменение настроек, связанных с VPN",
|
||||
"dnsDesc": "Обновление настроек, связанных с DNS",
|
||||
"key": "Ключ",
|
||||
"value": "Значение",
|
||||
"notEmpty": "Не может быть пустым",
|
||||
"hostsDesc": "Добавить Hosts",
|
||||
"vpnTip": "Изменения вступят в силу после перезапуска VPN",
|
||||
"vpnEnableDesc": "Автоматически направляет весь системный трафик через VpnService",
|
||||
"options": "Опции",
|
||||
"loopback": "Инструмент разблокировки Loopback",
|
||||
"loopbackDesc": "Используется для разблокировки Loopback UWP",
|
||||
"providers": "Провайдеры",
|
||||
"proxyProviders": "Провайдеры прокси",
|
||||
"ruleProviders": "Провайдеры правил",
|
||||
"overrideDns": "Переопределить DNS",
|
||||
"overrideDnsDesc": "Включение переопределит настройки DNS в профиле",
|
||||
"status": "Статус",
|
||||
"statusDesc": "Системный DNS будет использоваться при выключении",
|
||||
"preferH3Desc": "Приоритетное использование HTTP/3 для DOH",
|
||||
"respectRules": "Соблюдение правил",
|
||||
"respectRulesDesc": "DNS-соединение следует правилам, необходимо настроить proxy-server-nameserver",
|
||||
"dnsMode": "Режим DNS",
|
||||
"fakeipRange": "Диапазон Fakeip",
|
||||
"fakeipFilter": "Фильтр Fakeip",
|
||||
"defaultNameserver": "Сервер имен по умолчанию",
|
||||
"defaultNameserverDesc": "Для разрешения DNS-сервера",
|
||||
"nameserver": "Сервер имен",
|
||||
"nameserverDesc": "Для разрешения домена",
|
||||
"useHosts": "Использовать hosts",
|
||||
"useSystemHosts": "Использовать системные hosts",
|
||||
"nameserverPolicy": "Политика сервера имен",
|
||||
"nameserverPolicyDesc": "Указать соответствующую политику сервера имен",
|
||||
"proxyNameserver": "Прокси-сервер имен",
|
||||
"proxyNameserverDesc": "Домен для разрешения прокси-узлов",
|
||||
"fallback": "Резервный",
|
||||
"fallbackDesc": "Обычно используется оффшорный DNS",
|
||||
"fallbackFilter": "Фильтр резервного DNS",
|
||||
"geoipCode": "Код Geoip",
|
||||
"ipcidr": "IPCIDR",
|
||||
"domain": "Домен",
|
||||
"reset": "Сброс",
|
||||
"action_view": "Показать/Скрыть",
|
||||
"action_start": "Старт/Стоп",
|
||||
"action_mode": "Переключить режим",
|
||||
"action_proxy": "Системный прокси",
|
||||
"action_tun": "TUN",
|
||||
"disclaimer": "Отказ от ответственности",
|
||||
"disclaimerDesc": "Это программное обеспечение используется только в некоммерческих целях, таких как учебные обмены и научные исследования. Запрещено использовать это программное обеспечение в коммерческих целях. Любая коммерческая деятельность, если таковая имеется, не имеет отношения к этому программному обеспечению.",
|
||||
"agree": "Согласен",
|
||||
"hotkeyManagement": "Управление горячими клавишами",
|
||||
"hotkeyManagementDesc": "Использование клавиатуры для управления приложением",
|
||||
"pressKeyboard": "Пожалуйста, нажмите клавишу.",
|
||||
"inputCorrectHotkey": "Пожалуйста, введите правильную горячую клавишу",
|
||||
"hotkeyConflict": "Конфликт горячих клавиш",
|
||||
"remove": "Удалить",
|
||||
"noHotKey": "Нет горячей клавиши",
|
||||
"noNetwork": "Нет сети",
|
||||
"ipv6InboundDesc": "Разрешить входящий IPv6",
|
||||
"exportLogs": "Экспорт логов",
|
||||
"exportSuccess": "Экспорт успешен",
|
||||
"iconStyle": "Стиль иконки",
|
||||
"onlyIcon": "Только иконка",
|
||||
"noIcon": "Нет иконки",
|
||||
"stackMode": "Режим стека",
|
||||
"network": "Сеть",
|
||||
"networkDesc": "Изменение настроек, связанных с сетью",
|
||||
"bypassDomain": "Обход домена",
|
||||
"bypassDomainDesc": "Действует только при включенном системном прокси",
|
||||
"resetTip": "Убедитесь, что хотите сбросить",
|
||||
"regExp": "Регулярное выражение",
|
||||
"icon": "Иконка",
|
||||
"iconConfiguration": "Конфигурация иконки",
|
||||
"noData": "Нет данных",
|
||||
"adminAutoLaunch": "Автозапуск с правами администратора",
|
||||
"adminAutoLaunchDesc": "Запуск с правами администратора при загрузке системы",
|
||||
"fontFamily": "Семейство шрифтов",
|
||||
"systemFont": "Системный шрифт",
|
||||
"toggle": "Переключить",
|
||||
"system": "Система",
|
||||
"routeMode": "Режим маршрутизации",
|
||||
"routeMode_bypassPrivate": "Обход частных адресов маршрутизации",
|
||||
"routeMode_config": "Использовать конфигурацию",
|
||||
"routeAddress": "Адрес маршрутизации",
|
||||
"routeAddressDesc": "Настройка адреса прослушивания маршрутизации",
|
||||
"pleaseInputAdminPassword": "Пожалуйста, введите пароль администратора",
|
||||
"copyEnvVar": "Копирование переменных окружения",
|
||||
"memoryInfo": "Информация о памяти",
|
||||
"cancel": "Отмена",
|
||||
"fileIsUpdate": "Файл был изменен. Хотите сохранить изменения?",
|
||||
"profileHasUpdate": "Профиль был изменен. Хотите отключить автообновление?",
|
||||
"hasCacheChange": "Хотите сохранить изменения в кэше?",
|
||||
"nullProxies": "Нет прокси",
|
||||
"copySuccess": "Копирование успешно",
|
||||
"copyLink": "Копировать ссылку",
|
||||
"exportFile": "Экспорт файла",
|
||||
"cacheCorrupt": "Кэш поврежден. Хотите очистить его?",
|
||||
"detectionTip": "Опирается на сторонний API, только для справки",
|
||||
"listen": "Слушать",
|
||||
"keyExists": "Текущий ключ уже существует",
|
||||
"valueExists": "Текущее значение уже существует",
|
||||
"undo": "Отменить",
|
||||
"redo": "Повторить",
|
||||
"none": "Нет",
|
||||
"basicConfig": "Базовая конфигурация",
|
||||
"basicConfigDesc": "Глобальное изменение базовых настроек",
|
||||
"selectedCountTitle": "Выбрано {count} элементов",
|
||||
"addRule": "Добавить правило",
|
||||
"ruleProviderEmptyTip": "Поставщик правил не может быть пустым",
|
||||
"ruleName": "Название правила",
|
||||
"content": "Содержание",
|
||||
"contentEmptyTip": "Содержание не может быть пустым",
|
||||
"subRule": "Подправило",
|
||||
"subRuleEmptyTip": "Содержание подправила не может быть пустым",
|
||||
"ruleTarget": "Цель правила",
|
||||
"ruleTargetEmptyTip": "Цель правила не может быть пустой",
|
||||
"sourceIp": "Исходный IP",
|
||||
"noResolve": "Не разрешать IP",
|
||||
"getOriginRules": "Получить оригинальные правила",
|
||||
"overrideOriginRules": "Переопределить оригинальное правило",
|
||||
"addedOriginRules": "Добавить к оригинальным правилам",
|
||||
"enableOverride": "Включить переопределение",
|
||||
"deleteRuleTip": "Вы уверены, что хотите удалить выбранное правило?",
|
||||
"saveChanges": "Сохранить изменения?",
|
||||
"generalDesc": "Изменение общих настроек",
|
||||
"findProcessModeDesc": "При включении возможны небольшие потери производительности",
|
||||
"tabAnimationDesc": "Действительно только в мобильном виде",
|
||||
"saveTip": "Вы уверены, что хотите сохранить?"
|
||||
}
|
||||
@@ -30,6 +30,8 @@
|
||||
"other": "其他",
|
||||
"about": "关于",
|
||||
"en": "英语",
|
||||
"ja": "日语",
|
||||
"ru": "俄语",
|
||||
"zh_CN": "中文简体",
|
||||
"theme": "主题",
|
||||
"themeDesc": "设置深色模式,调整色彩",
|
||||
@@ -121,7 +123,6 @@
|
||||
"project": "项目",
|
||||
"core": "内核",
|
||||
"tabAnimation": "选项卡动画",
|
||||
"tabAnimationDesc": "开启后,主页选项卡将添加切换动画",
|
||||
"desc": "基于ClashMeta的多平台代理客户端,简单易用,开源无广告。",
|
||||
"startVpn": "正在启动VPN...",
|
||||
"stopVpn": "正在停止VPN...",
|
||||
@@ -167,7 +168,7 @@
|
||||
"externalControllerDesc": "开启后将可以通过9090端口控制Clash内核",
|
||||
"ipv6Desc": "开启后将可以接收IPv6流量",
|
||||
"app": "应用",
|
||||
"general": "基础",
|
||||
"general": "常规",
|
||||
"vpnSystemProxyDesc": "为VpnService附加HTTP代理",
|
||||
"systemProxyDesc": "设置系统代理",
|
||||
"unifiedDelay": "统一延迟",
|
||||
@@ -179,7 +180,6 @@
|
||||
"requests": "请求",
|
||||
"requestsDesc": "查看最近请求记录",
|
||||
"findProcessMode": "查找进程",
|
||||
"findProcessModeDesc": "开启后存在闪退风险",
|
||||
"init": "初始化",
|
||||
"infiniteTime": "长期有效",
|
||||
"expirationTime": "到期时间",
|
||||
@@ -220,7 +220,7 @@
|
||||
"onlyStatisticsProxy": "仅统计代理",
|
||||
"onlyStatisticsProxyDesc": "开启后,将只统计代理流量",
|
||||
"deleteProfileTip": "确定要删除当前配置吗?",
|
||||
"prueBlackMode": "纯黑模式",
|
||||
"pureBlackMode": "纯黑模式",
|
||||
"keepAliveIntervalDesc": "TCP保持活动间隔",
|
||||
"entries": "个条目",
|
||||
"local": "本地",
|
||||
@@ -247,7 +247,6 @@
|
||||
"stop": "暂停",
|
||||
"appDesc": "处理应用相关设置",
|
||||
"vpnDesc": "修改VPN相关设置",
|
||||
"generalDesc": "覆写基础设置",
|
||||
"dnsDesc": "更新DNS相关设置",
|
||||
"key": "键",
|
||||
"value": "值",
|
||||
@@ -343,5 +342,35 @@
|
||||
"copyLink": "复制链接",
|
||||
"exportFile": "导出文件",
|
||||
"cacheCorrupt": "缓存已损坏,是否清空?",
|
||||
"detectionTip": "依赖第三方api仅供参考"
|
||||
"detectionTip": "依赖第三方api,仅供参考",
|
||||
"listen": "监听",
|
||||
"keyExists": "当前键已存在",
|
||||
"valueExists": "当前值已存在",
|
||||
"undo": "撤销",
|
||||
"redo": "重做",
|
||||
"none": "无",
|
||||
"basicConfig": "基本配置",
|
||||
"basicConfigDesc": "全局修改基本配置",
|
||||
"selectedCountTitle": "已选择 {count} 项",
|
||||
"addRule": "添加规则",
|
||||
"ruleProviderEmptyTip": "规则提供者不能为空",
|
||||
"ruleName": "规则名称",
|
||||
"content": "内容",
|
||||
"contentEmptyTip": "内容不能为空",
|
||||
"subRule": "子规则",
|
||||
"subRuleEmptyTip": "子规则内容不能为空",
|
||||
"ruleTarget": "规则目标",
|
||||
"ruleTargetEmptyTip": "规则目标不能为空",
|
||||
"sourceIp": "源IP",
|
||||
"noResolve": "不解析IP",
|
||||
"getOriginRules": "获取原始规则",
|
||||
"overrideOriginRules": "覆盖原始规则",
|
||||
"addedOriginRules": "附加到原始规则",
|
||||
"enableOverride": "启用覆写",
|
||||
"deleteRuleTip": "确定要删除选中的规则吗?",
|
||||
"saveChanges": "是否保存更改?",
|
||||
"generalDesc": "修改通用设置",
|
||||
"findProcessModeDesc": "开启后会有一定性能损耗",
|
||||
"tabAnimationDesc": "仅在移动视图中有效",
|
||||
"saveTip": "确定要保存吗?"
|
||||
}
|
||||
|
||||
@@ -17,11 +17,15 @@ import 'package:intl/message_lookup_by_library.dart';
|
||||
import 'package:intl/src/intl_helpers.dart';
|
||||
|
||||
import 'messages_en.dart' as messages_en;
|
||||
import 'messages_ja.dart' as messages_ja;
|
||||
import 'messages_ru.dart' as messages_ru;
|
||||
import 'messages_zh_CN.dart' as messages_zh_cn;
|
||||
|
||||
typedef Future<dynamic> LibraryLoader();
|
||||
Map<String, LibraryLoader> _deferredLibraries = {
|
||||
'en': () => new SynchronousFuture(null),
|
||||
'ja': () => new SynchronousFuture(null),
|
||||
'ru': () => new SynchronousFuture(null),
|
||||
'zh_CN': () => new SynchronousFuture(null),
|
||||
};
|
||||
|
||||
@@ -29,6 +33,10 @@ MessageLookupByLibrary? _findExact(String localeName) {
|
||||
switch (localeName) {
|
||||
case 'en':
|
||||
return messages_en.messages;
|
||||
case 'ja':
|
||||
return messages_ja.messages;
|
||||
case 'ru':
|
||||
return messages_ru.messages;
|
||||
case 'zh_CN':
|
||||
return messages_zh_cn.messages;
|
||||
default:
|
||||
|
||||
@@ -20,6 +20,8 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'en';
|
||||
|
||||
static String m0(count) => "${count} items have been selected";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"about": MessageLookupByLibrary.simpleMessage("About"),
|
||||
@@ -44,6 +46,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
|
||||
"action_view": MessageLookupByLibrary.simpleMessage("Show/Hide"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("Add"),
|
||||
"addRule": MessageLookupByLibrary.simpleMessage("Add rule"),
|
||||
"addedOriginRules": MessageLookupByLibrary.simpleMessage(
|
||||
"Attach on the original rules",
|
||||
),
|
||||
"address": MessageLookupByLibrary.simpleMessage("Address"),
|
||||
"addressHelp": MessageLookupByLibrary.simpleMessage(
|
||||
"WebDAV server address",
|
||||
@@ -89,7 +95,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Auto check for updates when the app starts",
|
||||
),
|
||||
"autoCloseConnections": MessageLookupByLibrary.simpleMessage(
|
||||
"Auto lose connections",
|
||||
"Auto close connections",
|
||||
),
|
||||
"autoCloseConnectionsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Auto close connections after change node",
|
||||
@@ -114,6 +120,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Sync data via WebDAV or file",
|
||||
),
|
||||
"backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"),
|
||||
"basicConfig": MessageLookupByLibrary.simpleMessage("Basic configuration"),
|
||||
"basicConfigDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Modify the basic configuration globally",
|
||||
),
|
||||
"bind": MessageLookupByLibrary.simpleMessage("Bind"),
|
||||
"blacklistMode": MessageLookupByLibrary.simpleMessage("Blacklist mode"),
|
||||
"bypassDomain": MessageLookupByLibrary.simpleMessage("Bypass domain"),
|
||||
@@ -149,6 +159,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"View current connections data",
|
||||
),
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
|
||||
"content": MessageLookupByLibrary.simpleMessage("Content"),
|
||||
"contentEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Content cannot be empty",
|
||||
),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
|
||||
"copyEnvVar": MessageLookupByLibrary.simpleMessage(
|
||||
"Copying environment variables",
|
||||
@@ -177,6 +191,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"deleteProfileTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Sure you want to delete the current profile?",
|
||||
),
|
||||
"deleteRuleTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Are you sure you want to delete the selected rule?",
|
||||
),
|
||||
"desc": MessageLookupByLibrary.simpleMessage(
|
||||
"A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.",
|
||||
),
|
||||
@@ -205,6 +222,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"download": MessageLookupByLibrary.simpleMessage("Download"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("Edit"),
|
||||
"en": MessageLookupByLibrary.simpleMessage("English"),
|
||||
"enableOverride": MessageLookupByLibrary.simpleMessage("Enable override"),
|
||||
"entries": MessageLookupByLibrary.simpleMessage(" entries"),
|
||||
"exclude": MessageLookupByLibrary.simpleMessage("Hidden from recent tasks"),
|
||||
"excludeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -243,13 +261,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"findProcessMode": MessageLookupByLibrary.simpleMessage("Find process"),
|
||||
"findProcessModeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"There is a risk of flashback after opening",
|
||||
"There is a certain performance loss after opening",
|
||||
),
|
||||
"fontFamily": MessageLookupByLibrary.simpleMessage("FontFamily"),
|
||||
"fourColumns": MessageLookupByLibrary.simpleMessage("Four columns"),
|
||||
"general": MessageLookupByLibrary.simpleMessage("General"),
|
||||
"generalDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Overwrite general settings",
|
||||
"Modify general settings",
|
||||
),
|
||||
"geoData": MessageLookupByLibrary.simpleMessage("GeoData"),
|
||||
"geodataLoader": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -259,6 +277,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Enabling will use the Geo low memory loader",
|
||||
),
|
||||
"geoipCode": MessageLookupByLibrary.simpleMessage("Geoip code"),
|
||||
"getOriginRules": MessageLookupByLibrary.simpleMessage(
|
||||
"Get original rules",
|
||||
),
|
||||
"global": MessageLookupByLibrary.simpleMessage("Global"),
|
||||
"go": MessageLookupByLibrary.simpleMessage("Go"),
|
||||
"goDownload": MessageLookupByLibrary.simpleMessage("Go to download"),
|
||||
@@ -296,15 +317,20 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow IPv6 inbound",
|
||||
),
|
||||
"ja": MessageLookupByLibrary.simpleMessage("Japanese"),
|
||||
"just": MessageLookupByLibrary.simpleMessage("Just"),
|
||||
"keepAliveIntervalDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Tcp keep alive interval",
|
||||
),
|
||||
"key": MessageLookupByLibrary.simpleMessage("Key"),
|
||||
"keyExists": MessageLookupByLibrary.simpleMessage(
|
||||
"The current key already exists",
|
||||
),
|
||||
"language": MessageLookupByLibrary.simpleMessage("Language"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("Layout"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("Light"),
|
||||
"list": MessageLookupByLibrary.simpleMessage("List"),
|
||||
"listen": MessageLookupByLibrary.simpleMessage("Listen"),
|
||||
"local": MessageLookupByLibrary.simpleMessage("Local"),
|
||||
"localBackupDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Backup local data to local",
|
||||
@@ -364,6 +390,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Please create a profile or add a valid profile",
|
||||
),
|
||||
"noResolve": MessageLookupByLibrary.simpleMessage("No resolve IP"),
|
||||
"none": MessageLookupByLibrary.simpleMessage("none"),
|
||||
"notEmpty": MessageLookupByLibrary.simpleMessage("Cannot be empty"),
|
||||
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
|
||||
"The current proxy group cannot be selected.",
|
||||
@@ -405,6 +433,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Turning it on will override the DNS options in the profile",
|
||||
),
|
||||
"overrideOriginRules": MessageLookupByLibrary.simpleMessage(
|
||||
"Override the original rule",
|
||||
),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Password"),
|
||||
"passwordTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Password cannot be empty",
|
||||
@@ -470,7 +501,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Set the Clash listening port",
|
||||
),
|
||||
"proxyProviders": MessageLookupByLibrary.simpleMessage("Proxy providers"),
|
||||
"prueBlackMode": MessageLookupByLibrary.simpleMessage("Prue black mode"),
|
||||
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Pure black mode"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Scan QR code to obtain profile",
|
||||
@@ -481,6 +512,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Only recovery profiles",
|
||||
),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("Recovery success"),
|
||||
"redo": MessageLookupByLibrary.simpleMessage("redo"),
|
||||
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
|
||||
"remote": MessageLookupByLibrary.simpleMessage("Remote"),
|
||||
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -513,13 +545,29 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Bypass private route address",
|
||||
),
|
||||
"routeMode_config": MessageLookupByLibrary.simpleMessage("Use config"),
|
||||
"ru": MessageLookupByLibrary.simpleMessage("Russian"),
|
||||
"rule": MessageLookupByLibrary.simpleMessage("Rule"),
|
||||
"ruleName": MessageLookupByLibrary.simpleMessage("Rule name"),
|
||||
"ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Rule provider cannot be empty",
|
||||
),
|
||||
"ruleProviders": MessageLookupByLibrary.simpleMessage("Rule providers"),
|
||||
"ruleTarget": MessageLookupByLibrary.simpleMessage("Rule target"),
|
||||
"ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Rule target cannot be empty",
|
||||
),
|
||||
"save": MessageLookupByLibrary.simpleMessage("Save"),
|
||||
"saveChanges": MessageLookupByLibrary.simpleMessage(
|
||||
"Do you want to save the changes?",
|
||||
),
|
||||
"saveTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Are you sure you want to save?",
|
||||
),
|
||||
"search": MessageLookupByLibrary.simpleMessage("Search"),
|
||||
"seconds": MessageLookupByLibrary.simpleMessage("Seconds"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("Select all"),
|
||||
"selected": MessageLookupByLibrary.simpleMessage("Selected"),
|
||||
"selectedCountTitle": m0,
|
||||
"settings": MessageLookupByLibrary.simpleMessage("Settings"),
|
||||
"show": MessageLookupByLibrary.simpleMessage("Show"),
|
||||
"shrink": MessageLookupByLibrary.simpleMessage("Shrink"),
|
||||
@@ -530,6 +578,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"size": MessageLookupByLibrary.simpleMessage("Size"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("Sort"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("Source"),
|
||||
"sourceIp": MessageLookupByLibrary.simpleMessage("Source IP"),
|
||||
"stackMode": MessageLookupByLibrary.simpleMessage("Stack mode"),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("Standard"),
|
||||
"start": MessageLookupByLibrary.simpleMessage("Start"),
|
||||
@@ -541,6 +590,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"stop": MessageLookupByLibrary.simpleMessage("Stop"),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("Style"),
|
||||
"subRule": MessageLookupByLibrary.simpleMessage("Sub rule"),
|
||||
"subRuleEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Sub rule content cannot be empty",
|
||||
),
|
||||
"submit": MessageLookupByLibrary.simpleMessage("Submit"),
|
||||
"sync": MessageLookupByLibrary.simpleMessage("Sync"),
|
||||
"system": MessageLookupByLibrary.simpleMessage("System"),
|
||||
@@ -552,7 +605,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"tab": MessageLookupByLibrary.simpleMessage("Tab"),
|
||||
"tabAnimation": MessageLookupByLibrary.simpleMessage("Tab animation"),
|
||||
"tabAnimationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"When enabled, the home tab will add a toggle animation",
|
||||
"Effective only in mobile view",
|
||||
),
|
||||
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP concurrent"),
|
||||
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -580,6 +633,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"unable to update current profile",
|
||||
),
|
||||
"undo": MessageLookupByLibrary.simpleMessage("undo"),
|
||||
"unifiedDelay": MessageLookupByLibrary.simpleMessage("Unified delay"),
|
||||
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Remove extra delays such as handshaking",
|
||||
@@ -594,6 +648,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"useHosts": MessageLookupByLibrary.simpleMessage("Use hosts"),
|
||||
"useSystemHosts": MessageLookupByLibrary.simpleMessage("Use system hosts"),
|
||||
"value": MessageLookupByLibrary.simpleMessage("Value"),
|
||||
"valueExists": MessageLookupByLibrary.simpleMessage(
|
||||
"The current value already exists",
|
||||
),
|
||||
"view": MessageLookupByLibrary.simpleMessage("View"),
|
||||
"vpnDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Modify VPN related settings",
|
||||
|
||||
500
lib/l10n/intl/messages_ja.dart
Normal file
500
lib/l10n/intl/messages_ja.dart
Normal file
@@ -0,0 +1,500 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a ja locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'ja';
|
||||
|
||||
static String m0(count) => "${count} 項目が選択されています";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"about": MessageLookupByLibrary.simpleMessage("について"),
|
||||
"accessControl": MessageLookupByLibrary.simpleMessage("アクセス制御"),
|
||||
"accessControlAllowDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"選択したアプリのみVPNを許可",
|
||||
),
|
||||
"accessControlDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"アプリケーションのプロキシアクセスを設定",
|
||||
),
|
||||
"accessControlNotAllowDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"選択したアプリをVPNから除外",
|
||||
),
|
||||
"account": MessageLookupByLibrary.simpleMessage("アカウント"),
|
||||
"accountTip": MessageLookupByLibrary.simpleMessage("アカウントは必須です"),
|
||||
"action": MessageLookupByLibrary.simpleMessage("アクション"),
|
||||
"action_mode": MessageLookupByLibrary.simpleMessage("モード切替"),
|
||||
"action_proxy": MessageLookupByLibrary.simpleMessage("システムプロキシ"),
|
||||
"action_start": MessageLookupByLibrary.simpleMessage("開始/停止"),
|
||||
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
|
||||
"action_view": MessageLookupByLibrary.simpleMessage("表示/非表示"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("追加"),
|
||||
"addRule": MessageLookupByLibrary.simpleMessage("ルールを追加"),
|
||||
"addedOriginRules": MessageLookupByLibrary.simpleMessage("元のルールに追加"),
|
||||
"address": MessageLookupByLibrary.simpleMessage("アドレス"),
|
||||
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAVサーバーアドレス"),
|
||||
"addressTip": MessageLookupByLibrary.simpleMessage("有効なWebDAVアドレスを入力"),
|
||||
"adminAutoLaunch": MessageLookupByLibrary.simpleMessage("管理者自動起動"),
|
||||
"adminAutoLaunchDesc": MessageLookupByLibrary.simpleMessage("管理者モードで起動"),
|
||||
"ago": MessageLookupByLibrary.simpleMessage("前"),
|
||||
"agree": MessageLookupByLibrary.simpleMessage("同意"),
|
||||
"allApps": MessageLookupByLibrary.simpleMessage("全アプリ"),
|
||||
"allowBypass": MessageLookupByLibrary.simpleMessage("アプリがVPNをバイパスすることを許可"),
|
||||
"allowBypassDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"有効化すると一部アプリがVPNをバイパス",
|
||||
),
|
||||
"allowLan": MessageLookupByLibrary.simpleMessage("LANを許可"),
|
||||
"allowLanDesc": MessageLookupByLibrary.simpleMessage("LAN経由でのプロキシアクセスを許可"),
|
||||
"app": MessageLookupByLibrary.simpleMessage("アプリ"),
|
||||
"appAccessControl": MessageLookupByLibrary.simpleMessage("アプリアクセス制御"),
|
||||
"appDesc": MessageLookupByLibrary.simpleMessage("アプリ関連設定の処理"),
|
||||
"application": MessageLookupByLibrary.simpleMessage("アプリケーション"),
|
||||
"applicationDesc": MessageLookupByLibrary.simpleMessage("アプリ関連設定を変更"),
|
||||
"auto": MessageLookupByLibrary.simpleMessage("自動"),
|
||||
"autoCheckUpdate": MessageLookupByLibrary.simpleMessage("自動更新チェック"),
|
||||
"autoCheckUpdateDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"起動時に更新を自動チェック",
|
||||
),
|
||||
"autoCloseConnections": MessageLookupByLibrary.simpleMessage("接続を自動閉じる"),
|
||||
"autoCloseConnectionsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"ノード変更後に接続を自動閉じる",
|
||||
),
|
||||
"autoLaunch": MessageLookupByLibrary.simpleMessage("自動起動"),
|
||||
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage("システムの自動起動に従う"),
|
||||
"autoRun": MessageLookupByLibrary.simpleMessage("自動実行"),
|
||||
"autoRunDesc": MessageLookupByLibrary.simpleMessage("アプリ起動時に自動実行"),
|
||||
"autoUpdate": MessageLookupByLibrary.simpleMessage("自動更新"),
|
||||
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自動更新間隔(分)"),
|
||||
"backup": MessageLookupByLibrary.simpleMessage("バックアップ"),
|
||||
"backupAndRecovery": MessageLookupByLibrary.simpleMessage("バックアップと復元"),
|
||||
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"WebDAVまたはファイルでデータを同期",
|
||||
),
|
||||
"backupSuccess": MessageLookupByLibrary.simpleMessage("バックアップ成功"),
|
||||
"basicConfig": MessageLookupByLibrary.simpleMessage("基本設定"),
|
||||
"basicConfigDesc": MessageLookupByLibrary.simpleMessage("基本設定をグローバルに変更"),
|
||||
"bind": MessageLookupByLibrary.simpleMessage("バインド"),
|
||||
"blacklistMode": MessageLookupByLibrary.simpleMessage("ブラックリストモード"),
|
||||
"bypassDomain": MessageLookupByLibrary.simpleMessage("バイパスドメイン"),
|
||||
"bypassDomainDesc": MessageLookupByLibrary.simpleMessage("システムプロキシ有効時のみ適用"),
|
||||
"cacheCorrupt": MessageLookupByLibrary.simpleMessage(
|
||||
"キャッシュが破損しています。クリアしますか?",
|
||||
),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("キャンセル"),
|
||||
"cancelFilterSystemApp": MessageLookupByLibrary.simpleMessage(
|
||||
"システムアプリの除外を解除",
|
||||
),
|
||||
"cancelSelectAll": MessageLookupByLibrary.simpleMessage("全選択解除"),
|
||||
"checkError": MessageLookupByLibrary.simpleMessage("確認エラー"),
|
||||
"checkUpdate": MessageLookupByLibrary.simpleMessage("更新を確認"),
|
||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage("アプリは最新版です"),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("確認中..."),
|
||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("クリップボードにエクスポート"),
|
||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("クリップボードからインポート"),
|
||||
"columns": MessageLookupByLibrary.simpleMessage("列"),
|
||||
"compatible": MessageLookupByLibrary.simpleMessage("互換モード"),
|
||||
"compatibleDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"有効化すると一部機能を失いますが、Clashの完全サポートを獲得",
|
||||
),
|
||||
"confirm": MessageLookupByLibrary.simpleMessage("確認"),
|
||||
"connections": MessageLookupByLibrary.simpleMessage("接続"),
|
||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("現在の接続データを表示"),
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("接続性:"),
|
||||
"content": MessageLookupByLibrary.simpleMessage("内容"),
|
||||
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容は必須です"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("コピー"),
|
||||
"copyEnvVar": MessageLookupByLibrary.simpleMessage("環境変数をコピー"),
|
||||
"copyLink": MessageLookupByLibrary.simpleMessage("リンクをコピー"),
|
||||
"copySuccess": MessageLookupByLibrary.simpleMessage("コピー成功"),
|
||||
"core": MessageLookupByLibrary.simpleMessage("コア"),
|
||||
"coreInfo": MessageLookupByLibrary.simpleMessage("コア情報"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("国"),
|
||||
"create": MessageLookupByLibrary.simpleMessage("作成"),
|
||||
"cut": MessageLookupByLibrary.simpleMessage("切り取り"),
|
||||
"dark": MessageLookupByLibrary.simpleMessage("ダーク"),
|
||||
"dashboard": MessageLookupByLibrary.simpleMessage("ダッシュボード"),
|
||||
"days": MessageLookupByLibrary.simpleMessage("日"),
|
||||
"defaultNameserver": MessageLookupByLibrary.simpleMessage("デフォルトネームサーバー"),
|
||||
"defaultNameserverDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"DNSサーバーの解決用",
|
||||
),
|
||||
"defaultSort": MessageLookupByLibrary.simpleMessage("デフォルト順"),
|
||||
"defaultText": MessageLookupByLibrary.simpleMessage("デフォルト"),
|
||||
"delay": MessageLookupByLibrary.simpleMessage("遅延"),
|
||||
"delaySort": MessageLookupByLibrary.simpleMessage("遅延順"),
|
||||
"delete": MessageLookupByLibrary.simpleMessage("削除"),
|
||||
"deleteProfileTip": MessageLookupByLibrary.simpleMessage(
|
||||
"現在のプロファイルを削除しますか?",
|
||||
),
|
||||
"deleteRuleTip": MessageLookupByLibrary.simpleMessage("選択したルールを削除しますか?"),
|
||||
"desc": MessageLookupByLibrary.simpleMessage(
|
||||
"ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。",
|
||||
),
|
||||
"detectionTip": MessageLookupByLibrary.simpleMessage("サードパーティAPIに依存(参考値)"),
|
||||
"direct": MessageLookupByLibrary.simpleMessage("ダイレクト"),
|
||||
"disclaimer": MessageLookupByLibrary.simpleMessage("免責事項"),
|
||||
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"本ソフトウェアは学習交流や科学研究などの非営利目的でのみ使用されます。商用利用は厳禁です。いかなる商用活動も本ソフトウェアとは無関係です。",
|
||||
),
|
||||
"discoverNewVersion": MessageLookupByLibrary.simpleMessage("新バージョンを発見"),
|
||||
"discovery": MessageLookupByLibrary.simpleMessage("新しいバージョンを発見"),
|
||||
"dnsDesc": MessageLookupByLibrary.simpleMessage("DNS関連設定の更新"),
|
||||
"dnsMode": MessageLookupByLibrary.simpleMessage("DNSモード"),
|
||||
"doYouWantToPass": MessageLookupByLibrary.simpleMessage("通過させますか?"),
|
||||
"domain": MessageLookupByLibrary.simpleMessage("ドメイン"),
|
||||
"download": MessageLookupByLibrary.simpleMessage("ダウンロード"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("編集"),
|
||||
"en": MessageLookupByLibrary.simpleMessage("英語"),
|
||||
"enableOverride": MessageLookupByLibrary.simpleMessage("上書きを有効化"),
|
||||
"entries": MessageLookupByLibrary.simpleMessage(" エントリ"),
|
||||
"exclude": MessageLookupByLibrary.simpleMessage("最近のタスクから非表示"),
|
||||
"excludeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"アプリがバックグラウンド時に最近のタスクから非表示",
|
||||
),
|
||||
"exit": MessageLookupByLibrary.simpleMessage("終了"),
|
||||
"expand": MessageLookupByLibrary.simpleMessage("標準"),
|
||||
"expirationTime": MessageLookupByLibrary.simpleMessage("有効期限"),
|
||||
"exportFile": MessageLookupByLibrary.simpleMessage("ファイルをエクスポート"),
|
||||
"exportLogs": MessageLookupByLibrary.simpleMessage("ログをエクスポート"),
|
||||
"exportSuccess": MessageLookupByLibrary.simpleMessage("エクスポート成功"),
|
||||
"externalController": MessageLookupByLibrary.simpleMessage("外部コントローラー"),
|
||||
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"有効化するとClashコアをポート9090で制御可能",
|
||||
),
|
||||
"externalLink": MessageLookupByLibrary.simpleMessage("外部リンク"),
|
||||
"externalResources": MessageLookupByLibrary.simpleMessage("外部リソース"),
|
||||
"fakeipFilter": MessageLookupByLibrary.simpleMessage("Fakeipフィルター"),
|
||||
"fakeipRange": MessageLookupByLibrary.simpleMessage("Fakeip範囲"),
|
||||
"fallback": MessageLookupByLibrary.simpleMessage("フォールバック"),
|
||||
"fallbackDesc": MessageLookupByLibrary.simpleMessage("通常はオフショアDNSを使用"),
|
||||
"fallbackFilter": MessageLookupByLibrary.simpleMessage("フォールバックフィルター"),
|
||||
"file": MessageLookupByLibrary.simpleMessage("ファイル"),
|
||||
"fileDesc": MessageLookupByLibrary.simpleMessage("プロファイルを直接アップロード"),
|
||||
"fileIsUpdate": MessageLookupByLibrary.simpleMessage(
|
||||
"ファイルが変更されました。保存しますか?",
|
||||
),
|
||||
"filterSystemApp": MessageLookupByLibrary.simpleMessage("システムアプリを除外"),
|
||||
"findProcessMode": MessageLookupByLibrary.simpleMessage("プロセス検出"),
|
||||
"findProcessModeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"有効化するとパフォーマンスが若干低下します",
|
||||
),
|
||||
"fontFamily": MessageLookupByLibrary.simpleMessage("フォントファミリー"),
|
||||
"fourColumns": MessageLookupByLibrary.simpleMessage("4列"),
|
||||
"general": MessageLookupByLibrary.simpleMessage("一般"),
|
||||
"generalDesc": MessageLookupByLibrary.simpleMessage("一般設定を変更"),
|
||||
"geoData": MessageLookupByLibrary.simpleMessage("地域データ"),
|
||||
"geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低メモリモード"),
|
||||
"geodataLoaderDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"有効化するとGeo低メモリローダーを使用",
|
||||
),
|
||||
"geoipCode": MessageLookupByLibrary.simpleMessage("GeoIPコード"),
|
||||
"getOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを取得"),
|
||||
"global": MessageLookupByLibrary.simpleMessage("グローバル"),
|
||||
"go": MessageLookupByLibrary.simpleMessage("移動"),
|
||||
"goDownload": MessageLookupByLibrary.simpleMessage("ダウンロードへ"),
|
||||
"hasCacheChange": MessageLookupByLibrary.simpleMessage("変更をキャッシュしますか?"),
|
||||
"hostsDesc": MessageLookupByLibrary.simpleMessage("ホストを追加"),
|
||||
"hotkeyConflict": MessageLookupByLibrary.simpleMessage("ホットキー競合"),
|
||||
"hotkeyManagement": MessageLookupByLibrary.simpleMessage("ホットキー管理"),
|
||||
"hotkeyManagementDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"キーボードでアプリを制御",
|
||||
),
|
||||
"hours": MessageLookupByLibrary.simpleMessage("時間"),
|
||||
"icon": MessageLookupByLibrary.simpleMessage("アイコン"),
|
||||
"iconConfiguration": MessageLookupByLibrary.simpleMessage("アイコン設定"),
|
||||
"iconStyle": MessageLookupByLibrary.simpleMessage("アイコンスタイル"),
|
||||
"importFromURL": MessageLookupByLibrary.simpleMessage("URLからインポート"),
|
||||
"infiniteTime": MessageLookupByLibrary.simpleMessage("長期有効"),
|
||||
"init": MessageLookupByLibrary.simpleMessage("初期化"),
|
||||
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("正しいホットキーを入力"),
|
||||
"intelligentSelected": MessageLookupByLibrary.simpleMessage("インテリジェント選択"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"),
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"),
|
||||
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("IPv6インバウンドを許可"),
|
||||
"ja": MessageLookupByLibrary.simpleMessage("日本語"),
|
||||
"just": MessageLookupByLibrary.simpleMessage("たった今"),
|
||||
"keepAliveIntervalDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"TCPキープアライブ間隔",
|
||||
),
|
||||
"key": MessageLookupByLibrary.simpleMessage("キー"),
|
||||
"keyExists": MessageLookupByLibrary.simpleMessage("現在のキーは既に存在します"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("言語"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("レイアウト"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("ライト"),
|
||||
"list": MessageLookupByLibrary.simpleMessage("リスト"),
|
||||
"listen": MessageLookupByLibrary.simpleMessage("リスン"),
|
||||
"local": MessageLookupByLibrary.simpleMessage("ローカル"),
|
||||
"localBackupDesc": MessageLookupByLibrary.simpleMessage("ローカルにデータをバックアップ"),
|
||||
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("ファイルからデータを復元"),
|
||||
"logLevel": MessageLookupByLibrary.simpleMessage("ログレベル"),
|
||||
"logcat": MessageLookupByLibrary.simpleMessage("ログキャット"),
|
||||
"logcatDesc": MessageLookupByLibrary.simpleMessage("無効化するとログエントリを非表示"),
|
||||
"logs": MessageLookupByLibrary.simpleMessage("ログ"),
|
||||
"logsDesc": MessageLookupByLibrary.simpleMessage("ログキャプチャ記録"),
|
||||
"loopback": MessageLookupByLibrary.simpleMessage("ループバック解除ツール"),
|
||||
"loopbackDesc": MessageLookupByLibrary.simpleMessage("UWPループバック解除用"),
|
||||
"loose": MessageLookupByLibrary.simpleMessage("疎"),
|
||||
"memoryInfo": MessageLookupByLibrary.simpleMessage("メモリ情報"),
|
||||
"min": MessageLookupByLibrary.simpleMessage("最小化"),
|
||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("終了時に最小化"),
|
||||
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"システムの終了イベントを変更",
|
||||
),
|
||||
"minutes": MessageLookupByLibrary.simpleMessage("分"),
|
||||
"mode": MessageLookupByLibrary.simpleMessage("モード"),
|
||||
"months": MessageLookupByLibrary.simpleMessage("月"),
|
||||
"more": MessageLookupByLibrary.simpleMessage("詳細"),
|
||||
"name": MessageLookupByLibrary.simpleMessage("名前"),
|
||||
"nameSort": MessageLookupByLibrary.simpleMessage("名前順"),
|
||||
"nameserver": MessageLookupByLibrary.simpleMessage("ネームサーバー"),
|
||||
"nameserverDesc": MessageLookupByLibrary.simpleMessage("ドメイン解決用"),
|
||||
"nameserverPolicy": MessageLookupByLibrary.simpleMessage("ネームサーバーポリシー"),
|
||||
"nameserverPolicyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"対応するネームサーバーポリシーを指定",
|
||||
),
|
||||
"network": MessageLookupByLibrary.simpleMessage("ネットワーク"),
|
||||
"networkDesc": MessageLookupByLibrary.simpleMessage("ネットワーク関連設定の変更"),
|
||||
"networkDetection": MessageLookupByLibrary.simpleMessage("ネットワーク検出"),
|
||||
"networkSpeed": MessageLookupByLibrary.simpleMessage("ネットワーク速度"),
|
||||
"noData": MessageLookupByLibrary.simpleMessage("データなし"),
|
||||
"noHotKey": MessageLookupByLibrary.simpleMessage("ホットキーなし"),
|
||||
"noIcon": MessageLookupByLibrary.simpleMessage("なし"),
|
||||
"noInfo": MessageLookupByLibrary.simpleMessage("情報なし"),
|
||||
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("追加情報なし"),
|
||||
"noNetwork": MessageLookupByLibrary.simpleMessage("ネットワークなし"),
|
||||
"noProxy": MessageLookupByLibrary.simpleMessage("プロキシなし"),
|
||||
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"プロファイルを作成するか、有効なプロファイルを追加してください",
|
||||
),
|
||||
"noResolve": MessageLookupByLibrary.simpleMessage("IPを解決しない"),
|
||||
"none": MessageLookupByLibrary.simpleMessage("なし"),
|
||||
"notEmpty": MessageLookupByLibrary.simpleMessage("空欄不可"),
|
||||
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
|
||||
"現在のプロキシグループは選択できません",
|
||||
),
|
||||
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage("接続なし"),
|
||||
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage("コア情報を取得できません"),
|
||||
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("ログがありません"),
|
||||
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"プロファイルがありません。追加してください",
|
||||
),
|
||||
"nullProxies": MessageLookupByLibrary.simpleMessage("プロキシなし"),
|
||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("リクエストなし"),
|
||||
"oneColumn": MessageLookupByLibrary.simpleMessage("1列"),
|
||||
"onlyIcon": MessageLookupByLibrary.simpleMessage("アイコンのみ"),
|
||||
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("サードパーティアプリのみ"),
|
||||
"onlyStatisticsProxy": MessageLookupByLibrary.simpleMessage("プロキシのみ統計"),
|
||||
"onlyStatisticsProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"有効化するとプロキシトラフィックのみ統計",
|
||||
),
|
||||
"options": MessageLookupByLibrary.simpleMessage("オプション"),
|
||||
"other": MessageLookupByLibrary.simpleMessage("その他"),
|
||||
"otherContributors": MessageLookupByLibrary.simpleMessage("その他の貢献者"),
|
||||
"outboundMode": MessageLookupByLibrary.simpleMessage("アウトバウンドモード"),
|
||||
"override": MessageLookupByLibrary.simpleMessage("上書き"),
|
||||
"overrideDesc": MessageLookupByLibrary.simpleMessage("プロキシ関連設定を上書き"),
|
||||
"overrideDns": MessageLookupByLibrary.simpleMessage("DNS上書き"),
|
||||
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"有効化するとプロファイルのDNS設定を上書き",
|
||||
),
|
||||
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを上書き"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("パスワード"),
|
||||
"passwordTip": MessageLookupByLibrary.simpleMessage("パスワードは必須です"),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("貼り付け"),
|
||||
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage(
|
||||
"WebDAVをバインドしてください",
|
||||
),
|
||||
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
|
||||
"管理者パスワードを入力",
|
||||
),
|
||||
"pleaseUploadFile": MessageLookupByLibrary.simpleMessage(
|
||||
"ファイルをアップロードしてください",
|
||||
),
|
||||
"pleaseUploadValidQrcode": MessageLookupByLibrary.simpleMessage(
|
||||
"有効なQRコードをアップロードしてください",
|
||||
),
|
||||
"port": MessageLookupByLibrary.simpleMessage("ポート"),
|
||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage("DOHのHTTP/3を優先使用"),
|
||||
"pressKeyboard": MessageLookupByLibrary.simpleMessage("キーボードを押してください"),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("プレビュー"),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("プロファイル"),
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage("有効な間隔形式を入力してください"),
|
||||
"profileAutoUpdateIntervalNullValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage("自動更新間隔を入力してください"),
|
||||
"profileHasUpdate": MessageLookupByLibrary.simpleMessage(
|
||||
"プロファイルが変更されました。自動更新を無効化しますか?",
|
||||
),
|
||||
"profileNameNullValidationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"プロファイル名を入力してください",
|
||||
),
|
||||
"profileParseErrorDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"プロファイル解析エラー",
|
||||
),
|
||||
"profileUrlInvalidValidationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"有効なプロファイルURLを入力してください",
|
||||
),
|
||||
"profileUrlNullValidationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"プロファイルURLを入力してください",
|
||||
),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("プロファイル一覧"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("プロファイルの並び替え"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("プロジェクト"),
|
||||
"providers": MessageLookupByLibrary.simpleMessage("プロバイダー"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("プロキシ"),
|
||||
"proxiesSetting": MessageLookupByLibrary.simpleMessage("プロキシ設定"),
|
||||
"proxyGroup": MessageLookupByLibrary.simpleMessage("プロキシグループ"),
|
||||
"proxyNameserver": MessageLookupByLibrary.simpleMessage("プロキシネームサーバー"),
|
||||
"proxyNameserverDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"プロキシノード解決用ドメイン",
|
||||
),
|
||||
"proxyPort": MessageLookupByLibrary.simpleMessage("プロキシポート"),
|
||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("Clashのリスニングポートを設定"),
|
||||
"proxyProviders": MessageLookupByLibrary.simpleMessage("プロキシプロバイダー"),
|
||||
"pureBlackMode": MessageLookupByLibrary.simpleMessage("純黒モード"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("QRコード"),
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("QRコードをスキャンしてプロファイルを取得"),
|
||||
"recovery": MessageLookupByLibrary.simpleMessage("復元"),
|
||||
"recoveryAll": MessageLookupByLibrary.simpleMessage("全データ復元"),
|
||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("プロファイルのみ復元"),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("復元成功"),
|
||||
"redo": MessageLookupByLibrary.simpleMessage("やり直す"),
|
||||
"regExp": MessageLookupByLibrary.simpleMessage("正規表現"),
|
||||
"remote": MessageLookupByLibrary.simpleMessage("リモート"),
|
||||
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"WebDAVにデータをバックアップ",
|
||||
),
|
||||
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"WebDAVからデータを復元",
|
||||
),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("削除"),
|
||||
"requests": MessageLookupByLibrary.simpleMessage("リクエスト"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage("最近のリクエスト記録を表示"),
|
||||
"reset": MessageLookupByLibrary.simpleMessage("リセット"),
|
||||
"resetTip": MessageLookupByLibrary.simpleMessage("リセットを確定"),
|
||||
"resources": MessageLookupByLibrary.simpleMessage("リソース"),
|
||||
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部リソース関連情報"),
|
||||
"respectRules": MessageLookupByLibrary.simpleMessage("ルール尊重"),
|
||||
"respectRulesDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"DNS接続がルールに従う(proxy-server-nameserverの設定が必要)",
|
||||
),
|
||||
"routeAddress": MessageLookupByLibrary.simpleMessage("ルートアドレス"),
|
||||
"routeAddressDesc": MessageLookupByLibrary.simpleMessage("ルートアドレスを設定"),
|
||||
"routeMode": MessageLookupByLibrary.simpleMessage("ルートモード"),
|
||||
"routeMode_bypassPrivate": MessageLookupByLibrary.simpleMessage(
|
||||
"プライベートルートをバイパス",
|
||||
),
|
||||
"routeMode_config": MessageLookupByLibrary.simpleMessage("設定を使用"),
|
||||
"ru": MessageLookupByLibrary.simpleMessage("ロシア語"),
|
||||
"rule": MessageLookupByLibrary.simpleMessage("ルール"),
|
||||
"ruleName": MessageLookupByLibrary.simpleMessage("ルール名"),
|
||||
"ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"ルールプロバイダーは必須です",
|
||||
),
|
||||
"ruleProviders": MessageLookupByLibrary.simpleMessage("ルールプロバイダー"),
|
||||
"ruleTarget": MessageLookupByLibrary.simpleMessage("ルール対象"),
|
||||
"ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage("ルール対象は必須です"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("保存"),
|
||||
"saveChanges": MessageLookupByLibrary.simpleMessage("変更を保存しますか?"),
|
||||
"saveTip": MessageLookupByLibrary.simpleMessage("保存してもよろしいですか?"),
|
||||
"search": MessageLookupByLibrary.simpleMessage("検索"),
|
||||
"seconds": MessageLookupByLibrary.simpleMessage("秒"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("すべて選択"),
|
||||
"selected": MessageLookupByLibrary.simpleMessage("選択済み"),
|
||||
"selectedCountTitle": m0,
|
||||
"settings": MessageLookupByLibrary.simpleMessage("設定"),
|
||||
"show": MessageLookupByLibrary.simpleMessage("表示"),
|
||||
"shrink": MessageLookupByLibrary.simpleMessage("縮小"),
|
||||
"silentLaunch": MessageLookupByLibrary.simpleMessage("バックグラウンド起動"),
|
||||
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("バックグラウンドで起動"),
|
||||
"size": MessageLookupByLibrary.simpleMessage("サイズ"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("並び替え"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("ソース"),
|
||||
"sourceIp": MessageLookupByLibrary.simpleMessage("送信元IP"),
|
||||
"stackMode": MessageLookupByLibrary.simpleMessage("スタックモード"),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("標準"),
|
||||
"start": MessageLookupByLibrary.simpleMessage("開始"),
|
||||
"startVpn": MessageLookupByLibrary.simpleMessage("VPNを開始中..."),
|
||||
"status": MessageLookupByLibrary.simpleMessage("ステータス"),
|
||||
"statusDesc": MessageLookupByLibrary.simpleMessage("無効時はシステムDNSを使用"),
|
||||
"stop": MessageLookupByLibrary.simpleMessage("停止"),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("VPNを停止中..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("スタイル"),
|
||||
"subRule": MessageLookupByLibrary.simpleMessage("サブルール"),
|
||||
"subRuleEmptyTip": MessageLookupByLibrary.simpleMessage("サブルールの内容は必須です"),
|
||||
"submit": MessageLookupByLibrary.simpleMessage("送信"),
|
||||
"sync": MessageLookupByLibrary.simpleMessage("同期"),
|
||||
"system": MessageLookupByLibrary.simpleMessage("システム"),
|
||||
"systemFont": MessageLookupByLibrary.simpleMessage("システムフォント"),
|
||||
"systemProxy": MessageLookupByLibrary.simpleMessage("システムプロキシ"),
|
||||
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"HTTPプロキシをVpnServiceに接続",
|
||||
),
|
||||
"tab": MessageLookupByLibrary.simpleMessage("タブ"),
|
||||
"tabAnimation": MessageLookupByLibrary.simpleMessage("タブアニメーション"),
|
||||
"tabAnimationDesc": MessageLookupByLibrary.simpleMessage("モバイル表示でのみ有効"),
|
||||
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP並列処理"),
|
||||
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("TCP並列処理を許可"),
|
||||
"testUrl": MessageLookupByLibrary.simpleMessage("URLテスト"),
|
||||
"theme": MessageLookupByLibrary.simpleMessage("テーマ"),
|
||||
"themeColor": MessageLookupByLibrary.simpleMessage("テーマカラー"),
|
||||
"themeDesc": MessageLookupByLibrary.simpleMessage("ダークモードの設定、色の調整"),
|
||||
"themeMode": MessageLookupByLibrary.simpleMessage("テーマモード"),
|
||||
"threeColumns": MessageLookupByLibrary.simpleMessage("3列"),
|
||||
"tight": MessageLookupByLibrary.simpleMessage("密"),
|
||||
"time": MessageLookupByLibrary.simpleMessage("時間"),
|
||||
"tip": MessageLookupByLibrary.simpleMessage("ヒント"),
|
||||
"toggle": MessageLookupByLibrary.simpleMessage("トグル"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("ツール"),
|
||||
"trafficUsage": MessageLookupByLibrary.simpleMessage("トラフィック使用量"),
|
||||
"tun": MessageLookupByLibrary.simpleMessage("TUN"),
|
||||
"tunDesc": MessageLookupByLibrary.simpleMessage("管理者モードでのみ有効"),
|
||||
"twoColumns": MessageLookupByLibrary.simpleMessage("2列"),
|
||||
"unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"現在のプロファイルを更新できません",
|
||||
),
|
||||
"undo": MessageLookupByLibrary.simpleMessage("元に戻す"),
|
||||
"unifiedDelay": MessageLookupByLibrary.simpleMessage("統一遅延"),
|
||||
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"ハンドシェイクなどの余分な遅延を削除",
|
||||
),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("不明"),
|
||||
"update": MessageLookupByLibrary.simpleMessage("更新"),
|
||||
"upload": MessageLookupByLibrary.simpleMessage("アップロード"),
|
||||
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
||||
"urlDesc": MessageLookupByLibrary.simpleMessage("URL経由でプロファイルを取得"),
|
||||
"useHosts": MessageLookupByLibrary.simpleMessage("ホストを使用"),
|
||||
"useSystemHosts": MessageLookupByLibrary.simpleMessage("システムホストを使用"),
|
||||
"value": MessageLookupByLibrary.simpleMessage("値"),
|
||||
"valueExists": MessageLookupByLibrary.simpleMessage("現在の値は既に存在します"),
|
||||
"view": MessageLookupByLibrary.simpleMessage("表示"),
|
||||
"vpnDesc": MessageLookupByLibrary.simpleMessage("VPN関連設定の変更"),
|
||||
"vpnEnableDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"VpnService経由で全システムトラフィックをルーティング",
|
||||
),
|
||||
"vpnSystemProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"HTTPプロキシをVpnServiceに接続",
|
||||
),
|
||||
"vpnTip": MessageLookupByLibrary.simpleMessage("変更はVPN再起動後に有効"),
|
||||
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV設定"),
|
||||
"whitelistMode": MessageLookupByLibrary.simpleMessage("ホワイトリストモード"),
|
||||
"years": MessageLookupByLibrary.simpleMessage("年"),
|
||||
"zh_CN": MessageLookupByLibrary.simpleMessage("簡体字中国語"),
|
||||
};
|
||||
}
|
||||
720
lib/l10n/intl/messages_ru.dart
Normal file
720
lib/l10n/intl/messages_ru.dart
Normal file
@@ -0,0 +1,720 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a ru locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'ru';
|
||||
|
||||
static String m0(count) => "Выбрано ${count} элементов";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"about": MessageLookupByLibrary.simpleMessage("О программе"),
|
||||
"accessControl": MessageLookupByLibrary.simpleMessage("Контроль доступа"),
|
||||
"accessControlAllowDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Разрешить только выбранным приложениям доступ к VPN",
|
||||
),
|
||||
"accessControlDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Настройка доступа приложений к прокси",
|
||||
),
|
||||
"accessControlNotAllowDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Выбранные приложения будут исключены из VPN",
|
||||
),
|
||||
"account": MessageLookupByLibrary.simpleMessage("Аккаунт"),
|
||||
"accountTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Аккаунт не может быть пустым",
|
||||
),
|
||||
"action": MessageLookupByLibrary.simpleMessage("Действие"),
|
||||
"action_mode": MessageLookupByLibrary.simpleMessage("Переключить режим"),
|
||||
"action_proxy": MessageLookupByLibrary.simpleMessage("Системный прокси"),
|
||||
"action_start": MessageLookupByLibrary.simpleMessage("Старт/Стоп"),
|
||||
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
|
||||
"action_view": MessageLookupByLibrary.simpleMessage("Показать/Скрыть"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("Добавить"),
|
||||
"addRule": MessageLookupByLibrary.simpleMessage("Добавить правило"),
|
||||
"addedOriginRules": MessageLookupByLibrary.simpleMessage(
|
||||
"Добавить к оригинальным правилам",
|
||||
),
|
||||
"address": MessageLookupByLibrary.simpleMessage("Адрес"),
|
||||
"addressHelp": MessageLookupByLibrary.simpleMessage("Адрес сервера WebDAV"),
|
||||
"addressTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, введите действительный адрес WebDAV",
|
||||
),
|
||||
"adminAutoLaunch": MessageLookupByLibrary.simpleMessage(
|
||||
"Автозапуск с правами администратора",
|
||||
),
|
||||
"adminAutoLaunchDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Запуск с правами администратора при загрузке системы",
|
||||
),
|
||||
"ago": MessageLookupByLibrary.simpleMessage(" назад"),
|
||||
"agree": MessageLookupByLibrary.simpleMessage("Согласен"),
|
||||
"allApps": MessageLookupByLibrary.simpleMessage("Все приложения"),
|
||||
"allowBypass": MessageLookupByLibrary.simpleMessage(
|
||||
"Разрешить приложениям обходить VPN",
|
||||
),
|
||||
"allowBypassDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Некоторые приложения могут обходить VPN при включении",
|
||||
),
|
||||
"allowLan": MessageLookupByLibrary.simpleMessage("Разрешить LAN"),
|
||||
"allowLanDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Разрешить доступ к прокси через локальную сеть",
|
||||
),
|
||||
"app": MessageLookupByLibrary.simpleMessage("Приложение"),
|
||||
"appAccessControl": MessageLookupByLibrary.simpleMessage(
|
||||
"Контроль доступа приложений",
|
||||
),
|
||||
"appDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Обработка настроек, связанных с приложением",
|
||||
),
|
||||
"application": MessageLookupByLibrary.simpleMessage("Приложение"),
|
||||
"applicationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Изменение настроек, связанных с приложением",
|
||||
),
|
||||
"auto": MessageLookupByLibrary.simpleMessage("Авто"),
|
||||
"autoCheckUpdate": MessageLookupByLibrary.simpleMessage(
|
||||
"Автопроверка обновлений",
|
||||
),
|
||||
"autoCheckUpdateDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Автоматически проверять обновления при запуске приложения",
|
||||
),
|
||||
"autoCloseConnections": MessageLookupByLibrary.simpleMessage(
|
||||
"Автоматическое закрытие соединений",
|
||||
),
|
||||
"autoCloseConnectionsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Автоматически закрывать соединения после смены узла",
|
||||
),
|
||||
"autoLaunch": MessageLookupByLibrary.simpleMessage("Автозапуск"),
|
||||
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Следовать автозапуску системы",
|
||||
),
|
||||
"autoRun": MessageLookupByLibrary.simpleMessage("Автозапуск"),
|
||||
"autoRunDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Автоматический запуск при открытии приложения",
|
||||
),
|
||||
"autoUpdate": MessageLookupByLibrary.simpleMessage("Автообновление"),
|
||||
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage(
|
||||
"Интервал автообновления (минуты)",
|
||||
),
|
||||
"backup": MessageLookupByLibrary.simpleMessage("Резервное копирование"),
|
||||
"backupAndRecovery": MessageLookupByLibrary.simpleMessage(
|
||||
"Резервное копирование и восстановление",
|
||||
),
|
||||
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Синхронизация данных через WebDAV или файл",
|
||||
),
|
||||
"backupSuccess": MessageLookupByLibrary.simpleMessage(
|
||||
"Резервное копирование успешно",
|
||||
),
|
||||
"basicConfig": MessageLookupByLibrary.simpleMessage("Базовая конфигурация"),
|
||||
"basicConfigDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Глобальное изменение базовых настроек",
|
||||
),
|
||||
"bind": MessageLookupByLibrary.simpleMessage("Привязать"),
|
||||
"blacklistMode": MessageLookupByLibrary.simpleMessage(
|
||||
"Режим черного списка",
|
||||
),
|
||||
"bypassDomain": MessageLookupByLibrary.simpleMessage("Обход домена"),
|
||||
"bypassDomainDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Действует только при включенном системном прокси",
|
||||
),
|
||||
"cacheCorrupt": MessageLookupByLibrary.simpleMessage(
|
||||
"Кэш поврежден. Хотите очистить его?",
|
||||
),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("Отмена"),
|
||||
"cancelFilterSystemApp": MessageLookupByLibrary.simpleMessage(
|
||||
"Отменить фильтрацию системных приложений",
|
||||
),
|
||||
"cancelSelectAll": MessageLookupByLibrary.simpleMessage(
|
||||
"Отменить выбор всего",
|
||||
),
|
||||
"checkError": MessageLookupByLibrary.simpleMessage("Ошибка проверки"),
|
||||
"checkUpdate": MessageLookupByLibrary.simpleMessage("Проверить обновления"),
|
||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage(
|
||||
"Текущее приложение уже является последней версией",
|
||||
),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("Проверка..."),
|
||||
"clipboardExport": MessageLookupByLibrary.simpleMessage(
|
||||
"Экспорт в буфер обмена",
|
||||
),
|
||||
"clipboardImport": MessageLookupByLibrary.simpleMessage(
|
||||
"Импорт из буфера обмена",
|
||||
),
|
||||
"columns": MessageLookupByLibrary.simpleMessage("Столбцы"),
|
||||
"compatible": MessageLookupByLibrary.simpleMessage("Режим совместимости"),
|
||||
"compatibleDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Включение приведет к потере части функциональности приложения, но обеспечит полную поддержку Clash.",
|
||||
),
|
||||
"confirm": MessageLookupByLibrary.simpleMessage("Подтвердить"),
|
||||
"connections": MessageLookupByLibrary.simpleMessage("Соединения"),
|
||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Просмотр текущих данных о соединениях",
|
||||
),
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("Связь:"),
|
||||
"content": MessageLookupByLibrary.simpleMessage("Содержание"),
|
||||
"contentEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Содержание не может быть пустым",
|
||||
),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("Копировать"),
|
||||
"copyEnvVar": MessageLookupByLibrary.simpleMessage(
|
||||
"Копирование переменных окружения",
|
||||
),
|
||||
"copyLink": MessageLookupByLibrary.simpleMessage("Копировать ссылку"),
|
||||
"copySuccess": MessageLookupByLibrary.simpleMessage("Копирование успешно"),
|
||||
"core": MessageLookupByLibrary.simpleMessage("Ядро"),
|
||||
"coreInfo": MessageLookupByLibrary.simpleMessage("Информация о ядре"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("Страна"),
|
||||
"create": MessageLookupByLibrary.simpleMessage("Создать"),
|
||||
"cut": MessageLookupByLibrary.simpleMessage("Вырезать"),
|
||||
"dark": MessageLookupByLibrary.simpleMessage("Темный"),
|
||||
"dashboard": MessageLookupByLibrary.simpleMessage("Панель управления"),
|
||||
"days": MessageLookupByLibrary.simpleMessage("Дней"),
|
||||
"defaultNameserver": MessageLookupByLibrary.simpleMessage(
|
||||
"Сервер имен по умолчанию",
|
||||
),
|
||||
"defaultNameserverDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Для разрешения DNS-сервера",
|
||||
),
|
||||
"defaultSort": MessageLookupByLibrary.simpleMessage(
|
||||
"Сортировка по умолчанию",
|
||||
),
|
||||
"defaultText": MessageLookupByLibrary.simpleMessage("По умолчанию"),
|
||||
"delay": MessageLookupByLibrary.simpleMessage("Задержка"),
|
||||
"delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"),
|
||||
"delete": MessageLookupByLibrary.simpleMessage("Удалить"),
|
||||
"deleteProfileTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Вы уверены, что хотите удалить текущий профиль?",
|
||||
),
|
||||
"deleteRuleTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Вы уверены, что хотите удалить выбранное правило?",
|
||||
),
|
||||
"desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Многоплатформенный прокси-клиент на основе ClashMeta, простой и удобный в использовании, с открытым исходным кодом и без рекламы.",
|
||||
),
|
||||
"detectionTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Опирается на сторонний API, только для справки",
|
||||
),
|
||||
"direct": MessageLookupByLibrary.simpleMessage("Прямой"),
|
||||
"disclaimer": MessageLookupByLibrary.simpleMessage(
|
||||
"Отказ от ответственности",
|
||||
),
|
||||
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Это программное обеспечение используется только в некоммерческих целях, таких как учебные обмены и научные исследования. Запрещено использовать это программное обеспечение в коммерческих целях. Любая коммерческая деятельность, если таковая имеется, не имеет отношения к этому программному обеспечению.",
|
||||
),
|
||||
"discoverNewVersion": MessageLookupByLibrary.simpleMessage(
|
||||
"Обнаружена новая версия",
|
||||
),
|
||||
"discovery": MessageLookupByLibrary.simpleMessage(
|
||||
"Обнаружена новая версия",
|
||||
),
|
||||
"dnsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Обновление настроек, связанных с DNS",
|
||||
),
|
||||
"dnsMode": MessageLookupByLibrary.simpleMessage("Режим DNS"),
|
||||
"doYouWantToPass": MessageLookupByLibrary.simpleMessage(
|
||||
"Вы хотите пропустить",
|
||||
),
|
||||
"domain": MessageLookupByLibrary.simpleMessage("Домен"),
|
||||
"download": MessageLookupByLibrary.simpleMessage("Скачивание"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("Редактировать"),
|
||||
"en": MessageLookupByLibrary.simpleMessage("Английский"),
|
||||
"enableOverride": MessageLookupByLibrary.simpleMessage(
|
||||
"Включить переопределение",
|
||||
),
|
||||
"entries": MessageLookupByLibrary.simpleMessage(" записей"),
|
||||
"exclude": MessageLookupByLibrary.simpleMessage(
|
||||
"Скрыть из последних задач",
|
||||
),
|
||||
"excludeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Когда приложение находится в фоновом режиме, оно скрыто из последних задач",
|
||||
),
|
||||
"exit": MessageLookupByLibrary.simpleMessage("Выход"),
|
||||
"expand": MessageLookupByLibrary.simpleMessage("Стандартный"),
|
||||
"expirationTime": MessageLookupByLibrary.simpleMessage("Время истечения"),
|
||||
"exportFile": MessageLookupByLibrary.simpleMessage("Экспорт файла"),
|
||||
"exportLogs": MessageLookupByLibrary.simpleMessage("Экспорт логов"),
|
||||
"exportSuccess": MessageLookupByLibrary.simpleMessage("Экспорт успешен"),
|
||||
"externalController": MessageLookupByLibrary.simpleMessage(
|
||||
"Внешний контроллер",
|
||||
),
|
||||
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"При включении ядро Clash можно контролировать на порту 9090",
|
||||
),
|
||||
"externalLink": MessageLookupByLibrary.simpleMessage("Внешняя ссылка"),
|
||||
"externalResources": MessageLookupByLibrary.simpleMessage(
|
||||
"Внешние ресурсы",
|
||||
),
|
||||
"fakeipFilter": MessageLookupByLibrary.simpleMessage("Фильтр Fakeip"),
|
||||
"fakeipRange": MessageLookupByLibrary.simpleMessage("Диапазон Fakeip"),
|
||||
"fallback": MessageLookupByLibrary.simpleMessage("Резервный"),
|
||||
"fallbackDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Обычно используется оффшорный DNS",
|
||||
),
|
||||
"fallbackFilter": MessageLookupByLibrary.simpleMessage(
|
||||
"Фильтр резервного DNS",
|
||||
),
|
||||
"file": MessageLookupByLibrary.simpleMessage("Файл"),
|
||||
"fileDesc": MessageLookupByLibrary.simpleMessage("Прямая загрузка профиля"),
|
||||
"fileIsUpdate": MessageLookupByLibrary.simpleMessage(
|
||||
"Файл был изменен. Хотите сохранить изменения?",
|
||||
),
|
||||
"filterSystemApp": MessageLookupByLibrary.simpleMessage(
|
||||
"Фильтровать системные приложения",
|
||||
),
|
||||
"findProcessMode": MessageLookupByLibrary.simpleMessage(
|
||||
"Режим поиска процесса",
|
||||
),
|
||||
"findProcessModeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"При включении возможны небольшие потери производительности",
|
||||
),
|
||||
"fontFamily": MessageLookupByLibrary.simpleMessage("Семейство шрифтов"),
|
||||
"fourColumns": MessageLookupByLibrary.simpleMessage("Четыре столбца"),
|
||||
"general": MessageLookupByLibrary.simpleMessage("Общие"),
|
||||
"generalDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Изменение общих настроек",
|
||||
),
|
||||
"geoData": MessageLookupByLibrary.simpleMessage("Геоданные"),
|
||||
"geodataLoader": MessageLookupByLibrary.simpleMessage(
|
||||
"Режим низкого потребления памяти для геоданных",
|
||||
),
|
||||
"geodataLoaderDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Включение будет использовать загрузчик геоданных с низким потреблением памяти",
|
||||
),
|
||||
"geoipCode": MessageLookupByLibrary.simpleMessage("Код Geoip"),
|
||||
"getOriginRules": MessageLookupByLibrary.simpleMessage(
|
||||
"Получить оригинальные правила",
|
||||
),
|
||||
"global": MessageLookupByLibrary.simpleMessage("Глобальный"),
|
||||
"go": MessageLookupByLibrary.simpleMessage("Перейти"),
|
||||
"goDownload": MessageLookupByLibrary.simpleMessage("Перейти к загрузке"),
|
||||
"hasCacheChange": MessageLookupByLibrary.simpleMessage(
|
||||
"Хотите сохранить изменения в кэше?",
|
||||
),
|
||||
"hostsDesc": MessageLookupByLibrary.simpleMessage("Добавить Hosts"),
|
||||
"hotkeyConflict": MessageLookupByLibrary.simpleMessage(
|
||||
"Конфликт горячих клавиш",
|
||||
),
|
||||
"hotkeyManagement": MessageLookupByLibrary.simpleMessage(
|
||||
"Управление горячими клавишами",
|
||||
),
|
||||
"hotkeyManagementDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Использование клавиатуры для управления приложением",
|
||||
),
|
||||
"hours": MessageLookupByLibrary.simpleMessage("Часов"),
|
||||
"icon": MessageLookupByLibrary.simpleMessage("Иконка"),
|
||||
"iconConfiguration": MessageLookupByLibrary.simpleMessage(
|
||||
"Конфигурация иконки",
|
||||
),
|
||||
"iconStyle": MessageLookupByLibrary.simpleMessage("Стиль иконки"),
|
||||
"importFromURL": MessageLookupByLibrary.simpleMessage("Импорт из URL"),
|
||||
"infiniteTime": MessageLookupByLibrary.simpleMessage(
|
||||
"Долгосрочное действие",
|
||||
),
|
||||
"init": MessageLookupByLibrary.simpleMessage("Инициализация"),
|
||||
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, введите правильную горячую клавишу",
|
||||
),
|
||||
"intelligentSelected": MessageLookupByLibrary.simpleMessage(
|
||||
"Интеллектуальный выбор",
|
||||
),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("Внутренний IP"),
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
||||
"При включении будет возможно получать IPv6 трафик",
|
||||
),
|
||||
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Разрешить входящий IPv6",
|
||||
),
|
||||
"ja": MessageLookupByLibrary.simpleMessage("Японский"),
|
||||
"just": MessageLookupByLibrary.simpleMessage("Только что"),
|
||||
"keepAliveIntervalDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Интервал поддержания TCP-соединения",
|
||||
),
|
||||
"key": MessageLookupByLibrary.simpleMessage("Ключ"),
|
||||
"keyExists": MessageLookupByLibrary.simpleMessage(
|
||||
"Текущий ключ уже существует",
|
||||
),
|
||||
"language": MessageLookupByLibrary.simpleMessage("Язык"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("Макет"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("Светлый"),
|
||||
"list": MessageLookupByLibrary.simpleMessage("Список"),
|
||||
"listen": MessageLookupByLibrary.simpleMessage("Слушать"),
|
||||
"local": MessageLookupByLibrary.simpleMessage("Локальный"),
|
||||
"localBackupDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Резервное копирование локальных данных на локальный диск",
|
||||
),
|
||||
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановление данных из файла",
|
||||
),
|
||||
"logLevel": MessageLookupByLibrary.simpleMessage("Уровень логов"),
|
||||
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
|
||||
"logcatDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Отключение скроет запись логов",
|
||||
),
|
||||
"logs": MessageLookupByLibrary.simpleMessage("Логи"),
|
||||
"logsDesc": MessageLookupByLibrary.simpleMessage("Записи захвата логов"),
|
||||
"loopback": MessageLookupByLibrary.simpleMessage(
|
||||
"Инструмент разблокировки Loopback",
|
||||
),
|
||||
"loopbackDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Используется для разблокировки Loopback UWP",
|
||||
),
|
||||
"loose": MessageLookupByLibrary.simpleMessage("Свободный"),
|
||||
"memoryInfo": MessageLookupByLibrary.simpleMessage("Информация о памяти"),
|
||||
"min": MessageLookupByLibrary.simpleMessage("Мин"),
|
||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage(
|
||||
"Свернуть при выходе",
|
||||
),
|
||||
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Изменить стандартное событие выхода из системы",
|
||||
),
|
||||
"minutes": MessageLookupByLibrary.simpleMessage("Минут"),
|
||||
"mode": MessageLookupByLibrary.simpleMessage("Режим"),
|
||||
"months": MessageLookupByLibrary.simpleMessage("Месяцев"),
|
||||
"more": MessageLookupByLibrary.simpleMessage("Еще"),
|
||||
"name": MessageLookupByLibrary.simpleMessage("Имя"),
|
||||
"nameSort": MessageLookupByLibrary.simpleMessage("Сортировка по имени"),
|
||||
"nameserver": MessageLookupByLibrary.simpleMessage("Сервер имен"),
|
||||
"nameserverDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Для разрешения домена",
|
||||
),
|
||||
"nameserverPolicy": MessageLookupByLibrary.simpleMessage(
|
||||
"Политика сервера имен",
|
||||
),
|
||||
"nameserverPolicyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Указать соответствующую политику сервера имен",
|
||||
),
|
||||
"network": MessageLookupByLibrary.simpleMessage("Сеть"),
|
||||
"networkDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Изменение настроек, связанных с сетью",
|
||||
),
|
||||
"networkDetection": MessageLookupByLibrary.simpleMessage(
|
||||
"Обнаружение сети",
|
||||
),
|
||||
"networkSpeed": MessageLookupByLibrary.simpleMessage("Скорость сети"),
|
||||
"noData": MessageLookupByLibrary.simpleMessage("Нет данных"),
|
||||
"noHotKey": MessageLookupByLibrary.simpleMessage("Нет горячей клавиши"),
|
||||
"noIcon": MessageLookupByLibrary.simpleMessage("Нет иконки"),
|
||||
"noInfo": MessageLookupByLibrary.simpleMessage("Нет информации"),
|
||||
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Нет дополнительной информации",
|
||||
),
|
||||
"noNetwork": MessageLookupByLibrary.simpleMessage("Нет сети"),
|
||||
"noProxy": MessageLookupByLibrary.simpleMessage("Нет прокси"),
|
||||
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, создайте профиль или добавьте действительный профиль",
|
||||
),
|
||||
"noResolve": MessageLookupByLibrary.simpleMessage("Не разрешать IP"),
|
||||
"none": MessageLookupByLibrary.simpleMessage("Нет"),
|
||||
"notEmpty": MessageLookupByLibrary.simpleMessage("Не может быть пустым"),
|
||||
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Текущая группа прокси не может быть выбрана.",
|
||||
),
|
||||
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Нет соединений",
|
||||
),
|
||||
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Не удалось получить информацию о ядре",
|
||||
),
|
||||
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("Нет логов"),
|
||||
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Нет профиля, пожалуйста, добавьте профиль",
|
||||
),
|
||||
"nullProxies": MessageLookupByLibrary.simpleMessage("Нет прокси"),
|
||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("Нет запросов"),
|
||||
"oneColumn": MessageLookupByLibrary.simpleMessage("Один столбец"),
|
||||
"onlyIcon": MessageLookupByLibrary.simpleMessage("Только иконка"),
|
||||
"onlyOtherApps": MessageLookupByLibrary.simpleMessage(
|
||||
"Только сторонние приложения",
|
||||
),
|
||||
"onlyStatisticsProxy": MessageLookupByLibrary.simpleMessage(
|
||||
"Только статистика прокси",
|
||||
),
|
||||
"onlyStatisticsProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"При включении будет учитываться только трафик прокси",
|
||||
),
|
||||
"options": MessageLookupByLibrary.simpleMessage("Опции"),
|
||||
"other": MessageLookupByLibrary.simpleMessage("Другое"),
|
||||
"otherContributors": MessageLookupByLibrary.simpleMessage(
|
||||
"Другие участники",
|
||||
),
|
||||
"outboundMode": MessageLookupByLibrary.simpleMessage(
|
||||
"Режим исходящего трафика",
|
||||
),
|
||||
"override": MessageLookupByLibrary.simpleMessage("Переопределить"),
|
||||
"overrideDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Переопределить конфигурацию, связанную с прокси",
|
||||
),
|
||||
"overrideDns": MessageLookupByLibrary.simpleMessage("Переопределить DNS"),
|
||||
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Включение переопределит настройки DNS в профиле",
|
||||
),
|
||||
"overrideOriginRules": MessageLookupByLibrary.simpleMessage(
|
||||
"Переопределить оригинальное правило",
|
||||
),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Пароль"),
|
||||
"passwordTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Пароль не может быть пустым",
|
||||
),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("Вставить"),
|
||||
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, привяжите WebDAV",
|
||||
),
|
||||
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, введите пароль администратора",
|
||||
),
|
||||
"pleaseUploadFile": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, загрузите файл",
|
||||
),
|
||||
"pleaseUploadValidQrcode": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, загрузите действительный QR-код",
|
||||
),
|
||||
"port": MessageLookupByLibrary.simpleMessage("Порт"),
|
||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Приоритетное использование HTTP/3 для DOH",
|
||||
),
|
||||
"pressKeyboard": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, нажмите клавишу.",
|
||||
),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("Предпросмотр"),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("Профиль"),
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, введите действительный формат интервала времени",
|
||||
),
|
||||
"profileAutoUpdateIntervalNullValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, введите интервал времени для автообновления",
|
||||
),
|
||||
"profileHasUpdate": MessageLookupByLibrary.simpleMessage(
|
||||
"Профиль был изменен. Хотите отключить автообновление?",
|
||||
),
|
||||
"profileNameNullValidationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, введите имя профиля",
|
||||
),
|
||||
"profileParseErrorDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"ошибка разбора профиля",
|
||||
),
|
||||
"profileUrlInvalidValidationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, введите действительный URL профиля",
|
||||
),
|
||||
"profileUrlNullValidationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, введите URL профиля",
|
||||
),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("Профили"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("Сортировка профилей"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("Проект"),
|
||||
"providers": MessageLookupByLibrary.simpleMessage("Провайдеры"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("Прокси"),
|
||||
"proxiesSetting": MessageLookupByLibrary.simpleMessage("Настройка прокси"),
|
||||
"proxyGroup": MessageLookupByLibrary.simpleMessage("Группа прокси"),
|
||||
"proxyNameserver": MessageLookupByLibrary.simpleMessage(
|
||||
"Прокси-сервер имен",
|
||||
),
|
||||
"proxyNameserverDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Домен для разрешения прокси-узлов",
|
||||
),
|
||||
"proxyPort": MessageLookupByLibrary.simpleMessage("Порт прокси"),
|
||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Установить порт прослушивания Clash",
|
||||
),
|
||||
"proxyProviders": MessageLookupByLibrary.simpleMessage("Провайдеры прокси"),
|
||||
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Чисто черный режим"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("QR-код"),
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Сканируйте QR-код для получения профиля",
|
||||
),
|
||||
"recovery": MessageLookupByLibrary.simpleMessage("Восстановление"),
|
||||
"recoveryAll": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановить все данные",
|
||||
),
|
||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
|
||||
"Только восстановление профилей",
|
||||
),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановление успешно",
|
||||
),
|
||||
"redo": MessageLookupByLibrary.simpleMessage("Повторить"),
|
||||
"regExp": MessageLookupByLibrary.simpleMessage("Регулярное выражение"),
|
||||
"remote": MessageLookupByLibrary.simpleMessage("Удаленный"),
|
||||
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Резервное копирование локальных данных на WebDAV",
|
||||
),
|
||||
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Восстановление данных с WebDAV",
|
||||
),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("Удалить"),
|
||||
"requests": MessageLookupByLibrary.simpleMessage("Запросы"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Просмотр последних записей запросов",
|
||||
),
|
||||
"reset": MessageLookupByLibrary.simpleMessage("Сброс"),
|
||||
"resetTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Убедитесь, что хотите сбросить",
|
||||
),
|
||||
"resources": MessageLookupByLibrary.simpleMessage("Ресурсы"),
|
||||
"resourcesDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Информация, связанная с внешними ресурсами",
|
||||
),
|
||||
"respectRules": MessageLookupByLibrary.simpleMessage("Соблюдение правил"),
|
||||
"respectRulesDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"DNS-соединение следует правилам, необходимо настроить proxy-server-nameserver",
|
||||
),
|
||||
"routeAddress": MessageLookupByLibrary.simpleMessage("Адрес маршрутизации"),
|
||||
"routeAddressDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Настройка адреса прослушивания маршрутизации",
|
||||
),
|
||||
"routeMode": MessageLookupByLibrary.simpleMessage("Режим маршрутизации"),
|
||||
"routeMode_bypassPrivate": MessageLookupByLibrary.simpleMessage(
|
||||
"Обход частных адресов маршрутизации",
|
||||
),
|
||||
"routeMode_config": MessageLookupByLibrary.simpleMessage(
|
||||
"Использовать конфигурацию",
|
||||
),
|
||||
"ru": MessageLookupByLibrary.simpleMessage("Русский"),
|
||||
"rule": MessageLookupByLibrary.simpleMessage("Правило"),
|
||||
"ruleName": MessageLookupByLibrary.simpleMessage("Название правила"),
|
||||
"ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Поставщик правил не может быть пустым",
|
||||
),
|
||||
"ruleProviders": MessageLookupByLibrary.simpleMessage("Провайдеры правил"),
|
||||
"ruleTarget": MessageLookupByLibrary.simpleMessage("Цель правила"),
|
||||
"ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Цель правила не может быть пустой",
|
||||
),
|
||||
"save": MessageLookupByLibrary.simpleMessage("Сохранить"),
|
||||
"saveChanges": MessageLookupByLibrary.simpleMessage("Сохранить изменения?"),
|
||||
"saveTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Вы уверены, что хотите сохранить?",
|
||||
),
|
||||
"search": MessageLookupByLibrary.simpleMessage("Поиск"),
|
||||
"seconds": MessageLookupByLibrary.simpleMessage("Секунд"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("Выбрать все"),
|
||||
"selected": MessageLookupByLibrary.simpleMessage("Выбрано"),
|
||||
"selectedCountTitle": m0,
|
||||
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
|
||||
"show": MessageLookupByLibrary.simpleMessage("Показать"),
|
||||
"shrink": MessageLookupByLibrary.simpleMessage("Сжать"),
|
||||
"silentLaunch": MessageLookupByLibrary.simpleMessage("Тихий запуск"),
|
||||
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Запуск в фоновом режиме",
|
||||
),
|
||||
"size": MessageLookupByLibrary.simpleMessage("Размер"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("Сортировка"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("Источник"),
|
||||
"sourceIp": MessageLookupByLibrary.simpleMessage("Исходный IP"),
|
||||
"stackMode": MessageLookupByLibrary.simpleMessage("Режим стека"),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("Стандартный"),
|
||||
"start": MessageLookupByLibrary.simpleMessage("Старт"),
|
||||
"startVpn": MessageLookupByLibrary.simpleMessage("Запуск VPN..."),
|
||||
"status": MessageLookupByLibrary.simpleMessage("Статус"),
|
||||
"statusDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Системный DNS будет использоваться при выключении",
|
||||
),
|
||||
"stop": MessageLookupByLibrary.simpleMessage("Стоп"),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("Остановка VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("Стиль"),
|
||||
"subRule": MessageLookupByLibrary.simpleMessage("Подправило"),
|
||||
"subRuleEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Содержание подправила не может быть пустым",
|
||||
),
|
||||
"submit": MessageLookupByLibrary.simpleMessage("Отправить"),
|
||||
"sync": MessageLookupByLibrary.simpleMessage("Синхронизация"),
|
||||
"system": MessageLookupByLibrary.simpleMessage("Система"),
|
||||
"systemFont": MessageLookupByLibrary.simpleMessage("Системный шрифт"),
|
||||
"systemProxy": MessageLookupByLibrary.simpleMessage("Системный прокси"),
|
||||
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Прикрепить HTTP-прокси к VpnService",
|
||||
),
|
||||
"tab": MessageLookupByLibrary.simpleMessage("Вкладка"),
|
||||
"tabAnimation": MessageLookupByLibrary.simpleMessage("Анимация вкладок"),
|
||||
"tabAnimationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Действительно только в мобильном виде",
|
||||
),
|
||||
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP параллелизм"),
|
||||
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Включение позволит использовать параллелизм TCP",
|
||||
),
|
||||
"testUrl": MessageLookupByLibrary.simpleMessage("Тест URL"),
|
||||
"theme": MessageLookupByLibrary.simpleMessage("Тема"),
|
||||
"themeColor": MessageLookupByLibrary.simpleMessage("Цвет темы"),
|
||||
"themeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Установить темный режим, настроить цвет",
|
||||
),
|
||||
"themeMode": MessageLookupByLibrary.simpleMessage("Режим темы"),
|
||||
"threeColumns": MessageLookupByLibrary.simpleMessage("Три столбца"),
|
||||
"tight": MessageLookupByLibrary.simpleMessage("Плотный"),
|
||||
"time": MessageLookupByLibrary.simpleMessage("Время"),
|
||||
"tip": MessageLookupByLibrary.simpleMessage("подсказка"),
|
||||
"toggle": MessageLookupByLibrary.simpleMessage("Переключить"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("Инструменты"),
|
||||
"trafficUsage": MessageLookupByLibrary.simpleMessage(
|
||||
"Использование трафика",
|
||||
),
|
||||
"tun": MessageLookupByLibrary.simpleMessage("TUN"),
|
||||
"tunDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"действительно только в режиме администратора",
|
||||
),
|
||||
"twoColumns": MessageLookupByLibrary.simpleMessage("Два столбца"),
|
||||
"unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"невозможно обновить текущий профиль",
|
||||
),
|
||||
"undo": MessageLookupByLibrary.simpleMessage("Отменить"),
|
||||
"unifiedDelay": MessageLookupByLibrary.simpleMessage(
|
||||
"Унифицированная задержка",
|
||||
),
|
||||
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Убрать дополнительные задержки, такие как рукопожатие",
|
||||
),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("Неизвестно"),
|
||||
"update": MessageLookupByLibrary.simpleMessage("Обновить"),
|
||||
"upload": MessageLookupByLibrary.simpleMessage("Загрузка"),
|
||||
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
||||
"urlDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Получить профиль через URL",
|
||||
),
|
||||
"useHosts": MessageLookupByLibrary.simpleMessage("Использовать hosts"),
|
||||
"useSystemHosts": MessageLookupByLibrary.simpleMessage(
|
||||
"Использовать системные hosts",
|
||||
),
|
||||
"value": MessageLookupByLibrary.simpleMessage("Значение"),
|
||||
"valueExists": MessageLookupByLibrary.simpleMessage(
|
||||
"Текущее значение уже существует",
|
||||
),
|
||||
"view": MessageLookupByLibrary.simpleMessage("Просмотр"),
|
||||
"vpnDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Изменение настроек, связанных с VPN",
|
||||
),
|
||||
"vpnEnableDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Автоматически направляет весь системный трафик через VpnService",
|
||||
),
|
||||
"vpnSystemProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Прикрепить HTTP-прокси к VpnService",
|
||||
),
|
||||
"vpnTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Изменения вступят в силу после перезапуска VPN",
|
||||
),
|
||||
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage(
|
||||
"Конфигурация WebDAV",
|
||||
),
|
||||
"whitelistMode": MessageLookupByLibrary.simpleMessage(
|
||||
"Режим белого списка",
|
||||
),
|
||||
"years": MessageLookupByLibrary.simpleMessage("Лет"),
|
||||
"zh_CN": MessageLookupByLibrary.simpleMessage("Упрощенный китайский"),
|
||||
};
|
||||
}
|
||||
@@ -20,6 +20,8 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'zh_CN';
|
||||
|
||||
static String m0(count) => "已选择 ${count} 项";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"about": MessageLookupByLibrary.simpleMessage("关于"),
|
||||
@@ -40,6 +42,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"action_tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
|
||||
"action_view": MessageLookupByLibrary.simpleMessage("显示/隐藏"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("添加"),
|
||||
"addRule": MessageLookupByLibrary.simpleMessage("添加规则"),
|
||||
"addedOriginRules": MessageLookupByLibrary.simpleMessage("附加到原始规则"),
|
||||
"address": MessageLookupByLibrary.simpleMessage("地址"),
|
||||
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
|
||||
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
|
||||
@@ -76,6 +80,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"通过WebDAV或者文件同步数据",
|
||||
),
|
||||
"backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"),
|
||||
"basicConfig": MessageLookupByLibrary.simpleMessage("基本配置"),
|
||||
"basicConfigDesc": MessageLookupByLibrary.simpleMessage("全局修改基本配置"),
|
||||
"bind": MessageLookupByLibrary.simpleMessage("绑定"),
|
||||
"blacklistMode": MessageLookupByLibrary.simpleMessage("黑名单模式"),
|
||||
"bypassDomain": MessageLookupByLibrary.simpleMessage("排除域名"),
|
||||
@@ -99,6 +105,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"connections": MessageLookupByLibrary.simpleMessage("连接"),
|
||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
||||
"content": MessageLookupByLibrary.simpleMessage("内容"),
|
||||
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容不能为空"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
||||
"copyEnvVar": MessageLookupByLibrary.simpleMessage("复制环境变量"),
|
||||
"copyLink": MessageLookupByLibrary.simpleMessage("复制链接"),
|
||||
@@ -119,10 +127,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
|
||||
"delete": MessageLookupByLibrary.simpleMessage("删除"),
|
||||
"deleteProfileTip": MessageLookupByLibrary.simpleMessage("确定要删除当前配置吗?"),
|
||||
"deleteRuleTip": MessageLookupByLibrary.simpleMessage("确定要删除选中的规则吗?"),
|
||||
"desc": MessageLookupByLibrary.simpleMessage(
|
||||
"基于ClashMeta的多平台代理客户端,简单易用,开源无广告。",
|
||||
),
|
||||
"detectionTip": MessageLookupByLibrary.simpleMessage("依赖第三方api仅供参考"),
|
||||
"detectionTip": MessageLookupByLibrary.simpleMessage("依赖第三方api,仅供参考"),
|
||||
"direct": MessageLookupByLibrary.simpleMessage("直连"),
|
||||
"disclaimer": MessageLookupByLibrary.simpleMessage("免责声明"),
|
||||
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -137,6 +146,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"download": MessageLookupByLibrary.simpleMessage("下载"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("编辑"),
|
||||
"en": MessageLookupByLibrary.simpleMessage("英语"),
|
||||
"enableOverride": MessageLookupByLibrary.simpleMessage("启用覆写"),
|
||||
"entries": MessageLookupByLibrary.simpleMessage("个条目"),
|
||||
"exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"),
|
||||
"excludeDesc": MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"),
|
||||
@@ -162,15 +172,16 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"fileIsUpdate": MessageLookupByLibrary.simpleMessage("文件有修改,是否保存修改"),
|
||||
"filterSystemApp": MessageLookupByLibrary.simpleMessage("过滤系统应用"),
|
||||
"findProcessMode": MessageLookupByLibrary.simpleMessage("查找进程"),
|
||||
"findProcessModeDesc": MessageLookupByLibrary.simpleMessage("开启后存在闪退风险"),
|
||||
"findProcessModeDesc": MessageLookupByLibrary.simpleMessage("开启后会有一定性能损耗"),
|
||||
"fontFamily": MessageLookupByLibrary.simpleMessage("字体"),
|
||||
"fourColumns": MessageLookupByLibrary.simpleMessage("四列"),
|
||||
"general": MessageLookupByLibrary.simpleMessage("基础"),
|
||||
"generalDesc": MessageLookupByLibrary.simpleMessage("覆写基础设置"),
|
||||
"general": MessageLookupByLibrary.simpleMessage("常规"),
|
||||
"generalDesc": MessageLookupByLibrary.simpleMessage("修改通用设置"),
|
||||
"geoData": MessageLookupByLibrary.simpleMessage("地理数据"),
|
||||
"geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低内存模式"),
|
||||
"geodataLoaderDesc": MessageLookupByLibrary.simpleMessage("开启将使用Geo低内存加载器"),
|
||||
"geoipCode": MessageLookupByLibrary.simpleMessage("Geoip代码"),
|
||||
"getOriginRules": MessageLookupByLibrary.simpleMessage("获取原始规则"),
|
||||
"global": MessageLookupByLibrary.simpleMessage("全局"),
|
||||
"go": MessageLookupByLibrary.simpleMessage("前往"),
|
||||
"goDownload": MessageLookupByLibrary.simpleMessage("前往下载"),
|
||||
@@ -192,13 +203,16 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
|
||||
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"),
|
||||
"ja": MessageLookupByLibrary.simpleMessage("日语"),
|
||||
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
|
||||
"keepAliveIntervalDesc": MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
|
||||
"key": MessageLookupByLibrary.simpleMessage("键"),
|
||||
"keyExists": MessageLookupByLibrary.simpleMessage("当前键已存在"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("语言"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("布局"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("浅色"),
|
||||
"list": MessageLookupByLibrary.simpleMessage("列表"),
|
||||
"listen": MessageLookupByLibrary.simpleMessage("监听"),
|
||||
"local": MessageLookupByLibrary.simpleMessage("本地"),
|
||||
"localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"),
|
||||
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),
|
||||
@@ -236,6 +250,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"noNetwork": MessageLookupByLibrary.simpleMessage("无网络"),
|
||||
"noProxy": MessageLookupByLibrary.simpleMessage("暂无代理"),
|
||||
"noProxyDesc": MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"),
|
||||
"noResolve": MessageLookupByLibrary.simpleMessage("不解析IP"),
|
||||
"none": MessageLookupByLibrary.simpleMessage("无"),
|
||||
"notEmpty": MessageLookupByLibrary.simpleMessage("不能为空"),
|
||||
"notSelectedTip": MessageLookupByLibrary.simpleMessage("当前代理组无法选中"),
|
||||
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage("暂无连接"),
|
||||
@@ -259,6 +275,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"overrideDesc": MessageLookupByLibrary.simpleMessage("覆写代理相关配置"),
|
||||
"overrideDns": MessageLookupByLibrary.simpleMessage("覆写DNS"),
|
||||
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage("开启后将覆盖配置中的DNS选项"),
|
||||
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("覆盖原始规则"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("密码"),
|
||||
"passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("粘贴"),
|
||||
@@ -304,13 +321,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
|
||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
|
||||
"proxyProviders": MessageLookupByLibrary.simpleMessage("代理提供者"),
|
||||
"prueBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
|
||||
"pureBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
|
||||
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),
|
||||
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
|
||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
|
||||
"redo": MessageLookupByLibrary.simpleMessage("重做"),
|
||||
"regExp": MessageLookupByLibrary.simpleMessage("正则"),
|
||||
"remote": MessageLookupByLibrary.simpleMessage("远程"),
|
||||
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
|
||||
@@ -331,13 +349,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"routeMode": MessageLookupByLibrary.simpleMessage("路由模式"),
|
||||
"routeMode_bypassPrivate": MessageLookupByLibrary.simpleMessage("绕过私有路由地址"),
|
||||
"routeMode_config": MessageLookupByLibrary.simpleMessage("使用配置"),
|
||||
"ru": MessageLookupByLibrary.simpleMessage("俄语"),
|
||||
"rule": MessageLookupByLibrary.simpleMessage("规则"),
|
||||
"ruleName": MessageLookupByLibrary.simpleMessage("规则名称"),
|
||||
"ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage("规则提供者不能为空"),
|
||||
"ruleProviders": MessageLookupByLibrary.simpleMessage("规则提供者"),
|
||||
"ruleTarget": MessageLookupByLibrary.simpleMessage("规则目标"),
|
||||
"ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage("规则目标不能为空"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("保存"),
|
||||
"saveChanges": MessageLookupByLibrary.simpleMessage("是否保存更改?"),
|
||||
"saveTip": MessageLookupByLibrary.simpleMessage("确定要保存吗?"),
|
||||
"search": MessageLookupByLibrary.simpleMessage("搜索"),
|
||||
"seconds": MessageLookupByLibrary.simpleMessage("秒"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("全选"),
|
||||
"selected": MessageLookupByLibrary.simpleMessage("已选择"),
|
||||
"selectedCountTitle": m0,
|
||||
"settings": MessageLookupByLibrary.simpleMessage("设置"),
|
||||
"show": MessageLookupByLibrary.simpleMessage("显示"),
|
||||
"shrink": MessageLookupByLibrary.simpleMessage("紧凑"),
|
||||
@@ -346,6 +372,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"size": MessageLookupByLibrary.simpleMessage("尺寸"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("排序"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("来源"),
|
||||
"sourceIp": MessageLookupByLibrary.simpleMessage("源IP"),
|
||||
"stackMode": MessageLookupByLibrary.simpleMessage("栈模式"),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("标准"),
|
||||
"start": MessageLookupByLibrary.simpleMessage("启动"),
|
||||
@@ -355,6 +382,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"stop": MessageLookupByLibrary.simpleMessage("暂停"),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("风格"),
|
||||
"subRule": MessageLookupByLibrary.simpleMessage("子规则"),
|
||||
"subRuleEmptyTip": MessageLookupByLibrary.simpleMessage("子规则内容不能为空"),
|
||||
"submit": MessageLookupByLibrary.simpleMessage("提交"),
|
||||
"sync": MessageLookupByLibrary.simpleMessage("同步"),
|
||||
"system": MessageLookupByLibrary.simpleMessage("系统"),
|
||||
@@ -363,9 +392,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"systemProxyDesc": MessageLookupByLibrary.simpleMessage("设置系统代理"),
|
||||
"tab": MessageLookupByLibrary.simpleMessage("标签页"),
|
||||
"tabAnimation": MessageLookupByLibrary.simpleMessage("选项卡动画"),
|
||||
"tabAnimationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"开启后,主页选项卡将添加切换动画",
|
||||
),
|
||||
"tabAnimationDesc": MessageLookupByLibrary.simpleMessage("仅在移动视图中有效"),
|
||||
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"),
|
||||
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许TCP并发"),
|
||||
"testUrl": MessageLookupByLibrary.simpleMessage("测速链接"),
|
||||
@@ -386,6 +413,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"无法更新当前配置文件",
|
||||
),
|
||||
"undo": MessageLookupByLibrary.simpleMessage("撤销"),
|
||||
"unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"),
|
||||
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("未知"),
|
||||
@@ -396,6 +424,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"useHosts": MessageLookupByLibrary.simpleMessage("使用Hosts"),
|
||||
"useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"),
|
||||
"value": MessageLookupByLibrary.simpleMessage("值"),
|
||||
"valueExists": MessageLookupByLibrary.simpleMessage("当前值已存在"),
|
||||
"view": MessageLookupByLibrary.simpleMessage("查看"),
|
||||
"vpnDesc": MessageLookupByLibrary.simpleMessage("修改VPN相关设置"),
|
||||
"vpnEnableDesc": MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
@@ -255,6 +255,16 @@ class AppLocalizations {
|
||||
return Intl.message('English', name: 'en', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Japanese`
|
||||
String get ja {
|
||||
return Intl.message('Japanese', name: 'ja', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Russian`
|
||||
String get ru {
|
||||
return Intl.message('Russian', name: 'ru', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Simplified Chinese`
|
||||
String get zh_CN {
|
||||
return Intl.message(
|
||||
@@ -935,16 +945,6 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `When enabled, the home tab will add a toggle animation`
|
||||
String get tabAnimationDesc {
|
||||
return Intl.message(
|
||||
'When enabled, the home tab will add a toggle animation',
|
||||
name: 'tabAnimationDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.`
|
||||
String get desc {
|
||||
return Intl.message(
|
||||
@@ -1425,16 +1425,6 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `There is a risk of flashback after opening`
|
||||
String get findProcessModeDesc {
|
||||
return Intl.message(
|
||||
'There is a risk of flashback after opening',
|
||||
name: 'findProcessModeDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Init`
|
||||
String get init {
|
||||
return Intl.message('Init', name: 'init', desc: '', args: []);
|
||||
@@ -1670,10 +1660,10 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Auto lose connections`
|
||||
/// `Auto close connections`
|
||||
String get autoCloseConnections {
|
||||
return Intl.message(
|
||||
'Auto lose connections',
|
||||
'Auto close connections',
|
||||
name: 'autoCloseConnections',
|
||||
desc: '',
|
||||
args: [],
|
||||
@@ -1720,11 +1710,11 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Prue black mode`
|
||||
String get prueBlackMode {
|
||||
/// `Pure black mode`
|
||||
String get pureBlackMode {
|
||||
return Intl.message(
|
||||
'Prue black mode',
|
||||
name: 'prueBlackMode',
|
||||
'Pure black mode',
|
||||
name: 'pureBlackMode',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
@@ -1920,16 +1910,6 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Overwrite general settings`
|
||||
String get generalDesc {
|
||||
return Intl.message(
|
||||
'Overwrite general settings',
|
||||
name: 'generalDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Update DNS related settings`
|
||||
String get dnsDesc {
|
||||
return Intl.message(
|
||||
@@ -2679,6 +2659,251 @@ class AppLocalizations {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Listen`
|
||||
String get listen {
|
||||
return Intl.message('Listen', name: 'listen', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `The current key already exists`
|
||||
String get keyExists {
|
||||
return Intl.message(
|
||||
'The current key already exists',
|
||||
name: 'keyExists',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `The current value already exists`
|
||||
String get valueExists {
|
||||
return Intl.message(
|
||||
'The current value already exists',
|
||||
name: 'valueExists',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `undo`
|
||||
String get undo {
|
||||
return Intl.message('undo', name: 'undo', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `redo`
|
||||
String get redo {
|
||||
return Intl.message('redo', name: 'redo', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `none`
|
||||
String get none {
|
||||
return Intl.message('none', name: 'none', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Basic configuration`
|
||||
String get basicConfig {
|
||||
return Intl.message(
|
||||
'Basic configuration',
|
||||
name: 'basicConfig',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Modify the basic configuration globally`
|
||||
String get basicConfigDesc {
|
||||
return Intl.message(
|
||||
'Modify the basic configuration globally',
|
||||
name: 'basicConfigDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `{count} items have been selected`
|
||||
String selectedCountTitle(Object count) {
|
||||
return Intl.message(
|
||||
'$count items have been selected',
|
||||
name: 'selectedCountTitle',
|
||||
desc: '',
|
||||
args: [count],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Add rule`
|
||||
String get addRule {
|
||||
return Intl.message('Add rule', name: 'addRule', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Rule provider cannot be empty`
|
||||
String get ruleProviderEmptyTip {
|
||||
return Intl.message(
|
||||
'Rule provider cannot be empty',
|
||||
name: 'ruleProviderEmptyTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Rule name`
|
||||
String get ruleName {
|
||||
return Intl.message('Rule name', name: 'ruleName', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Content`
|
||||
String get content {
|
||||
return Intl.message('Content', name: 'content', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Content cannot be empty`
|
||||
String get contentEmptyTip {
|
||||
return Intl.message(
|
||||
'Content cannot be empty',
|
||||
name: 'contentEmptyTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Sub rule`
|
||||
String get subRule {
|
||||
return Intl.message('Sub rule', name: 'subRule', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Sub rule content cannot be empty`
|
||||
String get subRuleEmptyTip {
|
||||
return Intl.message(
|
||||
'Sub rule content cannot be empty',
|
||||
name: 'subRuleEmptyTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Rule target`
|
||||
String get ruleTarget {
|
||||
return Intl.message('Rule target', name: 'ruleTarget', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Rule target cannot be empty`
|
||||
String get ruleTargetEmptyTip {
|
||||
return Intl.message(
|
||||
'Rule target cannot be empty',
|
||||
name: 'ruleTargetEmptyTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Source IP`
|
||||
String get sourceIp {
|
||||
return Intl.message('Source IP', name: 'sourceIp', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `No resolve IP`
|
||||
String get noResolve {
|
||||
return Intl.message('No resolve IP', name: 'noResolve', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Get original rules`
|
||||
String get getOriginRules {
|
||||
return Intl.message(
|
||||
'Get original rules',
|
||||
name: 'getOriginRules',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Override the original rule`
|
||||
String get overrideOriginRules {
|
||||
return Intl.message(
|
||||
'Override the original rule',
|
||||
name: 'overrideOriginRules',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Attach on the original rules`
|
||||
String get addedOriginRules {
|
||||
return Intl.message(
|
||||
'Attach on the original rules',
|
||||
name: 'addedOriginRules',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Enable override`
|
||||
String get enableOverride {
|
||||
return Intl.message(
|
||||
'Enable override',
|
||||
name: 'enableOverride',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Are you sure you want to delete the selected rule?`
|
||||
String get deleteRuleTip {
|
||||
return Intl.message(
|
||||
'Are you sure you want to delete the selected rule?',
|
||||
name: 'deleteRuleTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Do you want to save the changes?`
|
||||
String get saveChanges {
|
||||
return Intl.message(
|
||||
'Do you want to save the changes?',
|
||||
name: 'saveChanges',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Modify general settings`
|
||||
String get generalDesc {
|
||||
return Intl.message(
|
||||
'Modify general settings',
|
||||
name: 'generalDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `There is a certain performance loss after opening`
|
||||
String get findProcessModeDesc {
|
||||
return Intl.message(
|
||||
'There is a certain performance loss after opening',
|
||||
name: 'findProcessModeDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Effective only in mobile view`
|
||||
String get tabAnimationDesc {
|
||||
return Intl.message(
|
||||
'Effective only in mobile view',
|
||||
name: 'tabAnimationDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Are you sure you want to save?`
|
||||
String get saveTip {
|
||||
return Intl.message(
|
||||
'Are you sure you want to save?',
|
||||
name: 'saveTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
@@ -2687,6 +2912,8 @@ class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
List<Locale> get supportedLocales {
|
||||
return const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en'),
|
||||
Locale.fromSubtags(languageCode: 'ja'),
|
||||
Locale.fromSubtags(languageCode: 'ru'),
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'CN'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ Future<void> main() async {
|
||||
await globalState.initApp(version);
|
||||
await android?.init();
|
||||
await window?.init(version);
|
||||
globalState.isPre = const String.fromEnvironment("APP_ENV") != 'stable';
|
||||
HttpOverrides.global = FlClashHttpOverrides();
|
||||
runApp(ProviderScope(
|
||||
child: const Application(),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -19,6 +21,7 @@ class _AppStateManagerState extends State<AppStateManager>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@@ -56,3 +59,24 @@ class _AppStateManagerState extends State<AppStateManager>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppEnvManager extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const AppEnvManager({
|
||||
super.key,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (globalState.isPre) {
|
||||
return Banner(
|
||||
message: 'PRE',
|
||||
location: BannerLocation.topEnd,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ export 'app_state_manager.dart';
|
||||
export 'vpn_manager.dart';
|
||||
export 'proxy_manager.dart';
|
||||
export 'connectivity_manager.dart';
|
||||
export 'message_manager.dart';
|
||||
export 'message_manager.dart';
|
||||
export 'theme_manager.dart';
|
||||
38
lib/manager/theme_manager.dart
Normal file
38
lib/manager/theme_manager.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'package:fl_clash/common/constant.dart';
|
||||
import 'package:fl_clash/common/measure.dart';
|
||||
import 'package:fl_clash/common/theme.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ThemeManager extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const ThemeManager({
|
||||
super.key,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
globalState.measure = Measure.of(context);
|
||||
globalState.theme = CommonTheme.of(context);
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaler: TextScaler.linear(
|
||||
textScaleFactor,
|
||||
),
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (_, container) {
|
||||
globalState.appController.updateViewSize(
|
||||
Size(
|
||||
container.maxWidth,
|
||||
container.maxHeight,
|
||||
),
|
||||
);
|
||||
return child;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
@@ -24,6 +25,7 @@ class WindowManager extends ConsumerStatefulWidget {
|
||||
|
||||
class _WindowContainerState extends ConsumerState<WindowManager>
|
||||
with WindowListener, WindowExtListener {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
@@ -271,7 +273,7 @@ class _WindowHeaderState extends State<WindowHeader> {
|
||||
_updateMaximized();
|
||||
},
|
||||
child: Container(
|
||||
color: context.colorScheme.secondary.toSoft,
|
||||
color: context.colorScheme.secondary.opacity15,
|
||||
alignment: Alignment.centerLeft,
|
||||
height: kHeaderHeight,
|
||||
),
|
||||
|
||||
@@ -18,7 +18,7 @@ class AppState with _$AppState {
|
||||
@Default([]) List<Package> packages,
|
||||
@Default(ColorSchemes()) ColorSchemes colorSchemes,
|
||||
@Default(0) int sortNum,
|
||||
required double viewWidth,
|
||||
required Size viewSize,
|
||||
@Default({}) DelayMap delayMap,
|
||||
@Default([]) List<Group> groups,
|
||||
@Default(0) int checkIpNum,
|
||||
@@ -35,7 +35,7 @@ class AppState with _$AppState {
|
||||
}
|
||||
|
||||
extension AppStateExt on AppState {
|
||||
ViewMode get viewMode => other.getViewMode(viewWidth);
|
||||
ViewMode get viewMode => other.getViewMode(viewSize.width);
|
||||
|
||||
bool get isStart => runTime != null;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class ProxyGroup with _$ProxyGroup {
|
||||
String? filter,
|
||||
@JsonKey(name: "expected-filter") String? excludeFilter,
|
||||
@JsonKey(name: "exclude-type") String? excludeType,
|
||||
@JsonKey(name: "expected-status") int? expectedStatus,
|
||||
@JsonKey(name: "expected-status") dynamic expectedStatus,
|
||||
bool? hidden,
|
||||
String? icon,
|
||||
}) = _ProxyGroup;
|
||||
@@ -132,6 +132,16 @@ class ProxyGroup with _$ProxyGroup {
|
||||
_$ProxyGroupFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RuleProvider with _$RuleProvider {
|
||||
const factory RuleProvider({
|
||||
required String name,
|
||||
}) = _RuleProvider;
|
||||
|
||||
factory RuleProvider.fromJson(Map<String, Object?> json) =>
|
||||
_$RuleProviderFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class Tun with _$Tun {
|
||||
const factory Tun({
|
||||
@@ -179,6 +189,7 @@ class FallbackFilter with _$FallbackFilter {
|
||||
class Dns with _$Dns {
|
||||
const factory Dns({
|
||||
@Default(true) bool enable,
|
||||
@Default("0.0.0.0:1053") String listen,
|
||||
@Default(false) @JsonKey(name: "prefer-h3") bool preferH3,
|
||||
@Default(true) @JsonKey(name: "use-hosts") bool useHosts,
|
||||
@Default(true) @JsonKey(name: "use-system-hosts") bool useSystemHosts,
|
||||
@@ -245,7 +256,7 @@ class GeoXUrl with _$GeoXUrl {
|
||||
)
|
||||
String mmdb,
|
||||
@Default(
|
||||
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
|
||||
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb",
|
||||
)
|
||||
String asn,
|
||||
@Default(
|
||||
@@ -273,6 +284,142 @@ class GeoXUrl with _$GeoXUrl {
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ParsedRule with _$ParsedRule {
|
||||
const factory ParsedRule({
|
||||
required RuleAction ruleAction,
|
||||
String? content,
|
||||
String? ruleTarget,
|
||||
String? ruleProvider,
|
||||
String? subRule,
|
||||
@Default(false) bool noResolve,
|
||||
@Default(false) bool src,
|
||||
}) = _ParsedRule;
|
||||
|
||||
factory ParsedRule.parseString(String value) {
|
||||
final splits = value.split(",");
|
||||
final shortSplits = splits
|
||||
.where(
|
||||
(item) => !item.contains("src") && !item.contains("no-resolve"),
|
||||
)
|
||||
.toList();
|
||||
final ruleAction = RuleAction.values.firstWhere(
|
||||
(item) => item.value == shortSplits.first,
|
||||
orElse: () => RuleAction.DOMAIN,
|
||||
);
|
||||
String? subRule;
|
||||
String? ruleTarget;
|
||||
|
||||
if (ruleAction == RuleAction.SUB_RULE) {
|
||||
subRule = shortSplits.last;
|
||||
} else {
|
||||
ruleTarget = shortSplits.last;
|
||||
}
|
||||
|
||||
String? content;
|
||||
String? ruleProvider;
|
||||
|
||||
if (ruleAction == RuleAction.RULE_SET) {
|
||||
ruleProvider = shortSplits.sublist(1, shortSplits.length - 1).join(",");
|
||||
} else {
|
||||
content = shortSplits.sublist(1, shortSplits.length - 1).join(",");
|
||||
}
|
||||
|
||||
return ParsedRule(
|
||||
ruleAction: ruleAction,
|
||||
content: content,
|
||||
src: splits.contains("src"),
|
||||
ruleProvider: ruleProvider,
|
||||
noResolve: splits.contains("no-resolve"),
|
||||
subRule: subRule,
|
||||
ruleTarget: ruleTarget,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension ParsedRuleExt on ParsedRule {
|
||||
String get value {
|
||||
return [
|
||||
ruleAction.value,
|
||||
ruleAction == RuleAction.RULE_SET ? ruleProvider : content,
|
||||
ruleAction == RuleAction.SUB_RULE ? subRule : ruleTarget,
|
||||
if (ruleAction.hasParams) ...[
|
||||
if (src) "src",
|
||||
if (noResolve) "no-resolve",
|
||||
]
|
||||
].join(",");
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class Rule with _$Rule {
|
||||
const factory Rule({
|
||||
required String id,
|
||||
required String value,
|
||||
}) = _Rule;
|
||||
|
||||
factory Rule.value(String value) {
|
||||
return Rule(
|
||||
value: value,
|
||||
id: other.uuidV4,
|
||||
);
|
||||
}
|
||||
|
||||
factory Rule.fromJson(Map<String, Object?> json) => _$RuleFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SubRule with _$SubRule {
|
||||
const factory SubRule({
|
||||
required String name,
|
||||
}) = _SubRule;
|
||||
|
||||
factory SubRule.fromJson(Map<String, Object?> json) =>
|
||||
_$SubRuleFromJson(json);
|
||||
}
|
||||
|
||||
_genRule(List<dynamic>? rules) {
|
||||
if (rules == null) {
|
||||
return [];
|
||||
}
|
||||
return rules
|
||||
.map(
|
||||
(item) => Rule.value(item),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<RuleProvider> _genRuleProviders(Map<String, dynamic> json) {
|
||||
return json.entries.map((entry) => RuleProvider(name: entry.key)).toList();
|
||||
}
|
||||
|
||||
List<SubRule> _genSubRules(Map<String, dynamic> json) {
|
||||
return json.entries
|
||||
.map(
|
||||
(entry) => SubRule(
|
||||
name: entry.key,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ClashConfigSnippet with _$ClashConfigSnippet {
|
||||
const factory ClashConfigSnippet({
|
||||
@Default([]) @JsonKey(name: "proxy-groups") List<ProxyGroup> proxyGroups,
|
||||
@JsonKey(fromJson: _genRule) @Default([]) List<Rule> rule,
|
||||
@JsonKey(name: "rule-providers", fromJson: _genRuleProviders)
|
||||
@Default([])
|
||||
List<RuleProvider> ruleProvider,
|
||||
@JsonKey(name: "sub-rules", fromJson: _genSubRules)
|
||||
@Default([])
|
||||
List<SubRule> subRules,
|
||||
}) = _ClashConfigSnippet;
|
||||
|
||||
factory ClashConfigSnippet.fromJson(Map<String, Object?> json) =>
|
||||
_$ClashConfigSnippetFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ClashConfig with _$ClashConfig {
|
||||
const factory ClashConfig({
|
||||
@@ -301,7 +448,7 @@ class ClashConfig with _$ClashConfig {
|
||||
@JsonKey(name: "geodata-loader")
|
||||
GeodataLoader geodataLoader,
|
||||
@Default([]) @JsonKey(name: "proxy-groups") List<ProxyGroup> proxyGroups,
|
||||
@Default([]) List<String> rules,
|
||||
@Default([]) List<String> rule,
|
||||
@JsonKey(name: "global-ua") String? globalUa,
|
||||
@Default(ExternalControllerStatus.close)
|
||||
@JsonKey(name: "external-controller")
|
||||
|
||||
@@ -382,7 +382,7 @@ extension ColorSchemesExt on ColorSchemes {
|
||||
);
|
||||
}
|
||||
return lightColorScheme != null
|
||||
? ColorScheme.fromSeed(seedColor: lightColorScheme!.primary)
|
||||
? ColorScheme.fromSeed(seedColor: lightColorScheme!.primary,dynamicSchemeVariant: DynamicSchemeVariant.vibrant)
|
||||
: ColorScheme.fromSeed(seedColor: defaultPrimaryColor);
|
||||
}
|
||||
}
|
||||
@@ -481,13 +481,13 @@ class Field with _$Field {
|
||||
}) = _Field;
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
enum PopupMenuItemType {
|
||||
primary,
|
||||
danger,
|
||||
}
|
||||
|
||||
class ActionItemData {
|
||||
const ActionItemData({
|
||||
class PopupMenuItemData {
|
||||
const PopupMenuItemData({
|
||||
this.icon,
|
||||
required this.label,
|
||||
required this.onPressed,
|
||||
@@ -497,7 +497,7 @@ class ActionItemData {
|
||||
|
||||
final double? iconSize;
|
||||
final String label;
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback? onPressed;
|
||||
final IconData? icon;
|
||||
final ActionType? type;
|
||||
final PopupMenuItemType? type;
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
// ignore_for_file: invalid_annotation_target
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -10,7 +8,6 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'models.dart';
|
||||
|
||||
part 'generated/config.freezed.dart';
|
||||
|
||||
part 'generated/config.g.dart';
|
||||
|
||||
const defaultBypassDomain = [
|
||||
@@ -39,15 +36,10 @@ const defaultNetworkProps = NetworkProps();
|
||||
const defaultProxiesStyle = ProxiesStyle();
|
||||
const defaultWindowProps = WindowProps();
|
||||
const defaultAccessControl = AccessControl();
|
||||
final defaultThemeProps = Platform.isWindows
|
||||
? ThemeProps().copyWith(
|
||||
fontFamily: FontFamily.miSans,
|
||||
primaryColor: defaultPrimaryColor.value,
|
||||
)
|
||||
: ThemeProps().copyWith(
|
||||
primaryColor: defaultPrimaryColor.value,
|
||||
themeMode: ThemeMode.dark,
|
||||
);
|
||||
final defaultThemeProps = ThemeProps().copyWith(
|
||||
primaryColor: defaultPrimaryColor.toARGB32(),
|
||||
themeMode: ThemeMode.dark,
|
||||
);
|
||||
|
||||
const List<DashboardWidget> defaultDashboardWidgets = [
|
||||
DashboardWidget.networkSpeed,
|
||||
@@ -129,7 +121,7 @@ extension AccessControlExt on AccessControl {
|
||||
@freezed
|
||||
class WindowProps with _$WindowProps {
|
||||
const factory WindowProps({
|
||||
@Default(900) double width,
|
||||
@Default(750) double width,
|
||||
@Default(600) double height,
|
||||
double? top,
|
||||
double? left,
|
||||
@@ -185,8 +177,7 @@ class ThemeProps with _$ThemeProps {
|
||||
const factory ThemeProps({
|
||||
int? primaryColor,
|
||||
@Default(ThemeMode.system) ThemeMode themeMode,
|
||||
@Default(false) bool prueBlack,
|
||||
@Default(FontFamily.system) FontFamily fontFamily,
|
||||
@Default(false) bool pureBlack,
|
||||
}) = _ThemeProps;
|
||||
|
||||
factory ThemeProps.fromJson(Map<String, Object?> json) =>
|
||||
@@ -244,5 +235,4 @@ extension ConfigExt on Config {
|
||||
Profile? get currentProfile {
|
||||
return profiles.getProfile(currentProfileId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ class ConfigExtendedParams with _$ConfigExtendedParams {
|
||||
@JsonKey(name: "is-patch") required bool isPatch,
|
||||
@JsonKey(name: "selected-map") required SelectedMap selectedMap,
|
||||
@JsonKey(name: "override-dns") required bool overrideDns,
|
||||
@JsonKey(name: "override-rule") required bool overrideRule,
|
||||
@JsonKey(name: "test-url") required String testUrl,
|
||||
}) = _ConfigExtendedParams;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ mixin _$AppState {
|
||||
List<Package> get packages => throw _privateConstructorUsedError;
|
||||
ColorSchemes get colorSchemes => throw _privateConstructorUsedError;
|
||||
int get sortNum => throw _privateConstructorUsedError;
|
||||
double get viewWidth => throw _privateConstructorUsedError;
|
||||
Size get viewSize => throw _privateConstructorUsedError;
|
||||
Map<String, Map<String, int?>> get delayMap =>
|
||||
throw _privateConstructorUsedError;
|
||||
List<Group> get groups => throw _privateConstructorUsedError;
|
||||
@@ -54,7 +54,7 @@ abstract class $AppStateCopyWith<$Res> {
|
||||
List<Package> packages,
|
||||
ColorSchemes colorSchemes,
|
||||
int sortNum,
|
||||
double viewWidth,
|
||||
Size viewSize,
|
||||
Map<String, Map<String, int?>> delayMap,
|
||||
List<Group> groups,
|
||||
int checkIpNum,
|
||||
@@ -91,7 +91,7 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
|
||||
Object? packages = null,
|
||||
Object? colorSchemes = null,
|
||||
Object? sortNum = null,
|
||||
Object? viewWidth = null,
|
||||
Object? viewSize = null,
|
||||
Object? delayMap = null,
|
||||
Object? groups = null,
|
||||
Object? checkIpNum = null,
|
||||
@@ -126,10 +126,10 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
|
||||
? _value.sortNum
|
||||
: sortNum // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
viewWidth: null == viewWidth
|
||||
? _value.viewWidth
|
||||
: viewWidth // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
viewSize: null == viewSize
|
||||
? _value.viewSize
|
||||
: viewSize // ignore: cast_nullable_to_non_nullable
|
||||
as Size,
|
||||
delayMap: null == delayMap
|
||||
? _value.delayMap
|
||||
: delayMap // ignore: cast_nullable_to_non_nullable
|
||||
@@ -206,7 +206,7 @@ abstract class _$$AppStateImplCopyWith<$Res>
|
||||
List<Package> packages,
|
||||
ColorSchemes colorSchemes,
|
||||
int sortNum,
|
||||
double viewWidth,
|
||||
Size viewSize,
|
||||
Map<String, Map<String, int?>> delayMap,
|
||||
List<Group> groups,
|
||||
int checkIpNum,
|
||||
@@ -242,7 +242,7 @@ class __$$AppStateImplCopyWithImpl<$Res>
|
||||
Object? packages = null,
|
||||
Object? colorSchemes = null,
|
||||
Object? sortNum = null,
|
||||
Object? viewWidth = null,
|
||||
Object? viewSize = null,
|
||||
Object? delayMap = null,
|
||||
Object? groups = null,
|
||||
Object? checkIpNum = null,
|
||||
@@ -277,10 +277,10 @@ class __$$AppStateImplCopyWithImpl<$Res>
|
||||
? _value.sortNum
|
||||
: sortNum // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
viewWidth: null == viewWidth
|
||||
? _value.viewWidth
|
||||
: viewWidth // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
viewSize: null == viewSize
|
||||
? _value.viewSize
|
||||
: viewSize // ignore: cast_nullable_to_non_nullable
|
||||
as Size,
|
||||
delayMap: null == delayMap
|
||||
? _value._delayMap
|
||||
: delayMap // ignore: cast_nullable_to_non_nullable
|
||||
@@ -342,7 +342,7 @@ class _$AppStateImpl implements _AppState {
|
||||
final List<Package> packages = const [],
|
||||
this.colorSchemes = const ColorSchemes(),
|
||||
this.sortNum = 0,
|
||||
required this.viewWidth,
|
||||
required this.viewSize,
|
||||
final Map<String, Map<String, int?>> delayMap = const {},
|
||||
final List<Group> groups = const [],
|
||||
this.checkIpNum = 0,
|
||||
@@ -382,7 +382,7 @@ class _$AppStateImpl implements _AppState {
|
||||
@JsonKey()
|
||||
final int sortNum;
|
||||
@override
|
||||
final double viewWidth;
|
||||
final Size viewSize;
|
||||
final Map<String, Map<String, int?>> _delayMap;
|
||||
@override
|
||||
@JsonKey()
|
||||
@@ -432,7 +432,7 @@ class _$AppStateImpl implements _AppState {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppState(isInit: $isInit, pageLabel: $pageLabel, packages: $packages, colorSchemes: $colorSchemes, sortNum: $sortNum, viewWidth: $viewWidth, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic)';
|
||||
return 'AppState(isInit: $isInit, pageLabel: $pageLabel, packages: $packages, colorSchemes: $colorSchemes, sortNum: $sortNum, viewSize: $viewSize, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -447,8 +447,8 @@ class _$AppStateImpl implements _AppState {
|
||||
(identical(other.colorSchemes, colorSchemes) ||
|
||||
other.colorSchemes == colorSchemes) &&
|
||||
(identical(other.sortNum, sortNum) || other.sortNum == sortNum) &&
|
||||
(identical(other.viewWidth, viewWidth) ||
|
||||
other.viewWidth == viewWidth) &&
|
||||
(identical(other.viewSize, viewSize) ||
|
||||
other.viewSize == viewSize) &&
|
||||
const DeepCollectionEquality().equals(other._delayMap, _delayMap) &&
|
||||
const DeepCollectionEquality().equals(other._groups, _groups) &&
|
||||
(identical(other.checkIpNum, checkIpNum) ||
|
||||
@@ -477,7 +477,7 @@ class _$AppStateImpl implements _AppState {
|
||||
const DeepCollectionEquality().hash(_packages),
|
||||
colorSchemes,
|
||||
sortNum,
|
||||
viewWidth,
|
||||
viewSize,
|
||||
const DeepCollectionEquality().hash(_delayMap),
|
||||
const DeepCollectionEquality().hash(_groups),
|
||||
checkIpNum,
|
||||
@@ -507,7 +507,7 @@ abstract class _AppState implements AppState {
|
||||
final List<Package> packages,
|
||||
final ColorSchemes colorSchemes,
|
||||
final int sortNum,
|
||||
required final double viewWidth,
|
||||
required final Size viewSize,
|
||||
final Map<String, Map<String, int?>> delayMap,
|
||||
final List<Group> groups,
|
||||
final int checkIpNum,
|
||||
@@ -532,7 +532,7 @@ abstract class _AppState implements AppState {
|
||||
@override
|
||||
int get sortNum;
|
||||
@override
|
||||
double get viewWidth;
|
||||
Size get viewSize;
|
||||
@override
|
||||
Map<String, Map<String, int?>> get delayMap;
|
||||
@override
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user