Compare commits

...

15 Commits

Author SHA1 Message Date
chen08209
75af47aead Change flutter version 2024-08-15 14:34:02 +08:00
chen08209
8dafe3b0ec Support profiles sort
Support windows country flags display

Optimize proxies page and profiles page columns
2024-08-15 14:18:33 +08:00
chen08209
813198a21d Update flutter version 2024-08-11 17:45:57 +08:00
chen08209
68dd262fef Update version 2024-08-11 17:09:31 +08:00
chen08209
5ef020db73 Update timeout time 2024-08-11 17:08:51 +08:00
chen08209
e3c9035903 Update access control page
Fix bug
2024-08-11 17:08:46 +08:00
chen08209
7fc54c5295 Optimize provider page
Optimize delay test

Support local backup and recovery
2024-08-05 18:17:05 +08:00
chen08209
00a78b5fb4 Fix android tile service issues 2024-08-01 23:51:28 +08:00
chen08209
8cdaf30de0 Fix linux core build error 2024-07-31 21:24:31 +08:00
chen08209
f39b9cf933 Add proxy-only traffic statistics
Update core

Optimize more details
2024-07-31 21:05:16 +08:00
chen08209
9df1ff46c2 Merge pull request #140 from txyyh/main
添加自建 F-Droid 仓库相关 workflow
2024-07-29 16:48:04 +08:00
txyyh
fcbbbdc698 Rename readme fingerprint 2024-07-29 16:45:12 +08:00
txyyh
3ba8355772 Rename workflow deploy repo name 2024-07-29 16:42:25 +08:00
txyyh
f6b97f82ae Add download guide to README 2024-07-29 16:39:52 +08:00
txyyh
13ac20f273 Add push release files to fdroid-repo 2024-07-29 16:39:52 +08:00
101 changed files with 5493 additions and 2652 deletions

View File

@@ -87,7 +87,7 @@ jobs:
- name: Setup Flutter - name: Setup Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: '3.x' flutter-version: 3.22.x
channel: 'stable' channel: 'stable'
cache: true cache: true
@@ -136,8 +136,29 @@ jobs:
gitchangelog "${pre}.." >> release.md 2>&1 || echo "Error in gitchangelog" gitchangelog "${pre}.." >> release.md 2>&1 || echo "Error in gitchangelog"
echo -e "\n\n</details>" >> release.md echo -e "\n\n</details>" >> release.md
fi fi
- name: Release - name: Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: ./dist/* files: ./dist/*
body_path: './release.md' body_path: './release.md'
- name: Create Fdroid Source Dir
run: |
mkdir -p ./tmp
cp ./dist/*android-arm64-v8a* ./tmp/ || true
echo "Files copied successfully"
- name: Push to fdroid repo
uses: cpina/github-action-push-to-another-repository@v1.7.2
env:
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
with:
source-directory: ./tmp/
destination-github-username: chen08209
destination-repository-name: FlClash-fdroid-repo
user-name: 'github-actions[bot]'
user-email: 'github-actions[bot]@users.noreply.github.com'
target-branch: action-pr
commit-message: Update from ${{ github.ref_name }}
target-directory: /tmp/

View File

@@ -38,6 +38,10 @@ on Mobile:
✨ Support subscription link, Dark mode ✨ Support subscription link, Dark mode
## Download
<a href="https://chen08209.github.io/FlClash-fdroid-repo/repo?fingerprint=789D6D32668712EF7672F9E58DEEB15FBD6DCEEC5AE7A4371EA72F2AAE8A12FD"><img alt="Get it on F-Droid" src="snapshots/get-it-on-fdroid.svg" width="200px"/></a> <a href="https://github.com/chen08209/FlClash/releases"><img alt="Get it on GitHub" src="snapshots/get-it-on-github.svg" width="200px"/></a>
## Contact ## Contact
[Telegram](https://t.me/+G-veVtwBOl4wODc1) [Telegram](https://t.me/+G-veVtwBOl4wODc1)

View File

@@ -38,6 +38,11 @@ on Mobile:
✨ 支持一键导入订阅, 深色模式 ✨ 支持一键导入订阅, 深色模式
## Download
<a href="https://chen08209.github.io/FlClash-fdroid-repo/repo?fingerprint=789D6D32668712EF7672F9E58DEEB15FBD6DCEEC5AE7A4371EA72F2AAE8A12FD"><img alt="Get it on F-Droid" src="snapshots/get-it-on-fdroid.svg" width="200px"/></a> <a href="https://github.com/chen08209/FlClash/releases"><img alt="Get it on GitHub" src="snapshots/get-it-on-github.svg" width="200px"/></a>
## Contact ## Contact
[Telegram](https://t.me/+G-veVtwBOl4wODc1) [Telegram](https://t.me/+G-veVtwBOl4wODc1)

View File

@@ -102,6 +102,9 @@ flutter {
dependencies { dependencies {
implementation 'androidx.core:core-splashscreen:1.0.1' implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'com.google.code.gson:gson:2.10' implementation 'com.google.code.gson:gson:2.10'
implementation("com.android.tools.smali:smali-dexlib2:3.0.7") {
exclude group: "com.google.guava", module: "guava"
}
} }

View File

@@ -78,7 +78,8 @@
android:icon="@drawable/ic_stat_name" android:icon="@drawable/ic_stat_name"
android:foregroundServiceType="specialUse" android:foregroundServiceType="specialUse"
android:label="FlClash" android:label="FlClash"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
>
<intent-filter> <intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" /> <action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter> </intent-filter>
@@ -86,11 +87,35 @@
android:name="android.service.quicksettings.TOGGLEABLE_TILE" android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" /> android:value="true" />
</service> </service>
<provider
android:name=".FilesProvider"
android:authorities="${applicationId}.files"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS"
android:process=":background">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<service <service
android:name=".services.FlClashVpnService" android:name=".services.FlClashVpnService"
android:exported="false" android:exported="false"
android:foregroundServiceType="specialUse" android:foregroundServiceType="specialUse"
android:permission="android.permission.BIND_VPN_SERVICE"> android:permission="android.permission.BIND_VPN_SERVICE"
>
<intent-filter> <intent-filter>
<action android:name="android.net.VpnService" /> <action android:name="android.net.VpnService" />
</intent-filter> </intent-filter>

View File

@@ -0,0 +1,112 @@
package com.follow.clash
import android.database.Cursor
import android.database.MatrixCursor
import android.os.CancellationSignal
import android.os.ParcelFileDescriptor
import android.provider.DocumentsContract.Document
import android.provider.DocumentsContract.Root
import android.provider.DocumentsProvider
import java.io.File
import java.io.FileNotFoundException
class FilesProvider : DocumentsProvider() {
companion object {
private const val DEFAULT_ROOT_ID = "0"
private val DEFAULT_DOCUMENT_COLUMNS = arrayOf(
Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_MIME_TYPE,
Document.COLUMN_FLAGS,
Document.COLUMN_SIZE,
)
private val DEFAULT_ROOT_COLUMNS = arrayOf(
Root.COLUMN_ROOT_ID,
Root.COLUMN_FLAGS,
Root.COLUMN_ICON,
Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY,
Root.COLUMN_DOCUMENT_ID
)
}
override fun onCreate(): Boolean {
return true
}
override fun queryRoots(projection: Array<String>?): Cursor {
return MatrixCursor(projection ?: DEFAULT_ROOT_COLUMNS).apply {
newRow().apply {
add(Root.COLUMN_ROOT_ID, DEFAULT_ROOT_ID)
add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY)
add(Root.COLUMN_ICON, R.mipmap.ic_launcher)
add(Root.COLUMN_TITLE, context!!.getString(R.string.fl_clash))
add(Root.COLUMN_SUMMARY, "Data")
add(Root.COLUMN_DOCUMENT_ID, "/")
}
}
}
override fun queryChildDocuments(
parentDocumentId: String,
projection: Array<String>?,
sortOrder: String?
): Cursor {
val result = MatrixCursor(resolveDocumentProjection(projection))
val parentFile = if (parentDocumentId == "/") {
context?.filesDir
} else {
File(parentDocumentId)
} ?: throw FileNotFoundException("Parent directory not found")
parentFile.listFiles()?.forEach { file ->
includeFile(result, file)
}
return result
}
override fun queryDocument(documentId: String, projection: Array<String>?): Cursor {
val result = MatrixCursor(resolveDocumentProjection(projection))
val file = File(documentId)
includeFile(result, file)
return result
}
override fun openDocument(
documentId: String,
mode: String,
signal: CancellationSignal?
): ParcelFileDescriptor {
val file = File(documentId)
val accessMode = ParcelFileDescriptor.parseMode(mode)
return ParcelFileDescriptor.open(file, accessMode)
}
private fun includeFile(result: MatrixCursor, file: File) {
result.newRow().apply {
add(Document.COLUMN_DOCUMENT_ID, file.absolutePath)
add(Document.COLUMN_DISPLAY_NAME, file.name)
add(Document.COLUMN_SIZE, file.length())
add(
Document.COLUMN_FLAGS,
Document.FLAG_SUPPORTS_WRITE or Document.FLAG_SUPPORTS_DELETE
)
add(Document.COLUMN_MIME_TYPE, getDocumentType(file))
}
}
private fun getDocumentType(file: File): String {
return if (file.isDirectory) {
Document.MIME_TYPE_DIR
} else {
"application/octet-stream"
}
}
private fun resolveDocumentProjection(projection: Array<String>?): Array<String> {
return projection ?: DEFAULT_DOCUMENT_COLUMNS
}
}

View File

@@ -57,8 +57,6 @@ object GlobalState {
serviceEngine?.dartExecutor?.executeDartEntrypoint( serviceEngine?.dartExecutor?.executeDartEntrypoint(
vpnService, vpnService,
) )
Log.e("FlClashVpnService", "initServiceEngine ===>")
} }
} }
} }

View File

@@ -1,7 +1,10 @@
package com.follow.clash.models package com.follow.clash.models
import java.util.Date
data class Package( data class Package(
val packageName: String, val packageName: String,
val label: String, val label: String,
val isSystem:Boolean val isSystem: Boolean,
val firstInstallTime: Long,
) )

View File

@@ -4,12 +4,16 @@ import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.ActivityManager import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.ComponentInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import com.android.tools.smali.dexlib2.dexbacked.DexBackedDexFile
import androidx.core.content.FileProvider
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import com.follow.clash.GlobalState import com.follow.clash.GlobalState
import com.follow.clash.extensions.getBase64 import com.follow.clash.extensions.getBase64
@@ -28,7 +32,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.util.zip.ZipFile
class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware { class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {
@@ -46,6 +52,62 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
private var connectivity: ConnectivityManager? = null private var connectivity: ConnectivityManager? = null
private val iconMap = mutableMapOf<String, String?>() private val iconMap = mutableMapOf<String, String?>()
private val packages = mutableListOf<Package>()
private val skipPrefixList = listOf(
"com.google",
"com.android.chrome",
"com.android.vending",
"com.microsoft",
"com.apple",
"com.zhiliaoapp.musically", // Banned by China
)
private val chinaAppPrefixList = listOf(
"com.tencent",
"com.alibaba",
"com.umeng",
"com.qihoo",
"com.ali",
"com.alipay",
"com.amap",
"com.sina",
"com.weibo",
"com.vivo",
"com.xiaomi",
"com.huawei",
"com.taobao",
"com.secneo",
"s.h.e.l.l",
"com.stub",
"com.kiwisec",
"com.secshell",
"com.wrapper",
"cn.securitystack",
"com.mogosec",
"com.secoen",
"com.netease",
"com.mx",
"com.qq.e",
"com.baidu",
"com.bytedance",
"com.bugly",
"com.miui",
"com.oppo",
"com.coloros",
"com.iqoo",
"com.meizu",
"com.gionee",
"cn.nubia",
"com.oplus",
"andes.oplus",
"com.unionpay",
"cn.wps"
)
private val chinaAppRegex by lazy {
("(" + chinaAppPrefixList.joinToString("|").replace(".", "\\.") + ").*").toRegex()
}
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
scope = CoroutineScope(Dispatchers.Default) scope = CoroutineScope(Dispatchers.Default)
@@ -61,7 +123,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
} }
private fun tip(message: String?) { private fun tip(message: String?) {
if(GlobalState.flutterEngine == null){ if (GlobalState.flutterEngine == null) {
if (toast != null) { if (toast != null) {
toast!!.cancel() toast!!.cancel()
} }
@@ -85,7 +147,13 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
"getPackages" -> { "getPackages" -> {
scope.launch { scope.launch {
result.success(getPackages()) result.success(getPackagesToJson())
}
}
"getChinaPackageNames" -> {
scope.launch {
result.success(getChinaPackageNames())
} }
} }
@@ -164,12 +232,56 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
result.success(true) result.success(true)
} }
"openFile" -> {
val path = call.argument<String>("path")!!
openFile(path)
result.success(true)
}
else -> { else -> {
result.notImplemented(); result.notImplemented();
} }
} }
} }
private fun openFile(path: String) {
context?.let {
val file = File(path)
val uri = FileProvider.getUriForFile(
it,
"${it.packageName}.fileProvider",
file
)
val intent = Intent(Intent.ACTION_VIEW).setDataAndType(
uri,
"text/plain"
)
val flags =
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
val resInfoList = it.packageManager.queryIntentActivities(
intent, PackageManager.MATCH_DEFAULT_ONLY
)
for (resolveInfo in resInfoList) {
val packageName = resolveInfo.activityInfo.packageName
it.grantUriPermission(
packageName,
uri,
flags
)
}
try {
activity?.startActivity(intent)
} catch (e: Exception) {
println(e)
}
}
}
private fun updateExcludeFromRecents(value: Boolean?) { private fun updateExcludeFromRecents(value: Boolean?) {
if (context == null) return if (context == null) return
val am = getSystemService(context!!, ActivityManager::class.java) val am = getSystemService(context!!, ActivityManager::class.java)
@@ -201,26 +313,106 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
return iconMap[packageName] return iconMap[packageName]
} }
private suspend fun getPackages(): String { private fun getPackages(): List<Package> {
return withContext(Dispatchers.Default) { val packageManager = context?.packageManager
val packageManager = context?.packageManager if (packages.isNotEmpty()) return packages;
val packages: List<Package>? = packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter {
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter { it.packageName != context?.packageName
it.packageName != context?.packageName || it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|| it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true || it.packageName == "android"
|| it.packageName == "android"
}?.map { }?.map {
Package( Package(
packageName = it.packageName, packageName = it.packageName,
label = it.applicationInfo.loadLabel(packageManager).toString(), label = it.applicationInfo.loadLabel(packageManager).toString(),
isSystem = (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 1 isSystem = (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 1,
) firstInstallTime = it.firstInstallTime
} )
}?.let { packages.addAll(it) }
return packages;
}
private suspend fun getPackagesToJson(): String {
return withContext(Dispatchers.Default) {
Gson().toJson(getPackages())
}
}
private suspend fun getChinaPackageNames(): String {
return withContext(Dispatchers.Default) {
val packages: List<String> =
getPackages().map { it.packageName }.filter { isChinaPackage(it) }
Gson().toJson(packages) Gson().toJson(packages)
} }
} }
private fun isChinaPackage(packageName: String): Boolean {
val packageManager = context?.packageManager ?: return false
skipPrefixList.forEach {
if (packageName == it || packageName.startsWith("$it.")) return false
}
val packageManagerFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PackageManager.MATCH_UNINSTALLED_PACKAGES or PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
} else {
@Suppress("DEPRECATION")
PackageManager.GET_UNINSTALLED_PACKAGES or PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
}
if (packageName.matches(chinaAppRegex)) {
return true
}
try {
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getPackageInfo(
packageName,
PackageManager.PackageInfoFlags.of(packageManagerFlags.toLong())
)
} else {
@Suppress("DEPRECATION") packageManager.getPackageInfo(
packageName, packageManagerFlags
)
}
mutableListOf<ComponentInfo>().apply {
packageInfo.services?.let { addAll(it) }
packageInfo.activities?.let { addAll(it) }
packageInfo.receivers?.let { addAll(it) }
packageInfo.providers?.let { addAll(it) }
}.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
}
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
}
}
}
} catch (_: Exception) {
return false
}
return false
}
fun requestGc() { fun requestGc() {
channel.invokeMethod("gc", null) channel.invokeMethod("gc", null)
} }
@@ -241,4 +433,4 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
channel.invokeMethod("exit", null) channel.invokeMethod("exit", null)
activity = null activity = null
} }
} }

View File

@@ -0,0 +1,6 @@
<paths>
<files-path
name="files"
path="."/>
</paths>

Binary file not shown.

View File

@@ -2,19 +2,24 @@ package main
import "C" import "C"
import ( import (
"context"
"errors"
"github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/outboundgroup"
ap "github.com/metacubex/mihomo/adapter/provider" "github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/common/batch"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant"
cp "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/hub" "github.com/metacubex/mihomo/hub"
"github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/hub/route" "github.com/metacubex/mihomo/hub/route"
"github.com/metacubex/mihomo/listener" "github.com/metacubex/mihomo/listener"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
rp "github.com/metacubex/mihomo/rules/provider"
"github.com/metacubex/mihomo/tunnel" "github.com/metacubex/mihomo/tunnel"
"os" "os"
"os/exec" "os/exec"
@@ -26,40 +31,40 @@ import (
"time" "time"
) )
type healthCheckSchema struct { //type healthCheckSchema struct {
Enable bool `provider:"enable"` // Enable bool `provider:"enable"`
URL string `provider:"url"` // URL string `provider:"url"`
Interval int `provider:"interval"` // Interval int `provider:"interval"`
TestTimeout int `provider:"timeout,omitempty"` // TestTimeout int `provider:"timeout,omitempty"`
Lazy bool `provider:"lazy,omitempty"` // Lazy bool `provider:"lazy,omitempty"`
ExpectedStatus string `provider:"expected-status,omitempty"` // ExpectedStatus string `provider:"expected-status,omitempty"`
} //}
type proxyProviderSchema struct { //type proxyProviderSchema struct {
Type string `provider:"type"` // Type string `provider:"type"`
Path string `provider:"path,omitempty"` // Path string `provider:"path,omitempty"`
URL string `provider:"url,omitempty"` // URL string `provider:"url,omitempty"`
Proxy string `provider:"proxy,omitempty"` // Proxy string `provider:"proxy,omitempty"`
Interval int `provider:"interval,omitempty"` // Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"` // Filter string `provider:"filter,omitempty"`
ExcludeFilter string `provider:"exclude-filter,omitempty"` // ExcludeFilter string `provider:"exclude-filter,omitempty"`
ExcludeType string `provider:"exclude-type,omitempty"` // ExcludeType string `provider:"exclude-type,omitempty"`
DialerProxy string `provider:"dialer-proxy,omitempty"` // DialerProxy string `provider:"dialer-proxy,omitempty"`
//
HealthCheck healthCheckSchema `provider:"health-check,omitempty"` // HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
Override ap.OverrideSchema `provider:"override,omitempty"` // Override ap.OverrideSchema `provider:"override,omitempty"`
Header map[string][]string `provider:"header,omitempty"` // Header map[string][]string `provider:"header,omitempty"`
} //}
//
type ruleProviderSchema struct { //type ruleProviderSchema struct {
Type string `provider:"type"` // Type string `provider:"type"`
Behavior string `provider:"behavior"` // Behavior string `provider:"behavior"`
Path string `provider:"path,omitempty"` // Path string `provider:"path,omitempty"`
URL string `provider:"url,omitempty"` // URL string `provider:"url,omitempty"`
Proxy string `provider:"proxy,omitempty"` // Proxy string `provider:"proxy,omitempty"`
Format string `provider:"format,omitempty"` // Format string `provider:"format,omitempty"`
Interval int `provider:"interval,omitempty"` // Interval int `provider:"interval,omitempty"`
} //}
type ConfigExtendedParams struct { type ConfigExtendedParams struct {
IsPatch bool `json:"is-patch"` IsPatch bool `json:"is-patch"`
@@ -69,9 +74,9 @@ type ConfigExtendedParams struct {
} }
type GenerateConfigParams struct { type GenerateConfigParams struct {
ProfilePath *string `json:"profile-path"` ProfileId string `json:"profile-id"`
Config config.RawConfig `json:"config" ` Config config.RawConfig `json:"config" `
Params ConfigExtendedParams `json:"params"` Params ConfigExtendedParams `json:"params"`
} }
type ChangeProxyParams struct { type ChangeProxyParams struct {
@@ -93,9 +98,19 @@ type ExternalProvider struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
VehicleType string `json:"vehicle-type"` VehicleType string `json:"vehicle-type"`
Count int `json:"count"`
Path string `json:"path"`
UpdateAt time.Time `json:"update-at"` UpdateAt time.Time `json:"update-at"`
} }
type ExternalProviders []ExternalProvider
func (a ExternalProviders) Len() int { return len(a) }
func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name }
func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
var b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
func restartExecutable(execPath string) { func restartExecutable(execPath string) {
var err error var err error
executor.Shutdown() executor.Shutdown()
@@ -145,26 +160,108 @@ func removeFile(path string) error {
return nil return nil
} }
func getRawConfigWithPath(path *string) *config.RawConfig { func getProfilePath(id string) string {
if path == nil { return filepath.Join(constant.Path.HomeDir(), "profiles", id+".yaml")
}
func getProfileProvidersPath(id string) string {
return filepath.Join(constant.Path.HomeDir(), "providers", id)
}
func getRawConfigWithId(id string) *config.RawConfig {
path := getProfilePath(id)
bytes, err := readFile(path)
if err != nil {
log.Errorln("profile is not exist")
return config.DefaultRawConfig() return config.DefaultRawConfig()
} else { }
bytes, err := readFile(*path) prof, err := config.UnmarshalRawConfig(bytes)
if err != nil { if err != nil {
log.Errorln("getProfile readFile error %v", err) log.Errorln("unmarshalRawConfig error %v", err)
return config.DefaultRawConfig() return config.DefaultRawConfig()
}
for _, mapping := range prof.ProxyProvider {
value, exist := mapping["path"].(string)
if !exist {
continue
} }
prof, err := config.UnmarshalRawConfig(bytes) mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
if err != nil { }
log.Errorln("getProfile UnmarshalRawConfig error %v", err) for _, mapping := range prof.RuleProvider {
return config.DefaultRawConfig() value, exist := mapping["path"].(string)
if !exist {
continue
} }
return prof mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
}
return prof
}
func getExternalProvidersRaw() map[string]cp.Provider {
eps := make(map[string]cp.Provider)
for n, p := range tunnel.Providers() {
if p.VehicleType() != cp.Compatible {
eps[n] = p
}
}
for n, p := range tunnel.RuleProviders() {
if p.VehicleType() != cp.Compatible {
eps[n] = p
}
}
return eps
}
func toExternalProvider(p cp.Provider) (*ExternalProvider, error) {
switch p.(type) {
case *provider.ProxySetProvider:
psp := p.(*provider.ProxySetProvider)
return &ExternalProvider{
Name: psp.Name(),
Type: psp.Type().String(),
VehicleType: psp.VehicleType().String(),
Count: psp.Count(),
Path: psp.Vehicle().Path(),
UpdateAt: psp.UpdatedAt,
}, nil
case *rp.RuleSetProvider:
rsp := p.(*rp.RuleSetProvider)
return &ExternalProvider{
Name: rsp.Name(),
Type: rsp.Type().String(),
VehicleType: rsp.VehicleType().String(),
Count: rsp.Count(),
Path: rsp.Vehicle().Path(),
UpdateAt: rsp.UpdatedAt,
}, nil
default:
return nil, errors.New("not external provider")
} }
} }
func decorationConfig(profilePath *string, cfg config.RawConfig) *config.RawConfig { func sideUpdateExternalProvider(p cp.Provider, bytes []byte) error {
prof := getRawConfigWithPath(profilePath) switch p.(type) {
case *provider.ProxySetProvider:
psp := p.(*provider.ProxySetProvider)
elm, same, err := psp.SideUpdate(bytes)
if err == nil && !same {
psp.OnUpdate(elm)
}
return nil
case rp.RuleSetProvider:
rsp := p.(*rp.RuleSetProvider)
elm, same, err := rsp.SideUpdate(bytes)
if err == nil && !same {
rsp.OnUpdate(elm)
}
return nil
default:
return errors.New("not external provider")
}
}
func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig {
prof := getRawConfigWithId(profileId)
overwriteConfig(prof, cfg) overwriteConfig(prof, cfg)
return prof return prof
} }
@@ -327,6 +424,7 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
targetConfig.LogLevel = patchConfig.LogLevel targetConfig.LogLevel = patchConfig.LogLevel
targetConfig.Port = 0 targetConfig.Port = 0
targetConfig.SocksPort = 0 targetConfig.SocksPort = 0
targetConfig.KeepAliveInterval = patchConfig.KeepAliveInterval
targetConfig.MixedPort = patchConfig.MixedPort targetConfig.MixedPort = patchConfig.MixedPort
targetConfig.FindProcessMode = patchConfig.FindProcessMode targetConfig.FindProcessMode = patchConfig.FindProcessMode
targetConfig.AllowLan = patchConfig.AllowLan targetConfig.AllowLan = patchConfig.AllowLan
@@ -410,7 +508,7 @@ func patchSelectGroup() {
var applyLock sync.Mutex var applyLock sync.Mutex
func applyConfig() { func applyConfig() error {
applyLock.Lock() applyLock.Lock()
defer applyLock.Unlock() defer applyLock.Unlock()
cfg, err := config.ParseRawConfig(currentConfig) cfg, err := config.ParseRawConfig(currentConfig)
@@ -428,4 +526,6 @@ func applyConfig() {
hub.UltraApplyConfig(cfg, true) hub.UltraApplyConfig(cfg, true)
patchSelectGroup() patchSelectGroup()
} }
externalProviders = getExternalProvidersRaw()
return err
} }

View File

@@ -16,7 +16,6 @@ require (
github.com/3andne/restls-client-go v0.1.6 // indirect github.com/3andne/restls-client-go v0.1.6 // indirect
github.com/RyuaNerin/go-krypto v1.2.4 // indirect github.com/RyuaNerin/go-krypto v1.2.4 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/ajg/form v1.5.1 // indirect github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect github.com/andybalholm/brotli v1.0.6 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect
@@ -46,22 +45,23 @@ require (
github.com/hashicorp/yamux v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6 // indirect github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/chacha v0.1.0 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect
github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e // indirect github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e // indirect
github.com/metacubex/randv2 v0.2.0 // indirect github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 // indirect github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 // indirect
github.com/metacubex/sing-shadowsocks v0.2.6 // indirect github.com/metacubex/sing-shadowsocks v0.2.7 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.0 // indirect github.com/metacubex/sing-shadowsocks2 v0.2.1 // indirect
github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e // indirect github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d // indirect
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f // indirect github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a // indirect github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a // indirect
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 // indirect github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 // indirect
github.com/metacubex/utls v1.6.6 // indirect github.com/metacubex/utls v1.6.6 // indirect
@@ -76,9 +76,10 @@ require (
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
github.com/sagernet/fswatch v0.1.1 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/sing v0.5.0-alpha.10 // indirect github.com/sagernet/sing v0.5.0-alpha.13 // indirect
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
@@ -96,13 +97,14 @@ require (
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.24.0 // indirect golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/sys v0.21.0 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.22.0 // indirect golang.org/x/tools v0.22.0 // indirect

View File

@@ -7,8 +7,6 @@ github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4
github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8= github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
@@ -90,8 +88,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -108,6 +106,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc=
github.com/metacubex/chacha v0.1.0/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 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
@@ -118,14 +118,14 @@ github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiL
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew=
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= github.com/metacubex/sing-shadowsocks v0.2.7 h1:9f3Dt2+71TNp0e202llA2ug5h/rkWs2EZxQ5IMpf+9g=
github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= github.com/metacubex/sing-shadowsocks v0.2.7/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A= github.com/metacubex/sing-shadowsocks2 v0.2.1 h1:XIZBXlazp8EEoPp1S0DViAhLkJakjQ2f+AOwwdKKNYg=
github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8= github.com/metacubex/sing-shadowsocks2 v0.2.1/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e h1:o+zohxPRo45P35fS9u1zfdBgr+L/7S0ObGU6YjbVBIc= github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d h1:iYlepjRCYlPXtELupDL+pQjGqkCnQz4KQOfKImP9sog=
github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e/go.mod h1:WwJGbCx7bQcBzuQXiDOJvZH27R0kIjKNNlISIWsL6kM= github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a h1:NpSGclHJUYndUwBmyIpFBSoBVg8PoVX7QQKhYg0DjM0= github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a h1:NpSGclHJUYndUwBmyIpFBSoBVg8PoVX7QQKhYg0DjM0=
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo= github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c=
@@ -166,13 +166,15 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.5.0-alpha.10 h1:kuHl10gpjbKQAdQfyogQU3u0CVnpqC3wrAHe/+BFaXc= github.com/sagernet/sing v0.5.0-alpha.13 h1:fpR4TFZfu/9V3LbHSAnnnwcaXGMF8ijmAAPoY2WHSKw=
github.com/sagernet/sing v0.5.0-alpha.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.5.0-alpha.13/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14= github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
@@ -218,6 +220,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
@@ -257,8 +261,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -11,21 +11,19 @@ import (
"github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/adapter/provider" "github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/component/mmdb"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant"
cp "github.com/metacubex/mihomo/constant/provider" cp "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
rp "github.com/metacubex/mihomo/rules/provider"
"github.com/metacubex/mihomo/tunnel" "github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic" "github.com/metacubex/mihomo/tunnel/statistic"
"golang.org/x/net/context" "golang.org/x/net/context"
"os" "os"
"runtime" "runtime"
"sort"
"time" "time"
"unsafe" "unsafe"
) )
@@ -34,9 +32,9 @@ var currentConfig = config.DefaultRawConfig()
var configParams = ConfigExtendedParams{} var configParams = ConfigExtendedParams{}
var isInit = false var externalProviders = map[string]cp.Provider{}
var currentProfileName = "" var isInit = false
//export initClash //export initClash
func initClash(homeDirStr *C.char) bool { func initClash(homeDirStr *C.char) bool {
@@ -76,16 +74,6 @@ func forceGc() {
}() }()
} }
//export setCurrentProfileName
func setCurrentProfileName(s *C.char) {
currentProfileName = C.GoString(s)
}
//export getCurrentProfileName
func getCurrentProfileName() *C.char {
return C.CString(currentProfileName)
}
//export validateConfig //export validateConfig
func validateConfig(s *C.char, port C.longlong) { func validateConfig(s *C.char, port C.longlong) {
i := int64(port) i := int64(port)
@@ -112,43 +100,23 @@ func updateConfig(s *C.char, port C.longlong) {
return return
} }
configParams = params.Params configParams = params.Params
prof := decorationConfig(params.ProfilePath, params.Config) prof := decorationConfig(params.ProfileId, params.Config)
currentConfig = prof currentConfig = prof
applyConfig() err = applyConfig()
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
bridge.SendToPort(i, "") bridge.SendToPort(i, "")
}() }()
} }
//export clearEffect //export clearEffect
func clearEffect(s *C.char) { func clearEffect(s *C.char) {
path := C.GoString(s) id := C.GoString(s)
go func() { go func() {
rawCfg := getRawConfigWithPath(&path) _ = removeFile(getProfilePath(id))
for _, mapping := range rawCfg.RuleProvider { _ = removeFile(getProfileProvidersPath(id))
schema := &ruleProviderSchema{}
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
if err := decoder.Decode(mapping, schema); err != nil {
return
}
if schema.Type == "http" {
_ = removeFile(constant.Path.Resolve(schema.Path))
}
}
for _, mapping := range rawCfg.ProxyProvider {
schema := &proxyProviderSchema{
HealthCheck: healthCheckSchema{
Lazy: true,
},
}
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
if err := decoder.Decode(mapping, schema); err != nil {
return
}
if schema.Type == "http" {
_ = removeFile(constant.Path.Resolve(schema.Path))
}
}
_ = removeFile(path)
}() }()
} }
@@ -164,35 +132,36 @@ func getProxies() *C.char {
//export changeProxy //export changeProxy
func changeProxy(s *C.char) { func changeProxy(s *C.char) {
paramsString := C.GoString(s) paramsString := C.GoString(s)
go func() { var params = &ChangeProxyParams{}
var params = &ChangeProxyParams{} err := json.Unmarshal([]byte(paramsString), params)
err := json.Unmarshal([]byte(paramsString), params) if err != nil {
if err != nil { log.Infoln("Unmarshal ChangeProxyParams %v", err)
log.Infoln("Unmarshal ChangeProxyParams %v", err) }
} groupName := *params.GroupName
groupName := *params.GroupName proxyName := *params.ProxyName
proxyName := *params.ProxyName proxies := tunnel.ProxiesWithProviders()
proxies := tunnel.ProxiesWithProviders() group, ok := proxies[groupName]
group, ok := proxies[groupName] if !ok {
if !ok { return
return }
} adapterProxy := group.(*adapter.Proxy)
adapterProxy := group.(*adapter.Proxy) selector, ok := adapterProxy.ProxyAdapter.(outboundgroup.SelectAble)
selector, ok := adapterProxy.ProxyAdapter.(*outboundgroup.Selector) if !ok {
if !ok { return
return }
} if proxyName == "" {
selector.ForceSet(proxyName)
} else {
err = selector.Set(proxyName) err = selector.Set(proxyName)
if err == nil { }
log.Infoln("[Selector] %s selected %s", groupName, proxyName) if err == nil {
} log.Infoln("[SelectAble] %s selected %s", groupName, proxyName)
}() }
} }
//export getTraffic //export getTraffic
func getTraffic() *C.char { func getTraffic() *C.char {
up, down := statistic.DefaultManager.Now() up, down := statistic.DefaultManager.Current(state.OnlyProxy)
traffic := map[string]int64{ traffic := map[string]int64{
"up": up, "up": up,
"down": down, "down": down,
@@ -207,7 +176,7 @@ func getTraffic() *C.char {
//export getTotalTraffic //export getTotalTraffic
func getTotalTraffic() *C.char { func getTotalTraffic() *C.char {
up, down := statistic.DefaultManager.Total() up, down := statistic.DefaultManager.Total(state.OnlyProxy)
traffic := map[string]int64{ traffic := map[string]int64{
"up": up, "up": up,
"down": down, "down": down,
@@ -229,16 +198,16 @@ func resetTraffic() {
func asyncTestDelay(s *C.char, port C.longlong) { func asyncTestDelay(s *C.char, port C.longlong) {
i := int64(port) i := int64(port)
paramsString := C.GoString(s) paramsString := C.GoString(s)
go func() { b.Go(paramsString, func() (bool, error) {
var params = &TestDelayParams{} var params = &TestDelayParams{}
err := json.Unmarshal([]byte(paramsString), params) err := json.Unmarshal([]byte(paramsString), params)
if err != nil { if err != nil {
return return false, nil
} }
expectedStatus, err := utils.NewUnsignedRanges[uint16]("") expectedStatus, err := utils.NewUnsignedRanges[uint16]("")
if err != nil { if err != nil {
return return false, nil
} }
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(params.Timeout)) ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(params.Timeout))
@@ -255,7 +224,7 @@ func asyncTestDelay(s *C.char, port C.longlong) {
delayData.Value = -1 delayData.Value = -1
data, _ := json.Marshal(delayData) data, _ := json.Marshal(delayData)
bridge.SendToPort(i, string(data)) bridge.SendToPort(i, string(data))
return return false, nil
} }
delay, err := proxy.URLTest(ctx, constant.DefaultTestURL, expectedStatus) delay, err := proxy.URLTest(ctx, constant.DefaultTestURL, expectedStatus)
@@ -263,14 +232,14 @@ func asyncTestDelay(s *C.char, port C.longlong) {
delayData.Value = -1 delayData.Value = -1
data, _ := json.Marshal(delayData) data, _ := json.Marshal(delayData)
bridge.SendToPort(i, string(data)) bridge.SendToPort(i, string(data))
return return false, nil
} }
delayData.Value = int32(delay) delayData.Value = int32(delay)
data, _ := json.Marshal(delayData) data, _ := json.Marshal(delayData)
bridge.SendToPort(i, string(data)) bridge.SendToPort(i, string(data))
return return false, nil
}() })
} }
//export getVersionInfo //export getVersionInfo
@@ -344,78 +313,67 @@ func getProvider(name *C.char) *C.char {
//export getExternalProviders //export getExternalProviders
func getExternalProviders() *C.char { func getExternalProviders() *C.char {
externalProviders := make([]ExternalProvider, 0) eps := make([]ExternalProvider, 0)
providers := tunnel.Providers() for _, p := range externalProviders {
for n, p := range providers { externalProvider, err := toExternalProvider(p)
if p.VehicleType() != cp.Compatible { if err != nil {
p := p.(*provider.ProxySetProvider) continue
externalProviders = append(externalProviders, ExternalProvider{
Name: n,
Type: p.Type().String(),
VehicleType: p.VehicleType().String(),
UpdateAt: p.UpdatedAt,
})
} }
eps = append(eps, *externalProvider)
} }
for n, p := range tunnel.RuleProviders() { sort.Sort(ExternalProviders(eps))
if p.VehicleType() != cp.Compatible { data, err := json.Marshal(eps)
p := p.(*rp.RuleSetProvider)
externalProviders = append(externalProviders, ExternalProvider{
Name: n,
Type: p.Type().String(),
VehicleType: p.VehicleType().String(),
UpdateAt: p.UpdatedAt,
})
}
}
data, err := json.Marshal(externalProviders)
if err != nil { if err != nil {
return C.CString("") return C.CString("")
} }
return C.CString(string(data)) return C.CString(string(data))
} }
//export updateExternalProvider //export getExternalProvider
func updateExternalProvider(providerName *C.char, providerType *C.char, port C.longlong) { func getExternalProvider(name *C.char) *C.char {
externalProviderName := C.GoString(name)
externalProvider, exist := externalProviders[externalProviderName]
if !exist {
return C.CString("")
}
e, err := toExternalProvider(externalProvider)
if err != nil {
return C.CString("")
}
data, err := json.Marshal(e)
if err != nil {
return C.CString("")
}
return C.CString(string(data))
}
//export updateGeoData
func updateGeoData(geoType *C.char, geoName *C.char, port C.longlong) {
i := int64(port) i := int64(port)
providerNameString := C.GoString(providerName) geoTypeString := C.GoString(geoType)
providerTypeString := C.GoString(providerType) geoNameString := C.GoString(geoName)
go func() { go func() {
switch providerTypeString { switch geoTypeString {
case "Proxy":
providers := tunnel.Providers()
err := providers[providerNameString].Update()
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
case "Rule":
providers := tunnel.RuleProviders()
err := providers[providerNameString].Update()
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
case "MMDB": case "MMDB":
err := mmdb.DownloadMMDB(constant.Path.Resolve(providerNameString)) err := updater.UpdateMMDB(constant.Path.Resolve(geoNameString))
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return
} }
case "ASN": case "ASN":
err := mmdb.DownloadASN(constant.Path.Resolve(providerNameString)) err := updater.UpdateASN(constant.Path.Resolve(geoNameString))
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return
} }
case "GeoIp": case "GeoIp":
err := geodata.DownloadGeoIP(constant.Path.Resolve(providerNameString)) err := updater.UpdateGeoIp(constant.Path.Resolve(geoNameString))
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return
} }
case "GeoSite": case "GeoSite":
err := geodata.DownloadGeoSite(constant.Path.Resolve(providerNameString)) err := updater.UpdateGeoSite(constant.Path.Resolve(geoNameString))
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return
@@ -425,6 +383,45 @@ func updateExternalProvider(providerName *C.char, providerType *C.char, port C.l
}() }()
} }
//export updateExternalProvider
func updateExternalProvider(providerName *C.char, port C.longlong) {
i := int64(port)
providerNameString := C.GoString(providerName)
go func() {
externalProvider, exist := externalProviders[providerNameString]
if !exist {
bridge.SendToPort(i, "external provider is not exist")
return
}
err := externalProvider.Update()
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
bridge.SendToPort(i, "")
}()
}
//export sideLoadExternalProvider
func sideLoadExternalProvider(providerName *C.char, data *C.char, port C.longlong) {
i := int64(port)
bytes := []byte(C.GoString(data))
providerNameString := C.GoString(providerName)
go func() {
externalProvider, exist := externalProviders[providerNameString]
if !exist {
bridge.SendToPort(i, "external provider is not exist")
return
}
err := sideUpdateExternalProvider(externalProvider, bytes)
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
bridge.SendToPort(i, "")
}()
}
//export initNativeApiBridge //export initNativeApiBridge
func initNativeApiBridge(api unsafe.Pointer) { func initNativeApiBridge(api unsafe.Pointer) {
bridge.InitDartApi(api) bridge.InitDartApi(api)
@@ -462,7 +459,7 @@ func init() {
Data: c, Data: c,
}) })
} }
executor.DefaultProxyProviderLoadedHook = func(providerName string) { executor.DefaultProviderLoadedHook = func(providerName string) {
SendMessage(Message{ SendMessage(Message{
Type: LoadedMessage, Type: LoadedMessage,
Data: providerName, Data: providerName,

View File

@@ -1,5 +1,3 @@
//go:build android
package main package main
import "C" import "C"
@@ -21,11 +19,18 @@ type AndroidProps struct {
SystemProxy bool `json:"systemProxy"` SystemProxy bool `json:"systemProxy"`
} }
var androidProps AndroidProps type State struct {
AndroidProps
CurrentProfileName string `json:"currentProfileName"`
MixedPort int `json:"mixedPort"`
OnlyProxy bool `json:"onlyProxy"`
}
//export getAndroidProps var state State
func getAndroidProps() *C.char {
data, err := json.Marshal(androidProps) //export getState
func getState() *C.char {
data, err := json.Marshal(state)
if err != nil { if err != nil {
fmt.Println("Error:", err) fmt.Println("Error:", err)
return C.CString("") return C.CString("")
@@ -33,10 +38,10 @@ func getAndroidProps() *C.char {
return C.CString(string(data)) return C.CString(string(data))
} }
//export setAndroidProps //export setState
func setAndroidProps(s *C.char) { func setState(s *C.char) {
paramsString := C.GoString(s) paramsString := C.GoString(s)
err := json.Unmarshal([]byte(paramsString), &androidProps) err := json.Unmarshal([]byte(paramsString), &state)
if err != nil { if err != nil {
return return
} }

View File

@@ -88,7 +88,6 @@ class ApplicationState extends State<Application> {
} }
await globalState.appController.init(); await globalState.appController.init();
globalState.appController.initLink(); globalState.appController.initLink();
_updateGroups();
}); });
} }
@@ -120,35 +119,22 @@ class ApplicationState extends State<Application> {
}); });
} }
_updateGroups() {
if (globalState.groupsUpdateTimer != null) {
globalState.groupsUpdateTimer?.cancel();
globalState.groupsUpdateTimer = null;
}
globalState.groupsUpdateTimer ??= Timer.periodic(
httpTimeoutDuration,
(timer) async {
await globalState.appController.updateGroups();
},
);
}
@override @override
Widget build(context) { Widget build(context) {
return AppStateContainer( return AppStateContainer(
child: ClashMessageContainer( child: ClashContainer(
child: Selector2<AppState, Config, ApplicationSelectorState>( child: Selector2<AppState, Config, ApplicationSelectorState>(
selector: (_, appState, config) => ApplicationSelectorState( selector: (_, appState, config) => ApplicationSelectorState(
locale: config.locale, locale: config.locale,
themeMode: config.themeMode, themeMode: config.themeMode,
primaryColor: config.primaryColor, primaryColor: config.primaryColor,
prueBlack: config.prueBlack,
), ),
builder: (_, state, child) { builder: (_, state, child) {
return DynamicColorBuilder( return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) { builder: (lightDynamic, darkDynamic) {
_updateSystemColorSchemes(lightDynamic, darkDynamic); _updateSystemColorSchemes(lightDynamic, darkDynamic);
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: globalState.navigatorKey, navigatorKey: globalState.navigatorKey,
localizationsDelegates: const [ localizationsDelegates: const [
AppLocalizations.delegate, AppLocalizations.delegate,
@@ -180,7 +166,7 @@ class ApplicationState extends State<Application> {
brightness: Brightness.dark, brightness: Brightness.dark,
systemColorSchemes: systemColorSchemes, systemColorSchemes: systemColorSchemes,
primaryColor: state.primaryColor, primaryColor: state.primaryColor,
), ).toPrueBlack(state.prueBlack),
), ),
home: child, home: child,
); );

View File

@@ -100,22 +100,6 @@ class ClashCore {
); );
} }
setProfileName(String profileName) {
final profileNameChar = profileName.toNativeUtf8().cast<Char>();
clashFFI.setCurrentProfileName(
profileNameChar,
);
malloc.free(profileNameChar);
}
getProfileName() {
final currentProfileNameRaw = clashFFI.getCurrentProfileName();
final currentProfileName =
currentProfileNameRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(currentProfileNameRaw);
return currentProfileName;
}
Future<List<Group>> getProxiesGroups() { Future<List<Group>> getProxiesGroups() {
final proxiesRaw = clashFFI.getProxies(); final proxiesRaw = clashFFI.getProxies();
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString(); final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
@@ -128,8 +112,7 @@ class ClashCore {
UsedProxy.GLOBAL.name, UsedProxy.GLOBAL.name,
...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) { ...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) {
final proxy = proxies[e] ?? {}; final proxy = proxies[e] ?? {};
return GroupTypeExtension.valueList.contains(proxy['type']) && return GroupTypeExtension.valueList.contains(proxy['type']);
proxy['hidden'] != true;
}) })
]; ];
final groupsRaw = groupNames.map((groupName) { final groupsRaw = groupNames.map((groupName) {
@@ -142,7 +125,11 @@ class ClashCore {
.toList(); .toList();
return group; return group;
}).toList(); }).toList();
return groupsRaw.map((e) => Group.fromJson(e)).toList(); return groupsRaw
.map(
(e) => Group.fromJson(e),
)
.toList();
}); });
} }
@@ -162,9 +149,46 @@ class ClashCore {
}); });
} }
Future<String> updateExternalProvider({ ExternalProvider? getExternalProvider(String externalProviderName) {
final externalProviderNameChar =
externalProviderName.toNativeUtf8().cast<Char>();
final externalProviderRaw =
clashFFI.getExternalProvider(externalProviderNameChar);
malloc.free(externalProviderNameChar);
final externalProviderRawString =
externalProviderRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(externalProviderRaw);
if(externalProviderRawString.isEmpty) return null;
return ExternalProvider.fromJson(json.decode(externalProviderRawString));
}
Future<String> updateGeoData({
required String geoType,
required String geoName,
}) {
final completer = Completer<String>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
completer.complete(message);
receiver.close();
}
});
final geoTypeChar = geoType.toNativeUtf8().cast<Char>();
final geoNameChar = geoName.toNativeUtf8().cast<Char>();
clashFFI.updateGeoData(
geoTypeChar,
geoNameChar,
receiver.sendPort.nativePort,
);
malloc.free(geoTypeChar);
malloc.free(geoNameChar);
return completer.future;
}
Future<String> sideLoadExternalProvider({
required String providerName, required String providerName,
required String providerType, required String data,
}) { }) {
final completer = Completer<String>(); final completer = Completer<String>();
final receiver = ReceivePort(); final receiver = ReceivePort();
@@ -175,14 +199,34 @@ class ClashCore {
} }
}); });
final providerNameChar = providerName.toNativeUtf8().cast<Char>(); final providerNameChar = providerName.toNativeUtf8().cast<Char>();
final providerTypeChar = providerType.toNativeUtf8().cast<Char>(); final dataChar = data.toNativeUtf8().cast<Char>();
clashFFI.updateExternalProvider( clashFFI.sideLoadExternalProvider(
providerNameChar,
dataChar,
receiver.sendPort.nativePort,
);
malloc.free(providerNameChar);
malloc.free(dataChar);
return completer.future;
}
Future<String> updateExternalProvider({
required String providerName,
}) {
final completer = Completer<String>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
completer.complete(message);
receiver.close();
}
});
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
clashFFI.updateExternalProvider(
providerNameChar, providerNameChar,
providerTypeChar,
receiver.sendPort.nativePort, receiver.sendPort.nativePort,
); );
malloc.free(providerNameChar); malloc.free(providerNameChar);
malloc.free(providerTypeChar);
return completer.future; return completer.future;
} }
@@ -213,21 +257,13 @@ class ClashCore {
receiver.sendPort.nativePort, receiver.sendPort.nativePort,
); );
malloc.free(delayParamsChar); malloc.free(delayParamsChar);
Future.delayed(httpTimeoutDuration + moreDuration, () {
receiver.close();
if (!completer.isCompleted) {
completer.complete(
Delay(name: proxyName, value: -1),
);
}
});
return completer.future; return completer.future;
} }
clearEffect(String path) { clearEffect(String profileId) {
final pathChar = path.toNativeUtf8().cast<Char>(); final profileIdChar = profileId.toNativeUtf8().cast<Char>();
clashFFI.clearEffect(pathChar); clashFFI.clearEffect(profileIdChar);
malloc.free(pathChar); malloc.free(profileIdChar);
} }
VersionInfo getVersionInfo() { VersionInfo getVersionInfo() {
@@ -237,19 +273,19 @@ class ClashCore {
return VersionInfo.fromJson(versionInfo); return VersionInfo.fromJson(versionInfo);
} }
setProps(Props props) { setState(CoreState state) {
final propsChar = json.encode(props).toNativeUtf8().cast<Char>(); final stateChar = json.encode(state).toNativeUtf8().cast<Char>();
clashFFI.setAndroidProps(propsChar); clashFFI.setState(stateChar);
malloc.free(propsChar); malloc.free(stateChar);
} }
Props getProps() { CoreState getState() {
final androidPropsRaw = clashFFI.getAndroidProps(); final stateRaw = clashFFI.getState();
final androidProps = json.decode( final state = json.decode(
androidPropsRaw.cast<Utf8>().toDartString(), stateRaw.cast<Utf8>().toDartString(),
); );
clashFFI.freeCString(androidPropsRaw); clashFFI.freeCString(stateRaw);
return Props.fromJson(androidProps); return CoreState.fromJson(state);
} }
Traffic getTraffic() { Traffic getTraffic() {

View File

@@ -5190,30 +5190,6 @@ class ClashFFI {
_lookup<ffi.NativeFunction<ffi.Void Function()>>('forceGc'); _lookup<ffi.NativeFunction<ffi.Void Function()>>('forceGc');
late final _forceGc = _forceGcPtr.asFunction<void Function()>(); late final _forceGc = _forceGcPtr.asFunction<void Function()>();
void setCurrentProfileName(
ffi.Pointer<ffi.Char> s,
) {
return _setCurrentProfileName(
s,
);
}
late final _setCurrentProfileNamePtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
'setCurrentProfileName');
late final _setCurrentProfileName = _setCurrentProfileNamePtr
.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
ffi.Pointer<ffi.Char> getCurrentProfileName() {
return _getCurrentProfileName();
}
late final _getCurrentProfileNamePtr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
'getCurrentProfileName');
late final _getCurrentProfileName =
_getCurrentProfileNamePtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
void validateConfig( void validateConfig(
ffi.Pointer<ffi.Char> s, ffi.Pointer<ffi.Char> s,
int port, int port,
@@ -5409,24 +5385,76 @@ class ClashFFI {
late final _getExternalProviders = late final _getExternalProviders =
_getExternalProvidersPtr.asFunction<ffi.Pointer<ffi.Char> Function()>(); _getExternalProvidersPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
ffi.Pointer<ffi.Char> getExternalProvider(
ffi.Pointer<ffi.Char> name,
) {
return _getExternalProvider(
name,
);
}
late final _getExternalProviderPtr = _lookup<
ffi.NativeFunction<
ffi.Pointer<ffi.Char> Function(
ffi.Pointer<ffi.Char>)>>('getExternalProvider');
late final _getExternalProvider = _getExternalProviderPtr
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
void updateGeoData(
ffi.Pointer<ffi.Char> geoType,
ffi.Pointer<ffi.Char> geoName,
int port,
) {
return _updateGeoData(
geoType,
geoName,
port,
);
}
late final _updateGeoDataPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.LongLong)>>('updateGeoData');
late final _updateGeoData = _updateGeoDataPtr.asFunction<
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
void updateExternalProvider( void updateExternalProvider(
ffi.Pointer<ffi.Char> providerName, ffi.Pointer<ffi.Char> providerName,
ffi.Pointer<ffi.Char> providerType,
int port, int port,
) { ) {
return _updateExternalProvider( return _updateExternalProvider(
providerName, providerName,
providerType,
port, port,
); );
} }
late final _updateExternalProviderPtr = _lookup< late final _updateExternalProviderPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Pointer<ffi.Char>, ffi.LongLong)>>('updateExternalProvider');
late final _updateExternalProvider = _updateExternalProviderPtr
.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
void sideLoadExternalProvider(
ffi.Pointer<ffi.Char> providerName,
ffi.Pointer<ffi.Char> data,
int port,
) {
return _sideLoadExternalProvider(
providerName,
data,
port,
);
}
late final _sideLoadExternalProviderPtr = _lookup<
ffi.NativeFunction< ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, ffi.Void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.LongLong)>>('updateExternalProvider'); ffi.LongLong)>>('sideLoadExternalProvider');
late final _updateExternalProvider = _updateExternalProviderPtr.asFunction< late final _sideLoadExternalProvider =
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>(); _sideLoadExternalProviderPtr.asFunction<
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
void initNativeApiBridge( void initNativeApiBridge(
ffi.Pointer<ffi.Void> api, ffi.Pointer<ffi.Void> api,
@@ -5499,29 +5527,28 @@ class ClashFFI {
late final _setProcessMap = late final _setProcessMap =
_setProcessMapPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>(); _setProcessMapPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
ffi.Pointer<ffi.Char> getAndroidProps() { ffi.Pointer<ffi.Char> getState() {
return _getAndroidProps(); return _getState();
} }
late final _getAndroidPropsPtr = late final _getStatePtr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>( _lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>('getState');
'getAndroidProps'); late final _getState =
late final _getAndroidProps = _getStatePtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
_getAndroidPropsPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
void setAndroidProps( void setState(
ffi.Pointer<ffi.Char> s, ffi.Pointer<ffi.Char> s,
) { ) {
return _setAndroidProps( return _setState(
s, s,
); );
} }
late final _setAndroidPropsPtr = late final _setStatePtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>( _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
'setAndroidProps'); 'setState');
late final _setAndroidProps = late final _setState =
_setAndroidPropsPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>(); _setStatePtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
void startTUN( void startTUN(
int fd, int fd,

28
lib/common/archive.dart Normal file
View File

@@ -0,0 +1,28 @@
import 'dart:convert';
import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:path/path.dart';
extension ArchiveExt on Archive {
addDirectoryToArchive(String dirPath, String parentPath) {
final dir = Directory(dirPath);
final entities = dir.listSync(recursive: false);
for (final entity in entities) {
final relativePath = relative(entity.path, from: parentPath);
if (entity is File) {
final data = entity.readAsBytesSync();
final archiveFile = ArchiveFile(relativePath, data.length, data);
addFile(archiveFile);
} else if (entity is Directory) {
addDirectoryToArchive(entity.path, parentPath);
}
}
}
add<T>(String name, T raw) {
final data = json.encode(raw);
addFile(
ArchiveFile(name, data.length, data),
);
}
}

View File

@@ -16,4 +16,21 @@ extension ColorExtension on Color {
toLittle() { toLittle() {
return withOpacity(0.03); return withOpacity(0.03);
} }
}
Color darken([double amount = .1]) {
assert(amount >= 0 && amount <= 1);
final hsl = HSLColor.fromColor(this);
final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0));
return hslDark.toColor();
}
}
extension ColorSchemeExtension on ColorScheme {
ColorScheme toPrueBlack(bool isPrueBlack) => isPrueBlack
? copyWith(
surface: Colors.black,
background: Colors.black,
surfaceContainer: surfaceContainer.darken(0.05),
)
: this;
}

View File

@@ -1,4 +1,3 @@
import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
@@ -8,7 +7,7 @@ import 'system.dart';
const appName = "FlClash"; const appName = "FlClash";
const coreName = "clash.meta"; const coreName = "clash.meta";
const packageName = "FlClash"; const packageName = "com.follow.clash";
const httpTimeoutDuration = Duration(milliseconds: 5000); const httpTimeoutDuration = Duration(milliseconds: 5000);
const moreDuration = Duration(milliseconds: 100); const moreDuration = Duration(milliseconds: 100);
const animateDuration = Duration(milliseconds: 100); const animateDuration = Duration(milliseconds: 100);
@@ -17,7 +16,7 @@ const mmdbFileName = "geoip.metadb";
const asnFileName = "ASN.mmdb"; const asnFileName = "ASN.mmdb";
const geoIpFileName = "GeoIP.dat"; const geoIpFileName = "GeoIP.dat";
const geoSiteFileName = "GeoSite.dat"; const geoSiteFileName = "GeoSite.dat";
final double kHeaderHeight = system.isDesktop ? (Platform.isMacOS ? 28 : 40) : 0; final double kHeaderHeight = system.isDesktop ? 40 : 0;
const GeoXMap defaultGeoXMap = { const GeoXMap defaultGeoXMap = {
"mmdb": "mmdb":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb", "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",

View File

@@ -1,11 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:typed_data';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:path/path.dart';
import 'package:webdav_client/webdav_client.dart'; import 'package:webdav_client/webdav_client.dart';
class DAVClient { class DAVClient {
@@ -33,8 +30,6 @@ class DAVClient {
Future<bool> _ping() async { Future<bool> _ping() async {
try { try {
await client.ping(); await client.ping();
await client.mkdir("/$appName");
await client.mkdir("/$appName/$profilesDirectoryName");
return true; return true;
} catch (_) { } catch (_) {
return false; return false;
@@ -43,65 +38,17 @@ class DAVClient {
get root => "/$appName"; get root => "/$appName";
get remoteConfig => "$root/$configKey.json"; get backupFile => "$root/backup.zip";
get remoteClashConfig => "$root/$clashConfigKey.json"; backup(Uint8List data) async {
get remoteProfiles => "$root/$profilesDirectoryName";
backup() async {
final appController = globalState.appController;
final config = appController.config;
final clashConfig = appController.clashConfig;
await client.mkdir("$root"); await client.mkdir("$root");
client.write( await client.write("$backupFile", data);
remoteConfig,
utf8.encode(
json.encode(config.toJson()),
),
);
client.write(
remoteClashConfig,
utf8.encode(
json.encode(clashConfig.toJson()),
),
);
await client.remove(remoteProfiles);
for (final profile in config.profiles) {
final path = await appPath.getProfilePath(profile.id);
if (path == null) continue;
await client.writeFromFile(
path,
"$remoteProfiles/${basename(path)}",
);
}
return true; return true;
} }
recovery({required RecoveryOption recoveryOption}) async { Future<List<int>> recovery() async {
final profiles = await client.readDir(remoteProfiles); await client.mkdir("$root");
final profilesPath = await appPath.getProfilesPath(); final data = await client.read(backupFile);
for (final file in profiles) { return data;
await client.read2File(
"$remoteProfiles/${file.name}",
join(
profilesPath,
file.name,
),
);
}
final configRaw = utf8.decode((await client.read(remoteConfig)));
final clashConfigRaw = utf8.decode(await client.read(remoteClashConfig));
final config = Config.fromJson(json.decode(configRaw));
final clashConfig = ClashConfig.fromJson(json.decode(clashConfigRaw));
if(recoveryOption == RecoveryOption.onlyProfiles){
globalState.appController.config.update(config, RecoveryOption.onlyProfiles);
}else{
globalState.appController.config.update(config, RecoveryOption.all);
globalState.appController.clashConfig.update(clashConfig);
}
await globalState.appController.applyProfile();
globalState.appController.savePreferences();
return true;
} }
} }

View File

@@ -44,7 +44,7 @@ class Navigation {
modes: [NavigationItemMode.desktop, NavigationItemMode.more], modes: [NavigationItemMode.desktop, NavigationItemMode.more],
), ),
const NavigationItem( const NavigationItem(
icon: Icon(Icons.swap_vert_circle), icon: Icon(Icons.storage),
label: "resources", label: "resources",
description: "resourcesDesc", description: "resourcesDesc",
keep: false, keep: false,

View File

@@ -1,11 +1,12 @@
import 'dart:io'; import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:fl_clash/common/app_localizations.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/constant.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lpinyin/lpinyin.dart';
import 'package:zxing2/qrcode.dart'; import 'package:zxing2/qrcode.dart';
import 'package:image/image.dart' as img; import 'package:image/image.dart' as img;
@@ -83,7 +84,7 @@ class Other {
if (charA == charB) { if (charA == charB) {
return sortByChar(a.substring(1), b.substring(1)); return sortByChar(a.substring(1), b.substring(1));
} else { } else {
return charA.compareTo(charB); return charA.compareToLower(charB);
} }
} }
@@ -191,22 +192,22 @@ class Other {
return ViewMode.desktop; return ViewMode.desktop;
} }
int getColumns(ViewMode viewMode, int currentColumns) {
final targetColumnsArray = viewModeColumnsMap[viewMode]!; int getProxiesColumns(double viewWidth, ProxiesLayout proxiesLayout) {
if (targetColumnsArray.contains(currentColumns)) { final columns = max((viewWidth / 300).ceil(), 2);
return currentColumns; return switch (proxiesLayout) {
} ProxiesLayout.tight => columns - 1,
return targetColumnsArray.first; ProxiesLayout.standard => columns,
ProxiesLayout.loose => columns + 1,
};
} }
String getColumnsTextForInt(int number){ int getProfilesColumns(double viewWidth) {
return switch(number){ return max((viewWidth / 400).floor(), 1);
1 => appLocalizations.oneColumn, }
2 => appLocalizations.twoColumns,
3 => appLocalizations.threeColumns, String getBackupFileName() {
4 => appLocalizations.fourColumns, return "${appName}_backup_${DateTime.now().show}.zip";
int() => throw UnimplementedError(),
};
} }
} }

View File

@@ -9,6 +9,7 @@ import 'constant.dart';
class AppPath { class AppPath {
static AppPath? _instance; static AppPath? _instance;
Completer<Directory> cacheDir = Completer(); Completer<Directory> cacheDir = Completer();
Completer<Directory> downloadDir = Completer();
// Future<Directory> _createDesktopCacheDir() async { // Future<Directory> _createDesktopCacheDir() async {
// final path = join(dirname(Platform.resolvedExecutable), 'cache'); // final path = join(dirname(Platform.resolvedExecutable), 'cache');
@@ -23,6 +24,9 @@ class AppPath {
getApplicationSupportDirectory().then((value) { getApplicationSupportDirectory().then((value) {
cacheDir.complete(value); cacheDir.complete(value);
}); });
getDownloadsDirectory().then((value) {
downloadDir.complete(value);
});
// if (Platform.isAndroid) { // if (Platform.isAndroid) {
// getApplicationSupportDirectory().then((value) { // getApplicationSupportDirectory().then((value) {
// cacheDir.complete(value); // cacheDir.complete(value);
@@ -39,6 +43,11 @@ class AppPath {
return _instance!; return _instance!;
} }
Future<String> getDownloadDirPath() async {
final directory = await downloadDir.future;
return directory.path;
}
Future<String> getHomeDirPath() async { Future<String> getHomeDirPath() async {
final directory = await cacheDir.future; final directory = await cacheDir.future;
return directory.path; return directory.path;

View File

@@ -1,22 +1,26 @@
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
class Picker { class Picker {
Future<PlatformFile?> pickerConfigFile() async { Future<PlatformFile?> pickerFile() async {
final filePickerResult = await FilePicker.platform.pickFiles( final filePickerResult = await FilePicker.platform.pickFiles(
withData: true, withData: true,
allowMultiple: false, allowMultiple: false,
initialDirectory: await appPath.getDownloadDirPath(),
); );
return filePickerResult?.files.first; return filePickerResult?.files.first;
} }
Future<PlatformFile?> pickerGeoDataFile() async { Future<String?> saveFile(String fileName,Uint8List bytes) async {
final filePickerResult = await FilePicker.platform.pickFiles( final path = await FilePicker.platform.saveFile(
withData: true, fileName: fileName,
allowMultiple: false, initialDirectory: await appPath.getDownloadDirPath(),
bytes: bytes,
); );
return filePickerResult?.files.first; return path;
} }
Future<String?> pickerConfigQRCode() async { Future<String?> pickerConfigQRCode() async {

View File

@@ -1,4 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio/io.dart'; import 'package:dio/io.dart';
@@ -13,9 +14,6 @@ class Request {
Request() { Request() {
_dio = Dio(); _dio = Dio();
_dio.options = BaseOptions(
headers: {"User-Agent": globalState.appController.clashConfig.globalUa},
);
_dio.interceptors.add( _dio.interceptors.add(
InterceptorsWrapper( InterceptorsWrapper(
onRequest: (options, handler) { onRequest: (options, handler) {
@@ -52,11 +50,14 @@ class Request {
.get( .get(
url, url,
options: Options( options: Options(
headers: {
"User-Agent": globalState.appController.clashConfig.globalUa
},
responseType: ResponseType.bytes, responseType: ResponseType.bytes,
), ),
) )
.timeout( .timeout(
httpTimeoutDuration * 2, httpTimeoutDuration * 6,
); );
return response; return response;
} }
@@ -85,11 +86,14 @@ class Request {
"https://ipinfo.io/json/": IpInfo.fromIpInfoIoJson, "https://ipinfo.io/json/": IpInfo.fromIpInfoIoJson,
}; };
Future<IpInfo?> checkIp(CancelToken? cancelToken) async { Future<IpInfo?> checkIp({CancelToken? cancelToken}) async {
for (final source in _ipInfoSources.entries) { for (final source in _ipInfoSources.entries.toList()..shuffle(Random())) {
try { try {
final response = await _dio final response = await _dio
.get<Map<String, dynamic>>(source.key, cancelToken: cancelToken) .get<Map<String, dynamic>>(
source.key,
cancelToken: cancelToken,
)
.timeout( .timeout(
httpTimeoutDuration, httpTimeoutDuration,
); );

View File

@@ -2,4 +2,10 @@ extension StringExtension on String {
bool get isUrl { bool get isUrl {
return RegExp(r'^(http|https|ftp)://').hasMatch(this); return RegExp(r'^(http|https|ftp)://').hasMatch(this);
} }
int compareToLower(String other) {
return toLowerCase().compareTo(
other.toLowerCase(),
);
}
} }

View File

@@ -20,6 +20,7 @@ class Window {
WindowOptions windowOptions = WindowOptions( WindowOptions windowOptions = WindowOptions(
size: Size(props.width, props.height), size: Size(props.width, props.height),
minimumSize: const Size(380, 500), minimumSize: const Size(380, 500),
windowButtonVisibility: false,
titleBarStyle: TitleBarStyle.hidden, titleBarStyle: TitleBarStyle.hidden,
); );
if (props.left != null || props.top != null) { if (props.left != null || props.top != null) {

View File

@@ -1,8 +1,16 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'dart:math';
import 'package:archive/archive.dart';
import 'package:fl_clash/common/archive.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lpinyin/lpinyin.dart';
import 'package:path/path.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@@ -17,6 +25,7 @@ class AppController {
late ClashConfig clashConfig; late ClashConfig clashConfig;
late Measure measure; late Measure measure;
late Function updateClashConfigDebounce; late Function updateClashConfigDebounce;
late Function updateGroupDebounce;
late Function addCheckIpNumDebounce; late Function addCheckIpNumDebounce;
AppController(this.context) { AppController(this.context) {
@@ -29,6 +38,9 @@ class AppController {
addCheckIpNumDebounce = debounce(() { addCheckIpNumDebounce = debounce(() {
appState.checkIpNum++; appState.checkIpNum++;
}); });
updateGroupDebounce = debounce(() async {
await updateGroups();
});
measure = Measure.of(context); measure = Measure.of(context);
} }
@@ -45,12 +57,15 @@ class AppController {
updateRunTime, updateRunTime,
updateTraffic, updateTraffic,
]; ];
if (Platform.isAndroid) return;
await applyProfile(isPrue: true);
} else { } else {
await globalState.stopSystemProxy(); await globalState.stopSystemProxy();
clashCore.resetTraffic(); clashCore.resetTraffic();
appState.traffics = []; appState.traffics = [];
appState.totalTraffic = Traffic(); appState.totalTraffic = Traffic();
appState.runTime = null; appState.runTime = null;
addCheckIpNumDebounce();
} }
} }
@@ -82,9 +97,7 @@ class AppController {
deleteProfile(String id) async { deleteProfile(String id) async {
config.deleteProfileById(id); config.deleteProfileById(id);
final profilePath = await appPath.getProfilePath(id); clashCore.clearEffect(id);
if (profilePath == null) return;
clashCore.clearEffect(profilePath);
if (config.currentProfileId == id) { if (config.currentProfileId == id) {
if (config.profiles.isNotEmpty) { if (config.profiles.isNotEmpty) {
final updateId = config.profiles.first.id; final updateId = config.profiles.first.id;
@@ -96,8 +109,10 @@ class AppController {
} }
Future<void> updateProfile(Profile profile) async { Future<void> updateProfile(Profile profile) async {
await profile.update(); final newProfile = await profile.update();
config.setProfile(await profile.update()); config.setProfile(
newProfile.copyWith(isUpdating: false),
);
} }
Future<void> updateClashConfig({bool isPatch = true}) async { Future<void> updateClashConfig({bool isPatch = true}) async {
@@ -108,32 +123,30 @@ class AppController {
); );
} }
Future applyProfile() async { Future applyProfile({bool isPrue = false}) async {
final commonScaffoldState = globalState.homeScaffoldKey.currentState; if (isPrue) {
if (commonScaffoldState?.mounted != true) return;
commonScaffoldState?.loadingRun(() async {
await globalState.applyProfile( await globalState.applyProfile(
appState: appState, appState: appState,
config: config, config: config,
clashConfig: clashConfig, clashConfig: clashConfig,
); );
}); } else {
} final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return;
Future rawApplyProfile() async { await commonScaffoldState?.loadingRun(() async {
await globalState.applyProfile( await globalState.applyProfile(
appState: appState, appState: appState,
config: config, config: config,
clashConfig: clashConfig, clashConfig: clashConfig,
); );
});
}
addCheckIpNumDebounce();
} }
changeProfile(String? value) async { changeProfile(String? value) async {
if (value == config.currentProfileId) return; if (value == config.currentProfileId) return;
config.currentProfileId = value; config.currentProfileId = value;
await applyProfile();
appState.delayMap = {};
saveConfigPreferences();
} }
autoUpdateProfiles() async { autoUpdateProfiles() async {
@@ -192,8 +205,19 @@ class AppController {
await preferences.saveClashConfig(clashConfig); await preferences.saveClashConfig(clashConfig);
} }
changeProxy({
required String groupName,
required String proxyName,
}) {
globalState.changeProxy(
config: config,
groupName: groupName,
proxyName: proxyName,
);
addCheckIpNumDebounce();
}
handleBackOrExit() async { handleBackOrExit() async {
print(config.isMinimizeOnExit);
if (config.isMinimizeOnExit) { if (config.isMinimizeOnExit) {
if (system.isDesktop) { if (system.isDesktop) {
await savePreferences(); await savePreferences();
@@ -274,26 +298,6 @@ class AppController {
if (!config.silentLaunch) { if (!config.silentLaunch) {
window?.show(); window?.show();
} }
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted == true) {
await commonScaffoldState?.loadingRun(() async {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
}, title: appLocalizations.init);
} else {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
}
await afterInit();
}
afterInit() async {
await proxyManager.updateStartTime(); await proxyManager.updateStartTime();
if (proxyManager.isStart) { if (proxyManager.isStart) {
await updateSystemProxy(true); await updateSystemProxy(true);
@@ -383,7 +387,11 @@ class AppController {
} }
addProfileFormFile() async { addProfileFormFile() async {
final platformFile = await globalState.safeRun(picker.pickerConfigFile); final platformFile = await globalState.safeRun(picker.pickerFile);
final bytes = platformFile?.bytes;
if (bytes == null) {
return null;
}
if (!context.mounted) return; if (!context.mounted) return;
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst); globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
toProfiles(); toProfiles();
@@ -392,10 +400,6 @@ class AppController {
final profile = await commonScaffoldState?.loadingRun<Profile?>( final profile = await commonScaffoldState?.loadingRun<Profile?>(
() async { () async {
await Future.delayed(const Duration(milliseconds: 300)); await Future.delayed(const Duration(milliseconds: 300));
final bytes = platformFile?.bytes;
if (bytes == null) {
return null;
}
return await Profile.normal(label: platformFile?.name).saveFile(bytes); return await Profile.normal(label: platformFile?.name).saveFile(bytes);
}, },
title: "${appLocalizations.add}${appLocalizations.profile}", title: "${appLocalizations.add}${appLocalizations.profile}",
@@ -411,7 +415,6 @@ class AppController {
addProfileFormURL(url); addProfileFormURL(url);
} }
int get columns => other.getColumns(appState.viewMode, config.proxiesColumns);
updateViewWidth(double width) { updateViewWidth(double width) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -422,7 +425,10 @@ class AppController {
List<Proxy> _sortOfName(List<Proxy> proxies) { List<Proxy> _sortOfName(List<Proxy> proxies) {
return List.of(proxies) return List.of(proxies)
..sort( ..sort(
(a, b) => other.sortByChar(a.name, b.name), (a, b) => other.sortByChar(
PinyinHelper.getPinyin(a.name),
PinyinHelper.getPinyin(b.name),
),
); );
} }
@@ -456,6 +462,67 @@ class AppController {
String getCurrentSelectedName(String groupName) { String getCurrentSelectedName(String groupName) {
final group = appState.getGroupWithName(groupName); final group = appState.getGroupWithName(groupName);
return config.currentSelectedMap[groupName] ?? group?.now ?? ''; return group?.getCurrentSelectedName(
config.currentSelectedMap[groupName] ?? '') ??
'';
}
Future<List<int>> backupData() async {
final homeDirPath = await appPath.getHomeDirPath();
final profilesPath = await appPath.getProfilesPath();
final configJson = config.toJson();
final clashConfigJson = clashConfig.toJson();
return Isolate.run<List<int>>(() async {
final archive = Archive();
archive.add("config.json", configJson);
archive.add("clashConfig.json", clashConfigJson);
await archive.addDirectoryToArchive(profilesPath, homeDirPath);
final zipEncoder = ZipEncoder();
return zipEncoder.encode(archive) ?? [];
});
}
recoveryData(
List<int> data,
RecoveryOption recoveryOption,
) async {
final archive = await Isolate.run<Archive>(() {
final zipDecoder = ZipDecoder();
return zipDecoder.decodeBytes(data);
});
final homeDirPath = await appPath.getHomeDirPath();
final configs =
archive.files.where((item) => item.name.endsWith(".json")).toList();
final profiles =
archive.files.where((item) => !item.name.endsWith(".json"));
final configIndex =
configs.indexWhere((config) => config.name == "config.json");
final clashConfigIndex =
configs.indexWhere((config) => config.name == "clashConfig.json");
if (configIndex == -1 || clashConfigIndex == -1) throw "invalid backup.zip";
final configFile = configs[configIndex];
final clashConfigFile = configs[clashConfigIndex];
final tempConfig = Config.fromJson(
json.decode(
utf8.decode(configFile.content),
),
);
final tempClashConfig = ClashConfig.fromJson(
json.decode(
utf8.decode(clashConfigFile.content),
),
);
for (final profile in profiles) {
final filePath = join(homeDirPath, profile.name);
final file = File(filePath);
await file.create(recursive: true);
await file.writeAsBytes(profile.content);
}
if (recoveryOption == RecoveryOption.onlyProfiles) {
config.update(tempConfig, RecoveryOption.onlyProfiles);
} else {
config.update(tempConfig, RecoveryOption.all);
clashConfig.update(tempClashConfig);
}
} }
} }

View File

@@ -52,6 +52,8 @@ enum TunStack { gvisor, system, mixed }
enum AccessControlMode { acceptSelected, rejectSelected } enum AccessControlMode { acceptSelected, rejectSelected }
enum AccessSortType { none, name, time }
enum ProfileType { file, url } enum ProfileType { file, url }
enum ResultType { success, error } enum ResultType { success, error }
@@ -84,4 +86,6 @@ enum CommonCardType { plain, filled }
enum ProxiesType { tab, list } enum ProxiesType { tab, list }
enum ProxiesLayout{ loose, standard, tight }
enum ProxyCardType { expand, shrink, min } enum ProxyCardType { expand, shrink, min }

View File

@@ -1,4 +1,5 @@
import 'package:collection/collection.dart'; import 'dart:convert';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/plugins/app.dart';
@@ -6,15 +7,9 @@ import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart'; import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
extension AccessControlExtension on AccessControl {
List<String> get currentList => switch (mode) {
AccessControlMode.acceptSelected => acceptList,
AccessControlMode.rejectSelected => rejectList,
};
}
class AccessFragment extends StatefulWidget { class AccessFragment extends StatefulWidget {
const AccessFragment({super.key}); const AccessFragment({super.key});
@@ -23,198 +18,164 @@ class AccessFragment extends StatefulWidget {
} }
class _AccessFragmentState extends State<AccessFragment> { class _AccessFragmentState extends State<AccessFragment> {
final packagesListenable = ValueNotifier<List<Package>>([]); List<String> acceptList = [];
List<String> rejectList = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_updateInitList();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
Future.delayed(const Duration(milliseconds: 300), () async { final appState = globalState.appController.appState;
packagesListenable.value = await app?.getPackages() ?? []; if (appState.packages.isEmpty) {
}); Future.delayed(const Duration(milliseconds: 300), () async {
appState.packages = await app?.getPackages() ?? [];
});
}
}); });
} }
@override _updateInitList() {
void dispose() { final accessControl = globalState.appController.config.accessControl;
super.dispose(); acceptList = accessControl.acceptList;
packagesListenable.dispose(); rejectList = accessControl.rejectList;
} }
Widget _buildAppProxyModePopup() { Widget _buildSearchButton() {
final items = [
CommonPopupMenuItem(
action: AccessControlMode.rejectSelected,
label: appLocalizations.blacklistMode,
),
CommonPopupMenuItem(
action: AccessControlMode.acceptSelected,
label: appLocalizations.whitelistMode,
),
];
return Selector<Config, AccessControlMode>(
selector: (_, config) => config.accessControl.mode,
builder: (context, mode, __) {
return CommonPopupMenu<AccessControlMode>.radio(
icon: Icon(
Icons.mode_standby,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
items: items,
onSelected: (value) {
final config = context.read<Config>();
config.accessControl = config.accessControl.copyWith(
mode: value,
);
},
selectedValue: mode,
);
},
);
}
Widget _buildFilterSystemAppButton() {
return Selector<Config, bool>(
selector: (_, config) => config.accessControl.isFilterSystemApp,
builder: (context, isFilterSystemApp, __) {
final tooltip = isFilterSystemApp
? appLocalizations.cancelFilterSystemApp
: appLocalizations.filterSystemApp;
return IconButton(
tooltip: tooltip,
onPressed: () {
final config = context.read<Config>();
config.accessControl = config.accessControl.copyWith(
isFilterSystemApp: !isFilterSystemApp,
);
},
icon: isFilterSystemApp
? const Icon(Icons.filter_list_off)
: const Icon(Icons.filter_list),
);
},
);
}
Widget _buildSearchButton(List<Package> packages) {
return IconButton( return IconButton(
tooltip: appLocalizations.search, tooltip: appLocalizations.search,
onPressed: () { onPressed: () {
showSearch( showSearch(
context: context, context: context,
delegate: AccessControlSearchDelegate( delegate: AccessControlSearchDelegate(
packages: packages, acceptList: acceptList,
rejectList: rejectList,
), ),
).then((_) => {setState(() {})}); ).then((_) => setState(() {
_updateInitList();
}));
}, },
icon: const Icon(Icons.search), icon: const Icon(Icons.search),
); );
} }
Widget _buildSelectedAllButton({ Widget _buildSelectedAllButton({
required bool isAccessControl,
required bool isSelectedAll, required bool isSelectedAll,
required List<String> allValueList, required List<String> allValueList,
}) { }) {
final tooltip = isSelectedAll final tooltip = isSelectedAll
? appLocalizations.cancelSelectAll ? appLocalizations.cancelSelectAll
: appLocalizations.selectAll; : appLocalizations.selectAll;
return AbsorbPointer( return IconButton(
absorbing: !isAccessControl, tooltip: tooltip,
child: FloatingActionButton( onPressed: () {
tooltip: tooltip, final config = globalState.appController.config;
onPressed: () { final isAccept =
final config = globalState.appController.config; config.accessControl.mode == AccessControlMode.acceptSelected;
final isAccept = if (isSelectedAll) {
config.accessControl.mode == AccessControlMode.acceptSelected; config.accessControl = switch (isAccept) {
true => config.accessControl.copyWith(
if (isSelectedAll) { acceptList: [],
config.accessControl = switch (isAccept) { ),
true => config.accessControl.copyWith( false => config.accessControl.copyWith(
acceptList: [], rejectList: [],
), ),
false => config.accessControl.copyWith( };
rejectList: [], } else {
), config.accessControl = switch (isAccept) {
}; true => config.accessControl.copyWith(
} else { acceptList: allValueList,
config.accessControl = switch (isAccept) { ),
true => config.accessControl.copyWith( false => config.accessControl.copyWith(
acceptList: allValueList, rejectList: allValueList,
), ),
false => config.accessControl.copyWith( };
rejectList: allValueList, }
), },
}; icon: isSelectedAll
} ? const Icon(Icons.deselect)
}, : const Icon(Icons.select_all),
child: isSelectedAll
? const Icon(Icons.deselect)
: const Icon(Icons.select_all),
),
); );
} }
Widget _buildPackageList() { Widget _buildSettingButton() {
return ValueListenableBuilder( return IconButton(
valueListenable: packagesListenable, onPressed: () {
builder: (_, packages, ___) { showSheet(
final accessControl = globalState.appController.config.accessControl; title: appLocalizations.proxiesSetting,
final acceptList = accessControl.acceptList; context: context,
final rejectList = accessControl.rejectList; builder: (_) {
final acceptPackages = packages.sorted((a, b) { return AccessControlWidget(
final isSelectA = acceptList.contains(a.packageName); context: context,
final isSelectB = acceptList.contains(b.packageName); );
if (isSelectA && isSelectB) return 0; },
if (isSelectA) return -1; );
if (isSelectB) return 1; },
return 0; icon: const Icon(Icons.tune),
}); );
final rejectPackages = packages.sorted((a, b) { }
final isSelectA = rejectList.contains(a.packageName);
final isSelectB = rejectList.contains(b.packageName); @override
if (isSelectA && isSelectB) return 0; Widget build(BuildContext context) {
if (isSelectA) return -1; return Selector<Config, bool>(
if (isSelectB) return 1; selector: (_, config) => config.isAccessControl,
return 0; builder: (_, isAccessControl, child) {
}); return Column(
return Selector<Config, PackageListSelectorState>( mainAxisSize: MainAxisSize.max,
selector: (_, config) => PackageListSelectorState( children: [
accessControl: config.accessControl, Flexible(
isAccessControl: config.isAccessControl, flex: 0,
), child: ListItem.switchItem(
builder: (context, state, __) { title: Text(appLocalizations.appAccessControl),
final accessControl = state.accessControl; delegate: SwitchDelegate(
final isAccessControl = state.isAccessControl; value: isAccessControl,
final isFilterSystemApp = accessControl.isFilterSystemApp; onChanged: (isAccessControl) {
final accessControlMode = accessControl.mode; final config = context.read<Config>();
final packages = config.isAccessControl = isAccessControl;
accessControlMode == AccessControlMode.acceptSelected },
? acceptPackages
: rejectPackages;
final currentList = accessControl.currentList;
final currentPackages = isFilterSystemApp
? packages
.where((element) => element.isSystem == false)
.toList()
: packages;
final packageNameList =
currentPackages.map((e) => e.packageName).toList();
final valueList = currentList.intersection(packageNameList);
final describe =
accessControlMode == AccessControlMode.acceptSelected
? appLocalizations.accessControlAllowDesc
: appLocalizations.accessControlNotAllowDesc;
return DisabledMask(
status: !isAccessControl,
child: FloatLayout(
floatingWidget: FloatWrapper(
child: _buildSelectedAllButton(
isAccessControl: isAccessControl,
isSelectedAll: valueList.length == packageNameList.length,
allValueList: packageNameList,
),
), ),
),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Divider(
height: 12,
),
),
Flexible(
child: child!,
),
],
);
},
child: Selector<AppState, List<Package>>(
selector: (_, appState) => appState.packages,
builder: (_, packages, ___) {
return Selector2<AppState, Config, PackageListSelectorState>(
selector: (_, appState, config) => PackageListSelectorState(
accessControl: config.accessControl,
isAccessControl: config.isAccessControl,
packages: appState.packages,
),
builder: (context, state, __) {
final accessControl = state.accessControl;
final isAccessControl = state.isAccessControl;
final accessControlMode = accessControl.mode;
final packages = state.getList(
accessControlMode == AccessControlMode.acceptSelected
? acceptList
: rejectList,
);
final currentList = accessControl.currentList;
final packageNameList =
packages.map((e) => e.packageName).toList();
final valueList = currentList.intersection(packageNameList);
final describe =
accessControlMode == AccessControlMode.acceptSelected
? appLocalizations.accessControlAllowDesc
: appLocalizations.accessControlNotAllowDesc;
return DisabledMask(
status: !isAccessControl,
child: Column( child: Column(
children: [ children: [
AbsorbPointer( AbsorbPointer(
@@ -285,9 +246,18 @@ class _AccessFragmentState extends State<AccessFragment> {
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Flexible( Flexible(
child: _buildSearchButton(currentPackages)), child: _buildSearchButton(),
Flexible(child: _buildFilterSystemAppButton()), ),
Flexible(child: _buildAppProxyModePopup()), Flexible(
child: _buildSelectedAllButton(
isSelectedAll: valueList.length ==
packageNameList.length,
allValueList: packageNameList,
),
),
Flexible(
child: _buildSettingButton(),
),
], ],
), ),
], ],
@@ -296,92 +266,52 @@ class _AccessFragmentState extends State<AccessFragment> {
), ),
Expanded( Expanded(
flex: 1, flex: 1,
child: FadeBox( child: packages.isEmpty
key: const Key("fade_box"), ? const Center(
child: currentPackages.isEmpty child: CircularProgressIndicator(),
? const Center( )
child: CircularProgressIndicator(), : ListView.builder(
) itemCount: packages.length,
: ListView.builder( itemBuilder: (_, index) {
itemCount: currentPackages.length, final package = packages[index];
itemBuilder: (_, index) { return PackageListItem(
final package = currentPackages[index]; key: Key(package.packageName),
return PackageListItem( package: package,
key: Key(package.packageName), value:
package: package, valueList.contains(package.packageName),
value: isActive: isAccessControl,
valueList.contains(package.packageName), onChanged: (value) {
isActive: isAccessControl, if (value == true) {
onChanged: (value) { valueList.add(package.packageName);
if (value == true) { } else {
valueList.add(package.packageName); valueList.remove(package.packageName);
} else { }
valueList.remove(package.packageName); final config =
} globalState.appController.config;
final config = if (accessControlMode ==
globalState.appController.config; AccessControlMode.acceptSelected) {
if (accessControlMode == config.accessControl =
AccessControlMode.acceptSelected) { config.accessControl.copyWith(
config.accessControl = acceptList: valueList,
config.accessControl.copyWith( );
acceptList: valueList, } else {
); config.accessControl =
} else { config.accessControl.copyWith(
config.accessControl = rejectList: valueList,
config.accessControl.copyWith( );
rejectList: valueList, }
); },
} );
}, },
); ),
},
),
),
), ),
], ],
), ),
), );
); },
}, );
); },
}, ),
);
}
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.isAccessControl,
builder: (_, isAccessControl, child) {
return Column(
mainAxisSize: MainAxisSize.max,
children: [
Flexible(
flex: 0,
child: ListItem.switchItem(
title: Text(appLocalizations.appAccessControl),
delegate: SwitchDelegate(
value: isAccessControl,
onChanged: (isAccessControl) {
final config = context.read<Config>();
config.isAccessControl = isAccessControl;
},
),
),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Divider(
height: 12,
),
),
Flexible(
child: child!,
),
],
);
},
child: _buildPackageList(),
); );
} }
} }
@@ -448,23 +378,14 @@ class PackageListItem extends StatelessWidget {
} }
class AccessControlSearchDelegate extends SearchDelegate { class AccessControlSearchDelegate extends SearchDelegate {
final List<Package> packages; List<String> acceptList = [];
List<String> rejectList = [];
AccessControlSearchDelegate({ AccessControlSearchDelegate({
required this.packages, required this.acceptList,
required this.rejectList,
}); });
List<Package> get _results {
final lowQuery = query.toLowerCase();
return packages
.where(
(package) =>
package.label.toLowerCase().contains(lowQuery) ||
package.packageName.contains(lowQuery),
)
.toList();
}
@override @override
List<Widget>? buildActions(BuildContext context) { List<Widget>? buildActions(BuildContext context) {
return [ return [
@@ -494,26 +415,39 @@ class AccessControlSearchDelegate extends SearchDelegate {
); );
} }
Widget _packageList(List<Package> packages) { Widget _packageList() {
return Selector<Config, PackageListSelectorState>( final lowQuery = query.toLowerCase();
selector: (_, config) => PackageListSelectorState( return Selector2<AppState, Config, PackageListSelectorState>(
selector: (_, appState, config) => PackageListSelectorState(
packages: appState.packages,
accessControl: config.accessControl, accessControl: config.accessControl,
isAccessControl: config.isAccessControl, isAccessControl: config.isAccessControl,
), ),
builder: (context, state, __) { builder: (context, state, __) {
final accessControl = state.accessControl; final accessControl = state.accessControl;
final isAccessControl = state.isAccessControl;
final accessControlMode = accessControl.mode; final accessControlMode = accessControl.mode;
final packages = state.getList(
accessControlMode == AccessControlMode.acceptSelected
? acceptList
: rejectList,
);
final queryPackages = packages
.where(
(package) =>
package.label.toLowerCase().contains(lowQuery) ||
package.packageName.contains(lowQuery),
)
.toList();
final isAccessControl = state.isAccessControl;
final currentList = accessControl.currentList; final currentList = accessControl.currentList;
final packageNameList = final packageNameList = packages.map((e) => e.packageName).toList();
this.packages.map((e) => e.packageName).toList();
final valueList = currentList.intersection(packageNameList); final valueList = currentList.intersection(packageNameList);
return DisabledMask( return DisabledMask(
status: !isAccessControl, status: !isAccessControl,
child: ListView.builder( child: ListView.builder(
itemCount: packages.length, itemCount: queryPackages.length,
itemBuilder: (_, index) { itemBuilder: (_, index) {
final package = packages[index]; final package = queryPackages[index];
return PackageListItem( return PackageListItem(
key: Key(package.packageName), key: Key(package.packageName),
package: package, package: package,
@@ -551,6 +485,268 @@ class AccessControlSearchDelegate extends SearchDelegate {
@override @override
Widget buildSuggestions(BuildContext context) { Widget buildSuggestions(BuildContext context) {
return _packageList(_results); return _packageList();
}
}
class AccessControlWidget extends StatelessWidget {
final BuildContext context;
const AccessControlWidget({
super.key,
required this.context,
});
IconData _getIconWithAccessControlMode(AccessControlMode mode) {
return switch (mode) {
AccessControlMode.acceptSelected => Icons.adjust_outlined,
AccessControlMode.rejectSelected => Icons.block_outlined,
};
}
String _getTextWithAccessControlMode(AccessControlMode mode) {
return switch (mode) {
AccessControlMode.acceptSelected => appLocalizations.whitelistMode,
AccessControlMode.rejectSelected => appLocalizations.blacklistMode,
};
}
String _getTextWithAccessSortType(AccessSortType type) {
return switch (type) {
AccessSortType.none => appLocalizations.defaultText,
AccessSortType.name => appLocalizations.name,
AccessSortType.time => appLocalizations.time,
};
}
IconData _getIconWithProxiesSortType(AccessSortType type) {
return switch (type) {
AccessSortType.none => Icons.sort,
AccessSortType.name => Icons.sort_by_alpha,
AccessSortType.time => Icons.timeline,
};
}
String _getTextWithIsFilterSystemApp(bool isFilterSystemApp) {
return switch (isFilterSystemApp) {
true => appLocalizations.onlyOtherApps,
false => appLocalizations.allApps,
};
}
List<Widget> _buildModeSetting() {
return generateSection(
title: appLocalizations.mode,
items: [
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Selector<Config, AccessControlMode>(
selector: (_, config) => config.accessControl.mode,
builder: (_, accessControlMode, __) {
return Wrap(
spacing: 16,
children: [
for (final item in AccessControlMode.values)
SettingInfoCard(
Info(
label: _getTextWithAccessControlMode(item),
iconData: _getIconWithAccessControlMode(item),
),
isSelected: accessControlMode == item,
onPressed: () {
final config = globalState.appController.config;
config.accessControl = config.accessControl.copyWith(
mode: item,
);
},
)
],
);
},
),
)
],
);
}
List<Widget> _buildSortSetting() {
return generateSection(
title: appLocalizations.sort,
items: [
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Selector<Config, AccessSortType>(
selector: (_, config) => config.accessControl.sort,
builder: (_, accessSortType, __) {
return Wrap(
spacing: 16,
children: [
for (final item in AccessSortType.values)
SettingInfoCard(
Info(
label: _getTextWithAccessSortType(item),
iconData: _getIconWithProxiesSortType(item),
),
isSelected: accessSortType == item,
onPressed: () {
final config = globalState.appController.config;
config.accessControl = config.accessControl.copyWith(
sort: item,
);
},
),
],
);
},
),
),
],
);
}
List<Widget> _buildSourceSetting() {
return generateSection(
title: appLocalizations.source,
items: [
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Selector<Config, bool>(
selector: (_, config) => config.accessControl.isFilterSystemApp,
builder: (_, isFilterSystemApp, __) {
return Wrap(
spacing: 16,
children: [
for (final item in [false, true])
SettingTextCard(
_getTextWithIsFilterSystemApp(item),
isSelected: isFilterSystemApp == item,
onPressed: () {
final config = globalState.appController.config;
config.accessControl = config.accessControl.copyWith(
isFilterSystemApp: item,
);
},
)
],
);
},
),
)
],
);
}
_intelligentSelected() async {
final appState = globalState.appController.appState;
final config = globalState.appController.config;
final accessControl = config.accessControl;
final packageNames = appState.packages
.where(
(item) =>
accessControl.isFilterSystemApp ? item.isSystem == false : true,
)
.map((item) => item.packageName);
Navigator.of(context).pop();
final commonScaffoldState = context.commonScaffoldState;
if (commonScaffoldState?.mounted != true) return;
final selectedPackageNames =
(await commonScaffoldState?.loadingRun<List<String>>(
() async {
return await app?.getChinaPackageNames() ?? [];
},
))
?.toSet() ??
{};
final acceptList = packageNames
.where((item) => !selectedPackageNames.contains(item))
.toList();
final rejectList = packageNames
.where((item) => selectedPackageNames.contains(item))
.toList();
config.accessControl = accessControl.copyWith(
acceptList: acceptList,
rejectList: rejectList,
);
}
_copyToClipboard() async {
await globalState.safeRun(() {
final data = globalState.appController.config.accessControl.toJson();
Clipboard.setData(
ClipboardData(
text: json.encode(data),
),
);
});
if (!context.mounted) return;
Navigator.of(context).pop();
}
_pasteToClipboard() async {
await globalState.safeRun(() async {
final config = globalState.appController.config;
final data = await Clipboard.getData('text/plain');
final text = data?.text;
if (text == null) return;
config.accessControl = AccessControl.fromJson(
json.decode(text),
);
});
if (!context.mounted) return;
Navigator.of(context).pop();
}
List<Widget> _buildActionSetting() {
return generateSection(
title: appLocalizations.action,
items: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: Wrap(
runSpacing: 16,
spacing: 16,
children: [
CommonChip(
avatar: const Icon(Icons.auto_awesome),
label: appLocalizations.intelligentSelected,
onPressed: _intelligentSelected,
),
CommonChip(
avatar: const Icon(Icons.paste),
label: appLocalizations.clipboardImport,
onPressed: _pasteToClipboard,
),
CommonChip(
avatar: const Icon(Icons.content_copy),
label: appLocalizations.clipboardExport,
onPressed: _copyToClipboard,
)
],
),
)
],
);
}
@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(),
],
),
);
} }
} }

View File

@@ -1,3 +1,5 @@
import 'dart:typed_data';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/dav_client.dart'; import 'package:fl_clash/common/dav_client.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
@@ -10,16 +12,9 @@ import 'package:fl_clash/widgets/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class BackupAndRecovery extends StatefulWidget { class BackupAndRecovery extends StatelessWidget {
const BackupAndRecovery({super.key}); const BackupAndRecovery({super.key});
@override
State<BackupAndRecovery> createState() => _BackupAndRecoveryState();
}
class _BackupAndRecoveryState extends State<BackupAndRecovery> {
DAVClient? _client;
_showAddWebDAV(DAV? dav) async { _showAddWebDAV(DAV? dav) async {
await globalState.showCommonDialog<String>( await globalState.showCommonDialog<String>(
child: WebDAVFormDialog( child: WebDAVFormDialog(
@@ -28,11 +23,15 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
); );
} }
_backup() async { _backupOnWebDAV(BuildContext context, DAVClient client) async {
final commonScaffoldState = context.commonScaffoldState; final commonScaffoldState = context.commonScaffoldState;
final res = await commonScaffoldState?.loadingRun<bool>(() async { final res = await commonScaffoldState?.loadingRun<bool>(
return await _client?.backup(); () async {
}); final backupData = await globalState.appController.backupData();
return await client.backup(Uint8List.fromList(backupData));
},
title: appLocalizations.backup,
);
if (res != true) return; if (res != true) return;
globalState.showMessage( globalState.showMessage(
title: appLocalizations.backup, title: appLocalizations.backup,
@@ -40,11 +39,20 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
); );
} }
_recovery(RecoveryOption recoveryOption) async { _recoveryOnWebDAV(
BuildContext context,
DAVClient client,
RecoveryOption recoveryOption,
) async {
final commonScaffoldState = context.commonScaffoldState; final commonScaffoldState = context.commonScaffoldState;
final res = await commonScaffoldState?.loadingRun<bool>(() async { final res = await commonScaffoldState?.loadingRun<bool>(
return await _client?.recovery(recoveryOption: recoveryOption); () async {
}); final data = await client.recovery();
await globalState.appController.recoveryData(data, recoveryOption);
return true;
},
title: appLocalizations.recovery,
);
if (res != true) return; if (res != true) return;
globalState.showMessage( globalState.showMessage(
title: appLocalizations.recovery, title: appLocalizations.recovery,
@@ -52,12 +60,66 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
); );
} }
_handleRecovery() async { _handleRecoveryOnWebDAV(BuildContext context, DAVClient client) async {
final recoveryOption = await globalState.showCommonDialog<RecoveryOption>( final recoveryOption = await globalState.showCommonDialog<RecoveryOption>(
child: const RecoveryOptionsDialog(), child: const RecoveryOptionsDialog(),
); );
if (recoveryOption == null) return; if (recoveryOption == null || !context.mounted) return;
_recovery(recoveryOption); _recoveryOnWebDAV(context, client, recoveryOption);
}
_backupOnLocal(BuildContext context) async {
final commonScaffoldState = context.commonScaffoldState;
final res = await commonScaffoldState?.loadingRun<bool>(
() async {
final backupData = await globalState.appController.backupData();
final value = await picker.saveFile(
other.getBackupFileName(),
Uint8List.fromList(backupData),
);
if(value == null) return false;
return true;
},
title: appLocalizations.backup,
);
if (res != true) return;
globalState.showMessage(
title: appLocalizations.backup,
message: TextSpan(text: appLocalizations.backupSuccess),
);
}
_recoveryOnLocal(
BuildContext context,
RecoveryOption recoveryOption,
) async {
final file = await picker.pickerFile();
final data = file?.bytes;
if (data == null || !context.mounted) return;
final commonScaffoldState = context.commonScaffoldState;
final res = await commonScaffoldState?.loadingRun<bool>(
() async {
await globalState.appController.recoveryData(
List<int>.from(data),
recoveryOption,
);
return true;
},
title: appLocalizations.recovery,
);
if (res != true) return;
globalState.showMessage(
title: appLocalizations.recovery,
message: TextSpan(text: appLocalizations.recoverySuccess),
);
}
_handleRecoveryOnLocal(BuildContext context) async {
final recoveryOption = await globalState.showCommonDialog<RecoveryOption>(
child: const RecoveryOptionsDialog(),
);
if (recoveryOption == null || !context.mounted) return;
_recoveryOnLocal(context, recoveryOption);
} }
@override @override
@@ -65,12 +127,11 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
return Selector<Config, DAV?>( return Selector<Config, DAV?>(
selector: (_, config) => config.dav, selector: (_, config) => config.dav,
builder: (_, dav, __) { builder: (_, dav, __) {
if (dav == null) { final client = dav != null ? DAVClient(dav) : null;
return ListView( return ListView(
children: [ children: [
ListHeader( ListHeader(title: appLocalizations.remote),
title: appLocalizations.account, if (dav == null)
),
ListItem( ListItem(
leading: const Icon(Icons.account_box), leading: const Icon(Icons.account_box),
title: Text(appLocalizations.noInfo), title: Text(appLocalizations.noInfo),
@@ -83,95 +144,95 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
appLocalizations.bind, appLocalizations.bind,
), ),
), ),
)
else ...[
ListItem(
leading: const Icon(Icons.account_box),
title: TooltipText(
text: Text(
dav.user,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(appLocalizations.connectivity),
FutureBuilder<bool>(
future: client!.pingCompleter.future,
builder: (_, snapshot) {
return Center(
child: FadeBox(
child: snapshot.connectionState ==
ConnectionState.waiting
? const SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(
strokeWidth: 1,
),
)
: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: snapshot.data == true
? Colors.green
: Colors.red,
),
width: 12,
height: 12,
),
),
);
},
),
],
),
),
trailing: FilledButton.tonal(
onPressed: () {
_showAddWebDAV(dav);
},
child: Text(
appLocalizations.edit,
),
),
),
const SizedBox(
height: 4,
),
ListItem(
onTap: () {
_backupOnWebDAV(context, client);
},
title: Text(appLocalizations.backup),
subtitle: Text(appLocalizations.remoteBackupDesc),
),
ListItem(
onTap: () {
_handleRecoveryOnWebDAV(context, client);
},
title: Text(appLocalizations.recovery),
subtitle: Text(appLocalizations.remoteRecoveryDesc),
), ),
], ],
); ListHeader(title: appLocalizations.local),
}
_client = DAVClient(dav);
final pingFuture = _client!.pingCompleter.future;
return ListView(
children: [
ListHeader(title: appLocalizations.account),
ListItem( ListItem(
leading: const Icon(Icons.account_box), onTap: () {
title: TooltipText( _backupOnLocal(context);
text: Text(
dav.user,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(appLocalizations.connectivity),
FutureBuilder<bool>(
future: pingFuture,
builder: (_, snapshot) {
return Center(
child: FadeBox(
key: const Key("fade_box_1"),
child: snapshot.connectionState == ConnectionState.waiting
? const SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(
strokeWidth: 1,
),
)
: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: snapshot.data == true
? Colors.green
: Colors.red,
),
width: 12,
height: 12,
),
),
);
},
),
],
),
),
trailing: FilledButton.tonal(
onPressed: () {
_showAddWebDAV(dav);
},
child: Text(
appLocalizations.edit,
),
),
),
FutureBuilder<bool>(
future: pingFuture,
builder: (_, snapshot) {
return FadeBox(
key: const Key("fade_box_2"),
child: snapshot.data == true
? Column(
children: [
ListHeader(
title: appLocalizations.backupAndRecovery),
ListItem(
onTap: _backup,
title: Text(appLocalizations.backup),
subtitle: Text(appLocalizations.backupDesc),
),
ListItem(
onTap: _handleRecovery,
title: Text(appLocalizations.recovery),
subtitle: Text(appLocalizations.recoveryDesc),
),
],
)
: Container(),
);
}, },
title: Text(appLocalizations.backup),
subtitle: Text(appLocalizations.localBackupDesc),
),
ListItem(
onTap: () {
_handleRecoveryOnLocal(context);
},
title: Text(appLocalizations.recovery),
subtitle: Text(appLocalizations.localRecoveryDesc),
), ),
], ],
); );
@@ -180,6 +241,50 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
} }
} }
class RecoveryOptionsDialog extends StatefulWidget {
const RecoveryOptionsDialog({super.key});
@override
State<RecoveryOptionsDialog> createState() => _RecoveryOptionsDialogState();
}
class _RecoveryOptionsDialogState extends State<RecoveryOptionsDialog> {
_handleOnTab(RecoveryOption? value) {
if (value == null) return;
Navigator.of(context).pop(value);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(appLocalizations.recovery),
contentPadding: 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),
)
],
),
),
);
}
}
class WebDAVFormDialog extends StatefulWidget { class WebDAVFormDialog extends StatefulWidget {
final DAV? dav; final DAV? dav;
@@ -238,7 +343,7 @@ class _WebDAVFormDialogState extends State<WebDAVFormDialog> {
children: [ children: [
TextFormField( TextFormField(
controller: uriController, controller: uriController,
maxLines: 2, maxLines: 5,
minLines: 1, minLines: 1,
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.link), prefixIcon: const Icon(Icons.link),
@@ -313,47 +418,3 @@ class _WebDAVFormDialogState extends State<WebDAVFormDialog> {
); );
} }
} }
class RecoveryOptionsDialog extends StatefulWidget {
const RecoveryOptionsDialog({super.key});
@override
State<RecoveryOptionsDialog> createState() => _RecoveryOptionsDialogState();
}
class _RecoveryOptionsDialogState extends State<RecoveryOptionsDialog> {
_handleOnTab(RecoveryOption? value) {
if (value == null) return;
Navigator.of(context).pop(value);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(appLocalizations.recovery),
contentPadding: 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),
)
],
),
),
);
}
}

View File

@@ -137,11 +137,40 @@ class _ConfigFragmentState extends State<ConfigFragment> {
} }
} }
_updateKeepAliveInterval(int keepAliveInterval) async {
final newKeepAliveIntervalString =
await globalState.showCommonDialog<String>(
child: KeepAliveIntervalFormDialog(
keepAliveInterval: keepAliveInterval,
),
);
if (newKeepAliveIntervalString != null &&
newKeepAliveIntervalString != "$keepAliveInterval" &&
mounted) {
try {
final newKeepAliveInterval = int.parse(newKeepAliveIntervalString);
if (newKeepAliveInterval <= 0) {
throw "Invalid keepAliveInterval";
}
globalState.appController.clashConfig.keepAliveInterval =
newKeepAliveInterval;
globalState.appController.updateClashConfigDebounce();
} catch (e) {
globalState.showMessage(
title: appLocalizations.testUrl,
message: TextSpan(
text: e.toString(),
),
);
}
}
}
List<Widget> _buildAppSection() { List<Widget> _buildAppSection() {
return generateSection( return generateSection(
title: appLocalizations.app, title: appLocalizations.app,
items: [ items: [
if (Platform.isAndroid)...[ if (Platform.isAndroid) ...[
Selector<Config, bool>( Selector<Config, bool>(
selector: (_, config) => config.allowBypass, selector: (_, config) => config.allowBypass,
builder: (_, allowBypass, __) { builder: (_, allowBypass, __) {
@@ -195,23 +224,40 @@ class _ConfigFragmentState extends State<ConfigFragment> {
}, },
), ),
Selector<Config, bool>( Selector<Config, bool>(
selector: (_, config) => config.isCompatible, selector: (_, config) => config.onlyProxy,
builder: (_, isCompatible, __) { builder: (_, onlyProxy, __) {
return ListItem.switchItem( return ListItem.switchItem(
leading: const Icon(Icons.expand_outlined), leading: const Icon(Icons.data_usage_outlined),
title: Text(appLocalizations.compatible), title: Text(appLocalizations.onlyStatisticsProxy),
subtitle: Text(appLocalizations.compatibleDesc), subtitle: Text(appLocalizations.onlyStatisticsProxyDesc),
delegate: SwitchDelegate( delegate: SwitchDelegate(
value: isCompatible, value: onlyProxy,
onChanged: (bool value) async { onChanged: (bool value) async {
final appController = globalState.appController; final appController = globalState.appController;
appController.config.isCompatible = value; appController.config.onlyProxy = value;
await appController.applyProfile();
}, },
), ),
); );
}, },
), ),
// Selector<Config, bool>(
// selector: (_, config) => config.isCompatible,
// builder: (_, isCompatible, __) {
// return ListItem.switchItem(
// leading: const Icon(Icons.expand_outlined),
// title: Text(appLocalizations.compatible),
// subtitle: Text(appLocalizations.compatibleDesc),
// delegate: SwitchDelegate(
// value: isCompatible,
// onChanged: (bool value) async {
// final appController = globalState.appController;
// appController.config.isCompatible = value;
// await appController.applyProfile();
// },
// ),
// );
// },
// ),
], ],
); );
} }
@@ -246,6 +292,19 @@ class _ConfigFragmentState extends State<ConfigFragment> {
); );
}, },
), ),
Selector<ClashConfig, int>(
selector: (_, config) => config.keepAliveInterval,
builder: (_, value, __) {
return ListItem(
leading: const Icon(Icons.timer_outlined),
title: Text(appLocalizations.keepAliveIntervalDesc),
subtitle: Text("$value ${appLocalizations.seconds}"),
onTap: () {
_updateKeepAliveInterval(value);
},
);
},
),
Selector<Config, String>( Selector<Config, String>(
selector: (_, config) => config.testUrl, selector: (_, config) => config.testUrl,
builder: (_, value, __) { builder: (_, value, __) {
@@ -572,3 +631,64 @@ class _TestUrlFormDialogState extends State<TestUrlFormDialog> {
); );
} }
} }
class KeepAliveIntervalFormDialog extends StatefulWidget {
final int keepAliveInterval;
const KeepAliveIntervalFormDialog({
super.key,
required this.keepAliveInterval,
});
@override
State<KeepAliveIntervalFormDialog> createState() =>
_KeepAliveIntervalFormDialogState();
}
class _KeepAliveIntervalFormDialogState
extends State<KeepAliveIntervalFormDialog> {
late TextEditingController keepAliveIntervalController;
@override
void initState() {
super.initState();
keepAliveIntervalController =
TextEditingController(text: "${widget.keepAliveInterval}");
}
_handleUpdate() async {
final keepAliveInterval = keepAliveIntervalController.value.text;
if (keepAliveInterval.isEmpty) return;
Navigator.of(context).pop<String>(keepAliveInterval);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(appLocalizations.keepAliveIntervalDesc),
content: SizedBox(
width: 300,
child: Wrap(
runSpacing: 16,
children: [
TextField(
maxLines: 1,
minLines: 1,
controller: keepAliveIntervalController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
suffixText: appLocalizations.seconds,
),
),
],
),
),
actions: [
TextButton(
onPressed: _handleUpdate,
child: Text(appLocalizations.submit),
)
],
);
}
}

View File

@@ -51,18 +51,6 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
final commonScaffoldState = final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>(); context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [ commonScaffoldState?.actions = [
IconButton(
onPressed: () {
clashCore.closeConnections();
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: clashCore.getConnections(),
);
},
icon: const Icon(Icons.delete_sweep_outlined),
),
const SizedBox(
width: 8,
),
IconButton( IconButton(
onPressed: () { onPressed: () {
showSearch( showSearch(
@@ -74,6 +62,18 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
}, },
icon: const Icon(Icons.search), icon: const Icon(Icons.search),
), ),
const SizedBox(
width: 8,
),
IconButton(
onPressed: () {
clashCore.closeConnections();
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: clashCore.getConnections(),
);
},
icon: const Icon(Icons.delete_sweep_outlined),
),
]; ];
}, },
); );

View File

@@ -1,4 +1,3 @@
import 'package:country_flags/country_flags.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
@@ -18,48 +17,44 @@ class _NetworkDetectionState extends State<NetworkDetection> {
final ipInfoNotifier = ValueNotifier<IpInfo?>(null); final ipInfoNotifier = ValueNotifier<IpInfo?>(null);
final timeoutNotifier = ValueNotifier<bool>(false); final timeoutNotifier = ValueNotifier<bool>(false);
bool? _preIsStart; bool? _preIsStart;
CancelToken? cancelToken;
Function? _checkIpDebounce; Function? _checkIpDebounce;
CancelToken? cancelToken;
_checkIp( _checkIp() async {
bool isInit, final appState = globalState.appController.appState;
bool isStart, final isInit = appState.isInit;
) async { final isStart = appState.isStart;
if (!isInit) return; if (!isInit) return;
timeoutNotifier.value = false; timeoutNotifier.value = false;
if (_preIsStart == false && _preIsStart == isStart) return; if (_preIsStart == false && _preIsStart == isStart) return;
_preIsStart = isStart;
ipInfoNotifier.value = null;
if (cancelToken != null) { if (cancelToken != null) {
cancelToken!.cancel(); cancelToken!.cancel();
_preIsStart = null;
timeoutNotifier.value == false;
cancelToken = null; cancelToken = null;
} }
ipInfoNotifier.value = null; cancelToken = CancelToken();
final ipInfo = await request.checkIp(cancelToken); final ipInfo = await request.checkIp(cancelToken: cancelToken);
if (ipInfo == null) { if (ipInfo == null) {
timeoutNotifier.value = true; timeoutNotifier.value = true;
return; return;
} else {
timeoutNotifier.value = false;
} }
_preIsStart = isStart; timeoutNotifier.value = false;
ipInfoNotifier.value = ipInfo; ipInfoNotifier.value = ipInfo;
} }
_checkIpContainer(Widget child) { _checkIpContainer(Widget child) {
_checkIpDebounce = debounce(_checkIp); return Selector<AppState, num>(
return Selector2<AppState, Config, CheckIpSelectorState>( selector: (_, appState) {
selector: (_, appState, config) { return appState.checkIpNum;
return CheckIpSelectorState(
isInit: appState.isInit,
selectedMap: appState.selectedMap,
isStart: appState.isStart,
checkIpNum: appState.checkIpNum,
);
}, },
builder: (_, state, __) { builder: (_, checkIpNum, child) {
if (_checkIpDebounce != null) { if (_checkIpDebounce != null) {
_checkIpDebounce!([state.isInit, state.isStart]); _checkIpDebounce!();
} }
return child; return child!;
}, },
child: child, child: child,
); );
@@ -72,8 +67,19 @@ class _NetworkDetectionState extends State<NetworkDetection> {
timeoutNotifier.dispose(); timeoutNotifier.dispose();
} }
String countryCodeToEmoji(String countryCode) {
final String code = countryCode.toUpperCase();
if (code.length != 2) {
return countryCode;
}
final int firstLetter = code.codeUnitAt(0) - 0x41 + 0x1F1E6;
final int secondLetter = code.codeUnitAt(1) - 0x41 + 0x1F1E6;
return String.fromCharCode(firstLetter) + String.fromCharCode(secondLetter);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_checkIpDebounce ??= debounce(_checkIp);
return _checkIpContainer( return _checkIpContainer(
ValueListenableBuilder<IpInfo?>( ValueListenableBuilder<IpInfo?>(
valueListenable: ipInfoNotifier, valueListenable: ipInfoNotifier,
@@ -99,10 +105,19 @@ class _NetworkDetectionState extends State<NetworkDetection> {
flex: 1, flex: 1,
child: FadeBox( child: FadeBox(
child: ipInfo != null child: ipInfo != null
? CountryFlag.fromCountryCode( ? Container(
ipInfo.countryCode, alignment: Alignment.centerLeft,
width: 24, height: globalState.appController.measure
height: 24, .titleMediumHeight,
child: Text(
countryCodeToEmoji(ipInfo.countryCode),
style: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(
fontFamily: "Twemoji",
),
),
) )
: ValueListenableBuilder( : ValueListenableBuilder(
valueListenable: timeoutNotifier, valueListenable: timeoutNotifier,

View File

@@ -1,10 +1,15 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart'; import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
class EditProfile extends StatefulWidget { class EditProfile extends StatefulWidget {
final Profile profile; final Profile profile;
@@ -26,6 +31,8 @@ class _EditProfileState extends State<EditProfile> {
late TextEditingController autoUpdateDurationController; late TextEditingController autoUpdateDurationController;
late bool autoUpdate; late bool autoUpdate;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final fileInfoNotifier = ValueNotifier<FileInfo?>(null);
Uint8List? fileData;
@override @override
void initState() { void initState() {
@@ -36,12 +43,16 @@ class _EditProfileState extends State<EditProfile> {
autoUpdateDurationController = TextEditingController( autoUpdateDurationController = TextEditingController(
text: widget.profile.autoUpdateDuration.inMinutes.toString(), text: widget.profile.autoUpdateDuration.inMinutes.toString(),
); );
appPath.getProfilePath(widget.profile.id).then((path) async {
if (path == null) return;
fileInfoNotifier.value = await _getFileInfo(path);
});
} }
_handleConfirm() { _handleConfirm() async {
if (!_formKey.currentState!.validate()) return; if (!_formKey.currentState!.validate()) return;
final config = widget.context.read<Config>(); final config = widget.context.read<Config>();
final profile = widget.profile.copyWith( var profile = widget.profile.copyWith(
url: urlController.text, url: urlController.text,
label: labelController.text, label: labelController.text,
autoUpdate: autoUpdate, autoUpdate: autoUpdate,
@@ -52,7 +63,11 @@ class _EditProfileState extends State<EditProfile> {
), ),
); );
final hasUpdate = widget.profile.url != profile.url; final hasUpdate = widget.profile.url != profile.url;
config.setProfile(profile); if (fileData != null) {
config.setProfile(await profile.saveFile(fileData!));
} else {
config.setProfile(profile);
}
if (hasUpdate) { if (hasUpdate) {
globalState.homeScaffoldKey.currentState?.loadingRun( globalState.homeScaffoldKey.currentState?.loadingRun(
() async { () async {
@@ -62,7 +77,9 @@ class _EditProfileState extends State<EditProfile> {
}, },
); );
} }
Navigator.of(context).pop(); if (mounted) {
Navigator.of(context).pop();
}
} }
_setAutoUpdate(bool value) { _setAutoUpdate(bool value) {
@@ -72,6 +89,47 @@ class _EditProfileState extends State<EditProfile> {
}); });
} }
Future<FileInfo?> _getFileInfo(path) async {
final file = File(path);
if (!await file.exists()) {
return null;
}
final lastModified = await file.lastModified();
final size = await file.length();
return FileInfo(
size: size,
lastModified: lastModified,
);
}
_editProfileFile() async {
final profilePath = await appPath.getProfilePath(widget.profile.id);
if (profilePath == null) return;
globalState.safeRun(() async {
if (Platform.isAndroid) {
await app?.openFile(
profilePath,
);
return;
}
await launchUrl(
Uri.file(
profilePath,
),
);
});
}
_uploadProfileFile() async {
final platformFile = await globalState.safeRun(picker.pickerFile);
if (platformFile?.bytes == null) return;
fileData = platformFile?.bytes;
fileInfoNotifier.value = fileInfoNotifier.value?.copyWith(
size: fileData?.length ?? 0,
lastModified: DateTime.now(),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final items = [ final items = [
@@ -141,7 +199,51 @@ class _EditProfileState extends State<EditProfile> {
}, },
), ),
), ),
] ],
ValueListenableBuilder<FileInfo?>(
valueListenable: fileInfoNotifier,
builder: (_, fileInfo, __) {
return FadeBox(
child: fileInfo == null
? Container()
: ListItem(
title: Text(
appLocalizations.profile,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 4,
),
Text(
fileInfo.desc,
),
const SizedBox(
height: 8,
),
Wrap(
runSpacing: 6,
spacing: 12,
children: [
CommonChip(
avatar: const Icon(Icons.edit),
label: appLocalizations.edit,
onPressed: _editProfileFile,
),
CommonChip(
avatar: const Icon(Icons.upload),
label: appLocalizations.upload,
onPressed: _uploadProfileFile,
),
],
),
],
),
),
);
},
),
]; ];
return FloatLayout( return FloatLayout(
floatingWidget: FloatWrapper( floatingWidget: FloatWrapper(
@@ -159,7 +261,9 @@ class _EditProfileState extends State<EditProfile> {
vertical: 16, vertical: 16,
), ),
child: ListView.separated( child: ListView.separated(
primary: true, padding: kMaterialListPadding.copyWith(
bottom: 72,
),
itemBuilder: (_, index) { itemBuilder: (_, index) {
return items[index]; return items[index];
}, },

View File

@@ -1,6 +1,7 @@
import 'dart:ui';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/fragments/profiles/edit_profile.dart'; import 'package:fl_clash/fragments/profiles/edit_profile.dart';
import 'package:fl_clash/fragments/profiles/view_profile.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
@@ -16,7 +17,6 @@ enum ProfileActions {
edit, edit,
update, update,
delete, delete,
view,
} }
class ProfilesFragment extends StatefulWidget { class ProfilesFragment extends StatefulWidget {
@@ -27,11 +27,8 @@ class ProfilesFragment extends StatefulWidget {
} }
class _ProfilesFragmentState extends State<ProfilesFragment> { class _ProfilesFragmentState extends State<ProfilesFragment> {
final hasPadding = ValueNotifier<bool>(false);
Function? applyConfigDebounce; Function? applyConfigDebounce;
List<GlobalObjectKey<_ProfileItemState>> profileItemKeys = [];
_handleShowAddExtendPage() { _handleShowAddExtendPage() {
showExtendPage( showExtendPage(
globalState.navigatorKey.currentState!.context, globalState.navigatorKey.currentState!.context,
@@ -42,29 +39,52 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
); );
} }
_getColumns(ViewMode viewMode) {
switch (viewMode) {
case ViewMode.mobile:
return 1;
case ViewMode.laptop:
return 1;
case ViewMode.desktop:
return 2;
}
}
_updateProfiles() async { _updateProfiles() async {
final updateProfiles = profileItemKeys.map<Future>( final appController = globalState.appController;
(key) async => await key.currentState?.updateProfile(false)); final config = appController.config;
final profiles = appController.config.profiles;
final messages = [];
final updateProfiles = profiles.map<Future>(
(profile) async {
config.setProfile(
profile.copyWith(isUpdating: true),
);
try {
await appController.updateProfile(profile);
if (profile.id == appController.config.currentProfile?.id) {
appController.applyProfile(isPrue: true);
}
} catch (e) {
messages.add("${profile.label ?? profile.id}: $e \n");
config.setProfile(
profile.copyWith(
isUpdating: false,
),
);
}
},
);
final titleMedium = context.textTheme.titleMedium;
await Future.wait(updateProfiles); await Future.wait(updateProfiles);
if (messages.isNotEmpty) {
globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(
children: [
for (final message in messages)
TextSpan(text: message, style: titleMedium)
],
),
);
}
} }
_initScaffoldState() { _initScaffoldState() {
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance.addPostFrameCallback(
(_) { (_) {
if (!mounted) return;
final commonScaffoldState = final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>(); context.findAncestorStateOfType<CommonScaffoldState>();
if (!context.mounted) return;
commonScaffoldState?.actions = [ commonScaffoldState?.actions = [
IconButton( IconButton(
onPressed: () { onPressed: () {
@@ -72,35 +92,29 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
}, },
icon: const Icon(Icons.sync), icon: const Icon(Icons.sync),
), ),
const SizedBox(
width: 8,
),
IconButton(
onPressed: () {
final profiles = globalState.appController.config.profiles;
showSheet(
title: appLocalizations.profilesSort,
context: context,
builder: (_) => SizedBox(
height: 400,
child: ReorderableProfiles(profiles: profiles),
),
);
},
icon: const Icon(Icons.sort),
iconSize: 26,
),
]; ];
}, },
); );
} }
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
hasPadding.dispose();
}
_changeProfile(String? id) async {
final appController = globalState.appController;
final config = appController.config;
if (id == config.currentProfileId) return;
config.currentProfileId = id;
applyConfigDebounce ??= debounce<Function()>(() async {
await appController.applyProfile();
appController.appState.delayMap = {};
appController.saveConfigPreferences();
});
applyConfigDebounce!();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FloatLayout( return FloatLayout(
@@ -125,7 +139,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
selector: (_, appState, config) => ProfilesSelectorState( selector: (_, appState, config) => ProfilesSelectorState(
profiles: config.profiles, profiles: config.profiles,
currentProfileId: config.currentProfileId, currentProfileId: config.currentProfileId,
viewMode: appState.viewMode, columns: other.getProfilesColumns(appState.viewWidth),
), ),
builder: (context, state, child) { builder: (context, state, child) {
if (state.profiles.isEmpty) { if (state.profiles.isEmpty) {
@@ -133,47 +147,30 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
label: appLocalizations.nullProfileDesc, label: appLocalizations.nullProfileDesc,
); );
} }
profileItemKeys = state.profiles
.map(
(profile) => GlobalObjectKey<_ProfileItemState>(profile.id))
.toList();
final columns = _getColumns(state.viewMode);
return Align( return Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: NotificationListener<ScrollNotification>( child: SingleChildScrollView(
onNotification: (scrollNotification) { padding: const EdgeInsets.only(
hasPadding.value = left: 16,
scrollNotification.metrics.maxScrollExtent > 0; right: 16,
return true; top: 16,
}, bottom: 88,
child: ValueListenableBuilder( ),
valueListenable: hasPadding, child: Grid(
builder: (_, hasPadding, __) { mainAxisSpacing: 16,
return SingleChildScrollView( crossAxisSpacing: 16,
padding: EdgeInsets.only( crossAxisCount: state.columns,
left: 16, children: [
right: 16, for (int i = 0; i < state.profiles.length; i++)
top: 16, GridItem(
bottom: 16 + (hasPadding ? 72 : 0), child: ProfileItem(
key: Key(state.profiles[i].id),
profile: state.profiles[i],
groupValue: state.currentProfileId,
onChanged: globalState.appController.changeProfile,
),
), ),
child: Grid( ],
mainAxisSpacing: 16,
crossAxisSpacing: 16,
crossAxisCount: columns,
children: [
for (int i = 0; i < state.profiles.length; i++)
GridItem(
child: ProfileItem(
key: profileItemKeys[i],
profile: state.profiles[i],
groupValue: state.currentProfileId,
onChanged: _changeProfile,
),
),
],
),
);
},
), ),
), ),
); );
@@ -184,7 +181,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
} }
} }
class ProfileItem extends StatefulWidget { class ProfileItem extends StatelessWidget {
final Profile profile; final Profile profile;
final String? groupValue; final String? groupValue;
final void Function(String? value) onChanged; final void Function(String? value) onChanged;
@@ -196,289 +193,330 @@ class ProfileItem extends StatefulWidget {
required this.onChanged, required this.onChanged,
}); });
@override _handleDeleteProfile(BuildContext context) async {
State<ProfileItem> createState() => _ProfileItemState(); globalState.showMessage(
} title: appLocalizations.tip,
message: TextSpan(
class _ProfileItemState extends State<ProfileItem> { text: appLocalizations.deleteProfileTip,
final isUpdating = ValueNotifier<bool>(false); ),
onTab: () async {
_handleDeleteProfile() async { await globalState.appController.deleteProfile(profile.id);
globalState.appController.deleteProfile(widget.profile.id); if (context.mounted) {
Navigator.of(context).pop();
}
},
);
} }
_handleUpdateProfile() async { _handleUpdateProfile() async {
await globalState.safeRun<void>(updateProfile); await globalState.safeRun<void>(updateProfile);
} }
Future updateProfile([isSingle = true]) async { Future updateProfile() async {
isUpdating.value = true; final appController = globalState.appController;
try { final config = appController.config;
final appController = globalState.appController; if (profile.type == ProfileType.file) return;
await appController.updateProfile(widget.profile); await globalState.safeRun(() async {
if (widget.profile.id == appController.config.currentProfile?.id && try {
!appController.appState.isStart) { config.setProfile(
globalState.appController.rawApplyProfile(); profile.copyWith(
} isUpdating: true,
} catch (e) { ),
isUpdating.value = false; );
if (!isSingle) { await appController.updateProfile(profile);
return e.toString(); if (profile.id == appController.config.currentProfile?.id) {
} else { appController.applyProfile(isPrue: true);
}
} catch (e) {
config.setProfile(
profile.copyWith(
isUpdating: false,
),
);
rethrow; rethrow;
} }
} });
isUpdating.value = false;
return null;
} }
_handleShowEditExtendPage() { _handleShowEditExtendPage(BuildContext context) {
showExtendPage( showExtendPage(
context, context,
body: EditProfile( body: EditProfile(
profile: widget.profile, profile: profile,
context: context, context: context,
), ),
title: "${appLocalizations.edit}${appLocalizations.profile}", title: "${appLocalizations.edit}${appLocalizations.profile}",
); );
} }
_handleViewProfile() { List<Widget> _buildUserInfo(BuildContext context, UserInfo userInfo) {
Navigator.of(context).push( final use = userInfo.upload + userInfo.download;
MaterialPageRoute( final total = userInfo.total;
builder: (context) => ViewProfile( if (total == 0) {
profile: widget.profile, return [];
), }
final useShow = TrafficValue(value: use).show;
final totalShow = TrafficValue(value: total).show;
final progress = total == 0 ? 0.0 : use / total;
final expireShow = userInfo.expire == 0
? appLocalizations.infiniteTime
: DateTime.fromMillisecondsSinceEpoch(userInfo.expire * 1000).show;
return [
LinearProgressIndicator(
minHeight: 6,
value: progress,
backgroundColor: context.colorScheme.primary.toSoft(),
), ),
); const SizedBox(
height: 8,
),
Text(
"$useShow / $totalShow · $expireShow",
style: context.textTheme.labelMedium?.toLight,
),
const SizedBox(
height: 4,
),
];
} }
_buildTitle(Profile profile) { List<Widget> _buildUrlProfileInfo(BuildContext context) {
final textTheme = context.textTheme; final userInfo = profile.userInfo;
return Container( return [
padding: const EdgeInsets.symmetric(vertical: 4), const SizedBox(
child: Column( height: 8,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
profile.label ?? profile.id,
style: textTheme.titleMedium,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Text(
profile.lastUpdateDate?.lastUpdateTimeDesc ?? '',
style: textTheme.labelMedium?.toLight,
),
],
),
Builder(builder: (context) {
final userInfo = profile.userInfo ?? const UserInfo();
final use = userInfo.upload + userInfo.download;
final total = userInfo.total;
final useShow = TrafficValue(value: use).show;
final totalShow = TrafficValue(value: total).show;
final progress = total == 0 ? 0.0 : use / total;
final expireShow = userInfo.expire == 0
? appLocalizations.infiniteTime
: DateTime.fromMillisecondsSinceEpoch(userInfo.expire * 1000)
.show;
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
margin: const EdgeInsets.symmetric(
vertical: 8,
),
child: LinearProgressIndicator(
minHeight: 6,
value: progress,
),
),
Text(
"$useShow / $totalShow",
style: textTheme.labelMedium?.toLight,
),
const SizedBox(
height: 2,
),
Row(
children: [
Text(
expireShow,
style: textTheme.labelMedium?.toLighter,
),
],
)
],
);
// final child = switch (userInfo != null) {
// true => () {
// final use = userInfo!.upload + userInfo.download;
// final total = userInfo.total;
// final useShow = TrafficValue(value: use).show;
// final totalShow = TrafficValue(value: total).show;
// final progress = total == 0 ? 0.0 : use / total;
// final expireShow = userInfo.expire == 0
// ? appLocalizations.infiniteTime
// : DateTime.fromMillisecondsSinceEpoch(
// userInfo.expire * 1000)
// .show;
// return Column(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Container(
// margin: const EdgeInsets.symmetric(
// vertical: 8,
// ),
// child: LinearProgressIndicator(
// minHeight: 6,
// value: progress,
// ),
// ),
// Text(
// "$useShow / $totalShow",
// style: textTheme.labelMedium?.toLight(),
// ),
// const SizedBox(
// height: 2,
// ),
// Row(
// children: [
// Text(
// appLocalizations.expirationTime,
// style: textTheme.labelMedium?.toLighter(),
// ),
// const SizedBox(
// width: 4,
// ),
// Text(
// expireShow,
// style: textTheme.labelMedium?.toLighter(),
// ),
// ],
// )
// ],
// );
// }(),
// false => Column(
// children: [
// Padding(
// padding: const EdgeInsets.only(top: 8),
// child: CommonChip(
// onPressed: _handleViewProfile,
// avatar: const Icon(Icons.remove_red_eye),
// label: appLocalizations.view,
// ),
// ),
// ],
// ),
// };
// final measure = globalState.appController.measure;
// final height = 6 + 8 * 2 + 2 + measure.labelMediumHeight * 2;
// return SizedBox(
// height: height,
// child: child,
// );
}),
],
), ),
); if (userInfo != null) ..._buildUserInfo(context, userInfo),
Text(
profile.lastUpdateDate?.lastUpdateTimeDesc ?? "",
style: context.textTheme.labelMedium?.toLight,
),
];
} }
@override List<Widget> _buildFileProfileInfo(BuildContext context) {
void dispose() { return [
isUpdating.dispose(); const SizedBox(
super.dispose(); height: 8,
),
Text(
profile.lastUpdateDate?.lastUpdateTimeDesc ?? "",
style: context.textTheme.labelMedium?.toLight,
),
];
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final profile = widget.profile;
final groupValue = widget.groupValue;
final onChanged = widget.onChanged;
return CommonCard( return CommonCard(
child: ListItem.radio( isSelected: profile.id == groupValue,
onPressed: () {
onChanged(profile.id);
},
child: ListItem(
key: Key(profile.id), key: Key(profile.id),
horizontalTitleGap: 16, horizontalTitleGap: 16,
delegate: RadioDelegate<String?>(
value: profile.id,
groupValue: groupValue,
onChanged: onChanged,
),
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
trailing: SizedBox( trailing: SizedBox(
height: 48, height: 40,
width: 48, width: 40,
child: ValueListenableBuilder( child: FadeBox(
valueListenable: isUpdating, child: profile.isUpdating
builder: (_, isUpdating, ___) { ? const Padding(
return FadeBox( padding: EdgeInsets.all(8),
child: isUpdating child: CircularProgressIndicator(),
? const Padding( )
padding: EdgeInsets.all(8), : CommonPopupMenu<ProfileActions>(
child: CircularProgressIndicator(), items: [
) CommonPopupMenuItem(
: CommonPopupMenu<ProfileActions>( action: ProfileActions.edit,
items: [ label: appLocalizations.edit,
CommonPopupMenuItem( iconData: Icons.edit,
action: ProfileActions.edit, ),
label: appLocalizations.edit, if (profile.type == ProfileType.url)
iconData: Icons.edit, CommonPopupMenuItem(
), action: ProfileActions.update,
if (profile.type == ProfileType.url) label: appLocalizations.update,
CommonPopupMenuItem( iconData: Icons.sync,
action: ProfileActions.update, ),
label: appLocalizations.update, CommonPopupMenuItem(
iconData: Icons.sync, action: ProfileActions.delete,
), label: appLocalizations.delete,
CommonPopupMenuItem( iconData: Icons.delete,
action: ProfileActions.view, ),
label: appLocalizations.view, ],
iconData: Icons.visibility, onSelected: (ProfileActions? action) async {
), switch (action) {
CommonPopupMenuItem( case ProfileActions.edit:
action: ProfileActions.delete, _handleShowEditExtendPage(context);
label: appLocalizations.delete, break;
iconData: Icons.delete, case ProfileActions.delete:
), _handleDeleteProfile(context);
], break;
onSelected: (ProfileActions? action) async { case ProfileActions.update:
switch (action) { _handleUpdateProfile();
case ProfileActions.edit: break;
_handleShowEditExtendPage(); case null:
break; break;
case ProfileActions.delete: }
_handleDeleteProfile(); },
break; ),
case ProfileActions.update: ),
_handleUpdateProfile(); ),
break; title: Container(
case ProfileActions.view: padding: const EdgeInsets.symmetric(vertical: 4),
_handleViewProfile(); child: Column(
break; crossAxisAlignment: CrossAxisAlignment.start,
case null: mainAxisAlignment: MainAxisAlignment.center,
break; children: [
} Text(
}, profile.label ?? profile.id,
)); style: context.textTheme.titleMedium,
}, maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
...switch (profile.type) {
ProfileType.file => _buildFileProfileInfo(context),
ProfileType.url => _buildUrlProfileInfo(context),
},
],
),
],
), ),
), ),
title: _buildTitle(profile),
tileTitleAlignment: ListTileTitleAlignment.titleHeight, tileTitleAlignment: ListTileTitleAlignment.titleHeight,
), ),
); );
} }
} }
class ReorderableProfiles extends StatefulWidget {
final List<Profile> profiles;
const ReorderableProfiles({
super.key,
required this.profiles,
});
@override
State<ReorderableProfiles> createState() => _ReorderableProfilesState();
}
class _ReorderableProfilesState extends State<ReorderableProfiles> {
late List<Profile> profiles;
@override
void initState() {
super.initState();
profiles = List.from(widget.profiles);
}
Widget proxyDecorator(
Widget child,
int index,
Animation<double> animation,
) {
final profile = profiles[index];
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: Container(
key: Key(profile.id),
padding: const EdgeInsets.symmetric(vertical: 4),
child: CommonCard(
type: CommonCardType.filled,
child: ListTile(
contentPadding: const EdgeInsets.only(
right: 44,
left: 16,
),
title: Text(profile.label ?? profile.id),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
flex: 1,
child: ReorderableListView.builder(
buildDefaultDragHandles: false,
padding: const EdgeInsets.all(12),
proxyDecorator: proxyDecorator,
onReorder: (int oldIndex, int newIndex) {
if (oldIndex == newIndex) return;
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,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 12,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
onPressed: () {
Navigator.of(context).pop();
globalState.appController.config.profiles = profiles;
},
icon: const Icon(
Icons.check,
),
iconSize: 32,
padding: const EdgeInsets.all(8),
),
],
),
),
],
);
}
}

View File

@@ -7,7 +7,7 @@ import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:re_editor/re_editor.dart'; import 'package:re_editor/re_editor.dart';
import 'package:re_highlight/languages/yaml.dart'; import 'package:re_highlight/languages/yaml.dart';
import 'package:re_highlight/styles/intellij-light.dart'; import 'package:re_highlight/styles/atom-one-light.dart';
class ViewProfile extends StatefulWidget { class ViewProfile extends StatefulWidget {
final Profile profile; final Profile profile;
@@ -23,29 +23,27 @@ class ViewProfile extends StatefulWidget {
class _ViewProfileState extends State<ViewProfile> { class _ViewProfileState extends State<ViewProfile> {
bool readOnly = true; bool readOnly = true;
CodeLineEditingController? controller; final CodeLineEditingController _controller = CodeLineEditingController();
final contentNotifier = ValueNotifier<String>("");
final key = GlobalKey<CommonScaffoldState>(); final key = GlobalKey<CommonScaffoldState>();
final _focusNode = FocusNode();
String? rawText;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async { appPath.getProfilePath(widget.profile.id).then((path) async {
final profilePath = await appPath.getProfilePath(widget.profile.id); if (path == null) return;
if (profilePath == null) { final file = File(path);
return; rawText = await file.readAsString();
} _controller.text = rawText ?? "";
final file = File(profilePath);
final text = await file.readAsString();
contentNotifier.value = text;
}); });
} }
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
contentNotifier.dispose(); _controller.dispose();
controller?.dispose(); _focusNode.dispose();
} }
Profile get profile => widget.profile; Profile get profile => widget.profile;
@@ -56,16 +54,9 @@ class _ViewProfileState extends State<ViewProfile> {
readOnly = false; readOnly = false;
}); });
} else { } else {
final text = controller?.text; if (_controller.text == rawText) return;
if (text == null || text == contentNotifier.value) {
setState(() {
readOnly = true;
});
return;
}
contentNotifier.value = text;
final newProfile = await key.currentState?.loadingRun<Profile>(() async { final newProfile = await key.currentState?.loadingRun<Profile>(() async {
return await profile.saveFileWithString(text); return await profile.saveFileWithString(_controller.text);
}); });
if (newProfile == null) return; if (newProfile == null) return;
globalState.appController.config.setProfile(newProfile); globalState.appController.config.setProfile(newProfile);
@@ -81,74 +72,67 @@ class _ViewProfileState extends State<ViewProfile> {
key: key, key: key,
actions: [ actions: [
IconButton( IconButton(
onPressed: controller?.undo, onPressed: _controller.undo,
icon: const Icon(Icons.undo), icon: const Icon(Icons.undo),
), ),
IconButton( IconButton(
onPressed: controller?.redo, onPressed: _controller.redo,
icon: const Icon(Icons.redo), icon: const Icon(Icons.redo),
), ),
if (!widget.profile.realAutoUpdate) IconButton(
IconButton( onPressed: _handleChangeReadOnly,
onPressed: _handleChangeReadOnly, icon: readOnly ? const Icon(Icons.edit) : const Icon(Icons.save),
icon: readOnly ? const Icon(Icons.edit) : const Icon(Icons.save), ),
),
const SizedBox(
width: 8,
)
], ],
body: ValueListenableBuilder( body: CodeEditor(
valueListenable: contentNotifier, readOnly: readOnly,
builder: (_, value, __) { focusNode: _focusNode,
if (value.isEmpty) return Container(); scrollbarBuilder: (context, child, details) {
controller = CodeLineEditingController.fromText(value); return Scrollbar(
return CodeEditor( controller: details.controller,
autofocus: false, thickness: 8,
readOnly: readOnly, radius: const Radius.circular(2),
scrollbarBuilder: (context, child, details) { interactive: true,
return Scrollbar( child: child,
controller: details.controller,
thickness: 8,
radius: const Radius.circular(2),
interactive: true,
child: child,
);
},
showCursorWhenReadOnly: false,
controller: controller,
toolbarController:
!readOnly ? const ContextMenuControllerImpl() : null,
shortcutsActivatorsBuilder:
const DefaultCodeShortcutsActivatorsBuilder(),
indicatorBuilder:
(context, editingController, chunkController, notifier) {
return Row(
children: [
DefaultCodeLineNumber(
controller: editingController,
notifier: notifier,
),
DefaultCodeChunkIndicator(
width: 20,
controller: chunkController,
notifier: notifier,
)
],
);
},
style: CodeEditorStyle(
fontSize: 14,
codeTheme: CodeHighlightTheme(
languages: {
'yaml': CodeHighlightThemeMode(
mode: langYaml,
)
},
theme: intellijLightTheme,
),
),
); );
}, },
showCursorWhenReadOnly: false,
controller: _controller,
shortcutsActivatorsBuilder:
const DefaultCodeShortcutsActivatorsBuilder(),
indicatorBuilder: (
context,
editingController,
chunkController,
notifier,
) {
return Row(
children: [
DefaultCodeLineNumber(
controller: editingController,
notifier: notifier,
),
DefaultCodeChunkIndicator(
width: 20,
controller: chunkController,
notifier: notifier,
)
],
);
},
toolbarController:
!readOnly ? ContextMenuControllerImpl(_focusNode) : null,
style: CodeEditorStyle(
fontSize: 14,
codeTheme: CodeHighlightTheme(
languages: {
'yaml': CodeHighlightThemeMode(
mode: langYaml,
)
},
theme: atomOneLightTheme,
),
),
), ),
title: widget.profile.label ?? widget.profile.id, title: widget.profile.label ?? widget.profile.id,
); );
@@ -164,10 +148,38 @@ class ContextMenuItemWidget extends PopupMenuItem<void> {
} }
class ContextMenuControllerImpl implements SelectionToolbarController { class ContextMenuControllerImpl implements SelectionToolbarController {
const ContextMenuControllerImpl(); OverlayEntry? _overlayEntry;
final FocusNode focusNode;
ContextMenuControllerImpl(
this.focusNode,
);
_removeOverLayEntry() {
_overlayEntry?.remove();
_overlayEntry = null;
}
@override @override
void hide(BuildContext context) {} void hide(BuildContext context) {
// _removeOverLayEntry();
}
_handleCut(CodeLineEditingController controller) {
controller.cut();
_removeOverLayEntry();
}
_handleCopy(CodeLineEditingController controller) async {
await controller.copy();
_removeOverLayEntry();
}
_handlePaste(CodeLineEditingController controller) {
controller.paste();
_removeOverLayEntry();
}
@override @override
void show({ void show({
@@ -181,27 +193,40 @@ class ContextMenuControllerImpl implements SelectionToolbarController {
if (controller.selectedText.isEmpty) { if (controller.selectedText.isEmpty) {
return; return;
} }
showMenu( _removeOverLayEntry();
context: context, final relativeRect = RelativeRect.fromSize(
position: RelativeRect.fromSize( (anchors.primaryAnchor) &
(anchors.secondaryAnchor ?? anchors.primaryAnchor) & const Size(150, double.infinity),
const Size(150, double.infinity), MediaQuery.of(context).size,
MediaQuery.of(context).size,
),
items: [
ContextMenuItemWidget(
text: appLocalizations.cut,
onTap: controller.cut,
),
ContextMenuItemWidget(
text: appLocalizations.copy,
onTap: controller.copy,
),
ContextMenuItemWidget(
text: appLocalizations.paste,
onTap: controller.paste,
),
],
); );
_overlayEntry ??= OverlayEntry(
builder: (context) => ValueListenableBuilder<CodeLineEditingValue>(
valueListenable: controller,
builder: (_, __, child) {
if (controller.selectedText.isEmpty) {
_removeOverLayEntry();
}
return child!;
},
child: Positioned(
left: relativeRect.left,
top: relativeRect.top,
child: Material(
color: Colors.transparent,
child: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(focusNode);
},
child: Container(
width: 200,
height: 200,
color: Colors.green,
),
),
),
),
),
);
Overlay.of(context).insert(_overlayEntry!);
} }
} }

View File

@@ -1,6 +1,6 @@
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/fragments/proxies/common.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart'; import 'package:fl_clash/widgets/widgets.dart';
@@ -10,7 +10,7 @@ import 'package:provider/provider.dart';
class ProxyCard extends StatelessWidget { class ProxyCard extends StatelessWidget {
final String groupName; final String groupName;
final Proxy proxy; final Proxy proxy;
final bool isSelected; final GroupType groupType;
final CommonCardType style; final CommonCardType style;
final ProxyCardType type; final ProxyCardType type;
@@ -18,7 +18,7 @@ class ProxyCard extends StatelessWidget {
super.key, super.key,
required this.groupName, required this.groupName,
required this.proxy, required this.proxy,
required this.isSelected, required this.groupType,
this.style = CommonCardType.plain, this.style = CommonCardType.plain,
required this.type, required this.type,
}); });
@@ -69,46 +69,51 @@ class ProxyCard extends StatelessWidget {
if (type == ProxyCardType.min) { if (type == ProxyCardType.min) {
return SizedBox( return SizedBox(
height: measure.bodyMediumHeight * 1, height: measure.bodyMediumHeight * 1,
child: Text( child: EmojiText(
proxy.name, proxy.name,
maxLines: 1, maxLines: 1,
style: context.textTheme.bodyMedium?.copyWith( overflow: TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis, style: context.textTheme.bodyMedium,
),
), ),
); );
} else { } else {
return SizedBox( return SizedBox(
height: measure.bodyMediumHeight * 2, height: measure.bodyMediumHeight * 2,
child: Text( child: EmojiText(
proxy.name, proxy.name,
maxLines: 2, maxLines: 2,
style: context.textTheme.bodyMedium?.copyWith( overflow: TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis, style: context.textTheme.bodyMedium,
),
), ),
); );
} }
} }
_changeProxy(BuildContext context) { _changeProxy(BuildContext context) async {
final appController = globalState.appController; final appController = globalState.appController;
final group = appController.appState.getGroupWithName(groupName)!; final isUrlTest = groupType == GroupType.URLTest;
if (group.type != GroupType.Selector) { final isSelector = groupType == GroupType.Selector;
globalState.showSnackBar( if (isUrlTest || isSelector) {
context, final currentProxyName =
message: appLocalizations.notSelectedTip, appController.config.currentSelectedMap[groupName];
final nextProxyName = switch (isUrlTest) {
true => currentProxyName == proxy.name ? "" : proxy.name,
false => proxy.name,
};
appController.config.updateCurrentSelectedMap(
groupName,
nextProxyName,
); );
appController.changeProxy(
groupName: groupName,
proxyName: nextProxyName,
);
await appController.updateGroupDebounce();
return; return;
} }
globalState.appController.config.updateCurrentSelectedMap( globalState.showSnackBar(
groupName, context,
proxy.name, message: appLocalizations.notSelectedTip,
);
globalState.changeProxy(
config: appController.config,
groupName: groupName,
proxyName: proxy.name,
); );
} }
@@ -117,75 +122,123 @@ class ProxyCard extends StatelessWidget {
final measure = globalState.appController.measure; final measure = globalState.appController.measure;
final delayText = _buildDelayText(); final delayText = _buildDelayText();
final proxyNameText = _buildProxyNameText(context); final proxyNameText = _buildProxyNameText(context);
return CommonCard( return currentGroupProxyNameBuilder(
type: style, groupName: groupName,
key: key, builder: (currentGroupName) {
onPressed: () { return Stack(
_changeProxy(context);
},
isSelected: isSelected,
child: Container(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
proxyNameText, CommonCard(
const SizedBox( type: style,
height: 8, key: key,
), onPressed: () {
if (type == ProxyCardType.expand) ...[ _changeProxy(context);
SizedBox( },
height: measure.bodySmallHeight, isSelected: currentGroupName == proxy.name,
child: Selector<AppState, String>( child: Container(
selector: (context, appState) => appState.getDesc( padding: const EdgeInsets.all(12),
proxy.type, child: Column(
proxy.name, mainAxisSize: MainAxisSize.min,
), crossAxisAlignment: CrossAxisAlignment.start,
builder: (_, desc, __) {
return TooltipText(
text: Text(
desc,
style: context.textTheme.bodySmall?.copyWith(
overflow: TextOverflow.ellipsis,
color: context.textTheme.bodySmall?.color?.toLight(),
),
),
);
},
),
),
const SizedBox(
height: 8,
),
delayText,
] else
SizedBox(
height: measure.bodySmallHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Flexible( proxyNameText,
flex: 1, const SizedBox(
child: TooltipText( height: 8,
text: Text( ),
proxy.type, if (type == ProxyCardType.expand) ...[
style: context.textTheme.bodySmall?.copyWith( SizedBox(
overflow: TextOverflow.ellipsis, height: measure.bodySmallHeight,
color: child: Selector<AppState, String>(
context.textTheme.bodySmall?.color?.toLight(), selector: (context, appState) => appState.getDesc(
proxy.type,
proxy.name,
), ),
builder: (_, desc, __) {
return EmojiText(
desc,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall?.copyWith(
color: context.textTheme.bodySmall?.color
?.toLight(),
),
);
},
),
),
const SizedBox(
height: 8,
),
delayText,
] else
SizedBox(
height: measure.bodySmallHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
flex: 1,
child: TooltipText(
text: Text(
proxy.type,
style: context.textTheme.bodySmall?.copyWith(
overflow: TextOverflow.ellipsis,
color: context.textTheme.bodySmall?.color
?.toLight(),
),
),
),
),
delayText,
],
), ),
), ),
),
delayText,
], ],
), ),
), ),
),
if (groupType == GroupType.URLTest)
Selector<Config, String>(
selector: (_, config) {
final selectedProxyName =
config.currentSelectedMap[groupName];
return selectedProxyName ?? '';
},
builder: (_, value, __) {
if (value != proxy.name) return Container();
return Positioned.fill(
child: Container(
alignment: Alignment.topRight,
margin: const EdgeInsets.all(8),
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color:
Theme.of(context).colorScheme.secondaryContainer,
),
child: const SelectIcon(),
),
),
);
},
child: Positioned.fill(
child: Container(
alignment: Alignment.topRight,
margin: const EdgeInsets.all(8),
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.secondaryContainer,
),
child: const SelectIcon(),
),
),
),
)
], ],
), );
), },
); );
} }
} }

View File

@@ -1,24 +1,25 @@
import 'dart:math'; import 'dart:math';
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/constant.dart'; import 'package:fl_clash/common/other.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
Widget currentProxyNameBuilder({ Widget currentGroupProxyNameBuilder({
required String groupName, required String groupName,
required Widget Function(String) builder, required Widget Function(String currentGroupName) builder,
}) { }) {
return Selector2<AppState, Config, String>( return Selector2<AppState, Config, String>(
selector: (_, appState, config) { selector: (_, appState, config) {
final group = appState.getGroupWithName(groupName); final group = appState.getGroupWithName(groupName);
return config.currentSelectedMap[groupName] ?? group?.now ?? ''; final selectedProxyName = config.currentSelectedMap[groupName];
return group?.getCurrentSelectedName(selectedProxyName ?? "") ?? "";
}, },
builder: (_, value, ___) { builder: (_, currentGroupName, ___) {
return builder(value); return builder(currentGroupName);
}, },
); );
} }
@@ -41,20 +42,17 @@ double getItemHeight(ProxyCardType proxyCardType) {
delayTest(List<Proxy> proxies) async { delayTest(List<Proxy> proxies) async {
final appController = globalState.appController; final appController = globalState.appController;
for (final proxy in proxies) { final delayProxies = proxies.map<Future>((proxy) async {
final proxyName = final proxyName = appController.appState.getRealProxyName(proxy.name);
appController.appState.getRealProxyName(proxy.name) ?? proxy.name;
globalState.appController.setDelay( globalState.appController.setDelay(
Delay( Delay(
name: proxyName, name: proxyName,
value: 0, value: 0,
), ),
); );
clashCore.getDelay(proxyName).then((delay) { globalState.appController.setDelay(await clashCore.getDelay(proxyName));
globalState.appController.setDelay(delay); });
}); await Future.wait(delayProxies);
}
await Future.delayed(httpTimeoutDuration + moreDuration);
appController.appState.sortNum++; appController.appState.sortNum++;
} }
@@ -63,13 +61,16 @@ double getScrollToSelectedOffset({
required List<Proxy> proxies, required List<Proxy> proxies,
}) { }) {
final appController = globalState.appController; final appController = globalState.appController;
final columns = appController.columns; final columns = other.getProxiesColumns(
appController.appState.viewWidth,
appController.config.proxiesLayout,
);
final proxyCardType = appController.config.proxyCardType; final proxyCardType = appController.config.proxyCardType;
final selectedName = appController.getCurrentSelectedName(groupName); final selectedName = appController.getCurrentSelectedName(groupName);
final findSelectedIndex = proxies.indexWhere( final findSelectedIndex = proxies.indexWhere(
(proxy) => proxy.name == selectedName, (proxy) => proxy.name == selectedName,
); );
final selectedIndex = findSelectedIndex != -1 ? findSelectedIndex : 0; final selectedIndex = findSelectedIndex != -1 ? findSelectedIndex : 0;
final rows = ((selectedIndex - 1) / columns).ceil(); final rows = (selectedIndex / columns).floor();
return max(rows * (getItemHeight(proxyCardType) + 8) - 8, 0); return max(rows * (getItemHeight(proxyCardType) + 8) - 8, 0);
} }

View File

@@ -4,6 +4,7 @@ import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/card.dart'; import 'package:fl_clash/widgets/card.dart';
import 'package:fl_clash/widgets/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -140,17 +141,13 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
final children = proxies final children = proxies
.map<Widget>( .map<Widget>(
(proxy) => Flexible( (proxy) => Flexible(
child: currentProxyNameBuilder( child: ProxyCard(
groupName: group.name, type: type,
builder: (currentProxyName) { groupType: group.type,
return ProxyCard( key: ValueKey('$groupName.${proxy.name}'),
type: type, proxy: proxy,
isSelected: currentProxyName == proxy.name, groupName: groupName,
key: ValueKey('$groupName.${proxy.name}'), ),
proxy: proxy,
groupName: groupName,
);
}),
), ),
) )
.fill( .fill(
@@ -209,6 +206,9 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
} }
_scrollToGroupSelected(String groupName) { _scrollToGroupSelected(String groupName) {
if (_controller.position.maxScrollExtent == 0) {
return;
}
final appController = globalState.appController; final appController = globalState.appController;
final currentGroups = appController.appState.currentGroups; final currentGroups = appController.appState.currentGroups;
final groupNames = currentGroups.map((e) => e.name).toList(); final groupNames = currentGroups.map((e) => e.name).toList();
@@ -238,7 +238,10 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
currentUnfoldSet: config.currentUnfoldSet, currentUnfoldSet: config.currentUnfoldSet,
proxyCardType: config.proxyCardType, proxyCardType: config.proxyCardType,
proxiesSortType: config.proxiesSortType, proxiesSortType: config.proxiesSortType,
columns: globalState.appController.columns, columns: other.getProxiesColumns(
appState.viewWidth,
config.proxiesLayout,
),
sortNum: appState.sortNum, sortNum: appState.sortNum,
); );
}, },
@@ -393,6 +396,18 @@ class _ListHeaderState extends State<ListHeader>
super.dispose(); super.dispose();
} }
@override
void didUpdateWidget(ListHeader oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.isExpand != widget.isExpand) {
if (isExpand) {
_animationController.value = 1.0;
} else {
_animationController.value = 0.0;
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CommonCard( return CommonCard(
@@ -410,9 +425,7 @@ class _ListHeaderState extends State<ListHeader>
children: [ children: [
Text( Text(
groupName, groupName,
style: context.textTheme.titleMedium?.copyWith( style: context.textTheme.titleMedium,
color: context.colorScheme.primary,
),
), ),
const SizedBox( const SizedBox(
height: 4, height: 4,
@@ -430,20 +443,20 @@ class _ListHeaderState extends State<ListHeader>
), ),
Flexible( Flexible(
flex: 1, flex: 1,
child: currentProxyNameBuilder( child: currentGroupProxyNameBuilder(
groupName: groupName, groupName: groupName,
builder: (value) { builder: (currentGroupName) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
if (value.isNotEmpty) ...[ if (currentGroupName.isNotEmpty) ...[
Flexible( Flexible(
flex: 1, flex: 1,
child: Text( child: EmojiText(
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
" · $value", " · $currentGroupName",
style: context style: context
.textTheme.labelMedium?.toLight, .textTheme.labelMedium?.toLight,
), ),

View File

@@ -0,0 +1,208 @@
import 'dart:convert';
import 'dart:io';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/app.dart';
import 'package:fl_clash/models/ffi.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
typedef UpdatingMap = Map<String, bool>;
class Providers extends StatefulWidget {
const Providers({
super.key,
});
@override
State<Providers> createState() => _ProvidersState();
}
class _ProvidersState extends State<Providers> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(
(_) {
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [
IconButton(
onPressed: () {
_updateProviders();
},
icon: const Icon(
Icons.sync,
),
)
];
},
);
}
_updateProviders() async {
final appState = globalState.appController.appState;
final providers = globalState.appController.appState.providers;
final updateProviders = providers.map<Future>(
(provider) async {
appState.setProvider(
provider.copyWith(isUpdating: true),
);
await clashCore.updateExternalProvider(
providerName: provider.name,
);
appState.setProvider(
clashCore.getExternalProvider(provider.name),
);
},
);
await Future.wait(updateProviders);
await globalState.appController.updateGroupDebounce();
}
@override
Widget build(BuildContext context) {
return Selector<AppState, List<ExternalProvider>>(
selector: (_, appState) => appState.providers,
builder: (_, providers, ___) {
return ListView.separated(
itemBuilder: (_, index) {
return ProviderItem(
provider: providers[index],
);
},
separatorBuilder: (_, index) {
return const Divider(
height: 0,
);
},
itemCount: providers.length,
);
},
);
}
}
class ProviderItem extends StatelessWidget {
final ExternalProvider provider;
const ProviderItem({
super.key,
required this.provider,
});
_handleUpdateProvider() async {
await globalState.safeRun<void>(() async {
final appState = globalState.appController.appState;
if (provider.vehicleType != "HTTP") return;
await globalState.safeRun(() async {
appState.setProvider(
provider.copyWith(
isUpdating: true,
),
);
final message = await clashCore.updateExternalProvider(
providerName: provider.name,
);
if (message.isNotEmpty) throw message;
});
appState.setProvider(
clashCore.getExternalProvider(provider.name),
);
});
await globalState.appController.updateGroupDebounce();
}
_handleSideLoadProvider() async {
await globalState.safeRun<void>(() async {
final platformFile = await picker.pickerFile();
final appState = globalState.appController.appState;
final bytes = platformFile?.bytes;
if (bytes == null) return;
final file = await File(provider.path).create(recursive: true);
await file.writeAsBytes(bytes);
final providerName = provider.name;
var message = await clashCore.sideLoadExternalProvider(
providerName: providerName,
data: utf8.decode(bytes),
);
if (message.isNotEmpty) throw message;
appState.setProvider(
clashCore.getExternalProvider(provider.name),
);
if (message.isNotEmpty) throw message;
});
await globalState.appController.updateGroupDebounce();
}
String _buildProviderDesc() {
final baseInfo =
"${provider.type}(${provider.vehicleType}) · ${provider.updateAt.lastUpdateTimeDesc}";
final count = provider.count;
return switch (count == 0) {
true => baseInfo,
false => "$baseInfo · $count${appLocalizations.entries}",
};
}
@override
Widget build(BuildContext context) {
return ListItem(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
title: Text(provider.name),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 4,
),
Text(
_buildProviderDesc(),
),
Text(
provider.path,
style: context.textTheme.bodyMedium?.toLight,
),
const SizedBox(
height: 8,
),
Wrap(
runSpacing: 6,
spacing: 12,
children: [
CommonChip(
avatar: const Icon(Icons.upload),
label: appLocalizations.upload,
onPressed: _handleSideLoadProvider,
),
if (provider.vehicleType == "HTTP")
CommonChip(
avatar: const Icon(Icons.sync),
label: appLocalizations.sync,
onPressed: _handleUpdateProvider,
),
],
),
],
),
trailing: SizedBox(
height: 48,
width: 48,
child: FadeBox(
child: provider.isUpdating
? const Padding(
padding: EdgeInsets.all(8),
child: CircularProgressIndicator(),
)
: const SizedBox(),
),
),
);
}
}

View File

@@ -6,6 +6,7 @@ import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'providers.dart';
import 'setting.dart'; import 'setting.dart';
import 'tab.dart'; import 'tab.dart';
@@ -19,18 +20,37 @@ class ProxiesFragment extends StatefulWidget {
class _ProxiesFragmentState extends State<ProxiesFragment> { class _ProxiesFragmentState extends State<ProxiesFragment> {
final GlobalKey<ProxiesTabFragmentState> _proxiesTabKey = GlobalKey(); final GlobalKey<ProxiesTabFragmentState> _proxiesTabKey = GlobalKey();
_initActions(ProxiesType proxiesType) { _initActions(ProxiesType proxiesType, bool hasProvider) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final commonScaffoldState = final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>(); context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [ commonScaffoldState?.actions = [
if (hasProvider) ...[
IconButton(
onPressed: () {
showExtendPage(
forceNotSide: true,
extendPageWidth: 360,
context,
body: const Providers(),
title: appLocalizations.externalResources,
);
},
icon: const Icon(
Icons.swap_vert_circle_outlined,
),
),
const SizedBox(
width: 8,
),
],
if (proxiesType == ProxiesType.tab) ...[ if (proxiesType == ProxiesType.tab) ...[
IconButton( IconButton(
onPressed: () { onPressed: () {
_proxiesTabKey.currentState?.scrollToGroupSelected(); _proxiesTabKey.currentState?.scrollToGroupSelected();
}, },
icon: const Icon( icon: const Icon(
Icons.gps_fixed, Icons.adjust_outlined,
), ),
), ),
const SizedBox( const SizedBox(
@@ -60,18 +80,18 @@ class _ProxiesFragmentState extends State<ProxiesFragment> {
return Selector<Config, ProxiesType>( return Selector<Config, ProxiesType>(
selector: (_, config) => config.proxiesType, selector: (_, config) => config.proxiesType,
builder: (_, proxiesType, __) { builder: (_, proxiesType, __) {
return Selector<AppState, bool>( return ProxiesActionsBuilder(
selector: (_, appState) => appState.currentLabel == 'proxies', builder: (state, child) {
builder: (_, isCurrent, child) { if (state.isCurrent) {
if (isCurrent) { _initActions(proxiesType, state.hasProvider);
_initActions(proxiesType);
} }
return switch (proxiesType) { return child!;
ProxiesType.tab => ProxiesTabFragment( },
key: _proxiesTabKey, child: switch (proxiesType) {
), ProxiesType.tab => ProxiesTabFragment(
ProxiesType.list => const ProxiesListFragment(), key: _proxiesTabKey,
}; ),
ProxiesType.list => const ProxiesListFragment(),
}, },
); );
}, },

View File

@@ -33,6 +33,14 @@ class ProxiesSettingWidget extends StatelessWidget {
}; };
} }
String getTextForProxiesLayout(ProxiesLayout proxiesLayout) {
return switch (proxiesLayout) {
ProxiesLayout.tight => appLocalizations.tight,
ProxiesLayout.standard => appLocalizations.standard,
ProxiesLayout.loose => appLocalizations.loose,
};
}
List<Widget> _buildStyleSetting() { List<Widget> _buildStyleSetting() {
return generateSection( return generateSection(
title: appLocalizations.style, title: appLocalizations.style,
@@ -132,36 +140,28 @@ class ProxiesSettingWidget extends StatelessWidget {
); );
} }
List<Widget> _buildColumnsSetting() { List<Widget> _buildLayoutSetting() {
return generateSection( return generateSection(
title: appLocalizations.columns, title: appLocalizations.layout,
items: [ items: [
SingleChildScrollView( SingleChildScrollView(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
), ),
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Selector2<AppState, Config, ColumnsSelectorState>( child: Selector< Config, ProxiesLayout>(
selector: (_, appState, config) => ColumnsSelectorState( selector: (_, config) => config.proxiesLayout,
columns: config.proxiesColumns, builder: (_, proxiesLayout, __) {
viewMode: appState.viewMode,
),
builder: (_, state, __) {
final config = globalState.appController.config; final config = globalState.appController.config;
final targetColumnsArray = viewModeColumnsMap[state.viewMode]!;
final currentColumns = other.getColumns(
state.viewMode,
state.columns,
);
return Wrap( return Wrap(
spacing: 16, spacing: 16,
children: [ children: [
for (final item in targetColumnsArray) for (final item in ProxiesLayout.values)
SettingTextCard( SettingTextCard(
other.getColumnsTextForInt(item), getTextForProxiesLayout(item),
isSelected: item == currentColumns, isSelected: item == proxiesLayout,
onPressed: () { onPressed: () {
config.proxiesColumns = item; config.proxiesLayout = item;
}, },
) )
], ],
@@ -183,80 +183,10 @@ class ProxiesSettingWidget extends StatelessWidget {
children: [ children: [
..._buildStyleSetting(), ..._buildStyleSetting(),
..._buildSortSetting(), ..._buildSortSetting(),
..._buildColumnsSetting(), ..._buildLayoutSetting(),
..._buildSizeSetting(), ..._buildSizeSetting(),
], ],
), ),
); );
} }
} }
class SettingInfoCard extends StatelessWidget {
final Info info;
final bool? isSelected;
final VoidCallback onPressed;
const SettingInfoCard(
this.info, {
super.key,
this.isSelected,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return CommonCard(
isSelected: isSelected,
onPressed: onPressed,
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(
child: Icon(info.iconData),
),
const SizedBox(
width: 8,
),
Flexible(
child: Text(
info.label,
style: context.textTheme.bodyMedium,
),
),
],
),
),
);
}
}
class SettingTextCard extends StatelessWidget {
final String text;
final bool? isSelected;
final VoidCallback onPressed;
const SettingTextCard(
this.text, {
super.key,
this.isSelected,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return CommonCard(
onPressed: onPressed,
isSelected: isSelected,
child: Padding(
padding: const EdgeInsets.all(12),
child: Text(
text,
style: context.textTheme.bodyMedium,
),
),
);
}
}

View File

@@ -1,7 +1,6 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/fragments/proxies/setting.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart'; import 'package:fl_clash/widgets/widgets.dart';
@@ -262,54 +261,10 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
_controller.dispose(); _controller.dispose();
} }
Widget _buildTabGroupView({
required List<Proxy> proxies,
required int columns,
required ProxyCardType proxyCardType,
}) {
final sortedProxies = globalState.appController.getSortProxies(
proxies,
);
_lastProxies = sortedProxies;
return DelayTestButtonContainer(
onClick: () async {
await _delayTest(
proxies,
);
},
child: Align(
alignment: Alignment.topCenter,
child: GridView.builder(
controller: _controller,
padding: const EdgeInsets.all(16),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
mainAxisExtent: getItemHeight(proxyCardType),
),
itemCount: sortedProxies.length,
itemBuilder: (_, index) {
final proxy = sortedProxies[index];
return currentProxyNameBuilder(
builder: (value) {
return ProxyCard(
type: proxyCardType,
key: ValueKey('$groupName.${proxy.name}'),
isSelected: value == proxy.name,
proxy: proxy,
groupName: groupName,
);
},
groupName: groupName,
);
},
),
),
);
}
scrollToSelected() { scrollToSelected() {
if (_controller.position.maxScrollExtent == 0) {
return;
}
_controller.animateTo( _controller.animateTo(
16 + 16 +
getScrollToSelectedOffset( getScrollToSelectedOffset(
@@ -329,19 +284,53 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
return ProxyGroupSelectorState( return ProxyGroupSelectorState(
proxyCardType: config.proxyCardType, proxyCardType: config.proxyCardType,
proxiesSortType: config.proxiesSortType, proxiesSortType: config.proxiesSortType,
columns: globalState.appController.columns, columns: other.getProxiesColumns(
appState.viewWidth,
config.proxiesLayout,
),
sortNum: appState.sortNum, sortNum: appState.sortNum,
proxies: group.all, proxies: group.all,
groupType: group.type,
); );
}, },
builder: (_, state, __) { builder: (_, state, __) {
final proxies = state.proxies; final proxies = state.proxies;
final columns = state.columns; final columns = state.columns;
final proxyCardType = state.proxyCardType; final proxyCardType = state.proxyCardType;
return _buildTabGroupView( final sortedProxies = globalState.appController.getSortProxies(
proxies: proxies, proxies,
columns: columns, );
proxyCardType: proxyCardType, _lastProxies = sortedProxies;
return DelayTestButtonContainer(
onClick: () async {
await _delayTest(
proxies,
);
},
child: Align(
alignment: Alignment.topCenter,
child: GridView.builder(
controller: _controller,
padding: const EdgeInsets.all(16),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
mainAxisExtent: getItemHeight(proxyCardType),
),
itemCount: sortedProxies.length,
itemBuilder: (_, index) {
final proxy = sortedProxies[index];
return ProxyCard(
groupType: state.groupType,
type: proxyCardType,
key: ValueKey('$groupName.${proxy.name}'),
proxy: proxy,
groupName: groupName,
);
},
),
),
); );
}, },
); );

View File

@@ -22,91 +22,11 @@ class GeoItem {
}); });
} }
@immutable class Resources extends StatelessWidget {
class FileInfo {
final String size;
final DateTime lastModified;
const FileInfo({
required this.size,
required this.lastModified,
});
}
class Resources extends StatefulWidget {
const Resources({super.key}); const Resources({super.key});
@override @override
State<Resources> createState() => _ResourcesState(); Widget build(BuildContext context) {
}
class _ResourcesState extends State<Resources> {
List<ExternalProvider> externalProviders = [];
List<GlobalObjectKey<_ProviderItemState>> providerItemKeys = [];
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_syncExternalProviders();
});
}
_syncExternalProviders() async {
externalProviders = await clashCore.getExternalProviders();
if (mounted) {
setState(() {});
}
}
_updateProviders() async {
final updateProviders = providerItemKeys.map<Future>(
(key) async => await key.currentState?.updateProvider(false),
);
await Future.wait(updateProviders);
_syncExternalProviders();
}
List<Widget> _buildExternalProviderSection() {
List<GlobalObjectKey<_ProviderItemState>> keys = [];
final res = generateInfoSection(
info: Info(
iconData: Icons.source,
label: appLocalizations.externalResources,
),
actions: [
IconButton.filledTonal(
onPressed: () {
_updateProviders();
},
padding: const EdgeInsets.all(4),
iconSize: 20,
icon: const Icon(
Icons.sync,
),
)
],
items: externalProviders.map(
(externalProvider) {
final key =
GlobalObjectKey<_ProviderItemState>(externalProvider.name);
keys.add(key);
return ProviderItem(
key: key,
provider: externalProvider,
onUpdated: () {
_syncExternalProviders();
},
);
},
),
);
providerItemKeys = keys;
return res;
}
List<Widget> _buildGeoDataSection() {
const geoItems = <GeoItem>[ const geoItems = <GeoItem>[
GeoItem( GeoItem(
label: "GeoIp", label: "GeoIp",
@@ -122,26 +42,19 @@ class _ResourcesState extends State<Resources> {
GeoItem(label: "ASN", fileName: asnFileName, key: "asn"), GeoItem(label: "ASN", fileName: asnFileName, key: "asn"),
]; ];
return generateInfoSection( return ListView.separated(
info: Info( itemBuilder: (_, index) {
iconData: Icons.storage, final geoItem = geoItems[index];
label: appLocalizations.geoData, return GeoDataListItem(
),
items: geoItems.map(
(geoItem) => GeoDataListItem(
geoItem: geoItem, geoItem: geoItem,
), );
), },
); separatorBuilder: (BuildContext context, int index) {
} return const Divider(
height: 0,
@override );
Widget build(BuildContext context) { },
return generateListView( itemCount: geoItems.length,
[
..._buildGeoDataSection(),
..._buildExternalProviderSection(),
],
); );
} }
} }
@@ -196,27 +109,11 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
final lastModified = await file.lastModified(); final lastModified = await file.lastModified();
final size = await file.length(); final size = await file.length();
return FileInfo( return FileInfo(
size: TrafficValue(value: size).show, size: size,
lastModified: lastModified, lastModified: lastModified,
); );
} }
// _uploadGeoFile(String fileName) async {
// final res = await picker.pickerGeoDataFile();
// if (res == null || res.bytes == null) return;
// final homePath = await appPath.getHomeDirPath();
// final file = File(join(homePath, fileName));
// await file.writeAsBytes(
// res.bytes!,
// flush: true,
// );
// setState(() {});
// }
String _buildFileInfoDesc(FileInfo fileInfo) {
return "${fileInfo.size} · ${fileInfo.lastModified.lastUpdateTimeDesc}";
}
Widget _buildSubtitle(String url) { Widget _buildSubtitle(String url) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -240,7 +137,7 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
), ),
) )
: Text( : Text(
_buildFileInfoDesc(snapshot.data!), snapshot.data!.desc,
), ),
), ),
); );
@@ -253,9 +150,6 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
const SizedBox( const SizedBox(
height: 8, height: 8,
), ),
const SizedBox(
height: 8,
),
Wrap( Wrap(
runSpacing: 6, runSpacing: 6,
spacing: 12, spacing: 12,
@@ -288,9 +182,9 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
updateGeoDateItem() async { updateGeoDateItem() async {
isUpdating.value = true; isUpdating.value = true;
try { try {
final message = await clashCore.updateExternalProvider( final message = await clashCore.updateGeoData(
providerName: geoItem.fileName, geoName: geoItem.fileName,
providerType: geoItem.label, geoType: geoItem.label,
); );
if (message.isNotEmpty) throw message; if (message.isNotEmpty) throw message;
} catch (e) { } catch (e) {
@@ -342,117 +236,6 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
} }
} }
class ProviderItem extends StatefulWidget {
final ExternalProvider provider;
final Function onUpdated;
const ProviderItem({
super.key,
required this.provider,
required this.onUpdated,
});
@override
State<ProviderItem> createState() => _ProviderItemState();
}
class _ProviderItemState extends State<ProviderItem> {
final isUpdating = ValueNotifier<bool>(false);
ExternalProvider get provider => widget.provider;
_handleUpdateProfile() async {
await globalState.safeRun<void>(updateProvider);
widget.onUpdated();
}
updateProvider([isSingle = true]) async {
if (provider.vehicleType != "HTTP") return;
isUpdating.value = true;
try {
final message = await clashCore.updateExternalProvider(
providerName: provider.name,
providerType: provider.type,
);
if (message.isNotEmpty) throw message;
} catch (e) {
isUpdating.value = false;
if (!isSingle) {
return e.toString();
} else {
rethrow;
}
}
isUpdating.value = false;
return null;
}
String _buildProviderDesc() {
return "${provider.type} (${provider.vehicleType}) · ${provider.updateAt.lastUpdateTimeDesc}";
}
@override
void dispose() {
super.dispose();
isUpdating.dispose();
}
Widget _buildSubtitle() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 4,
),
Text(
_buildProviderDesc(),
),
if (provider.vehicleType == "HTTP") ...[
const SizedBox(
height: 8,
),
CommonChip(
avatar: const Icon(Icons.sync),
label: appLocalizations.sync,
onPressed: () {
_handleUpdateProfile();
},
),
],
],
);
}
@override
Widget build(BuildContext context) {
return ListItem(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
title: Text(provider.name),
subtitle: _buildSubtitle(),
trailing: SizedBox(
height: 48,
width: 48,
child: ValueListenableBuilder(
valueListenable: isUpdating,
builder: (_, isUpdating, ___) {
return FadeBox(
child: isUpdating
? const Padding(
padding: EdgeInsets.all(8),
child: CircularProgressIndicator(),
)
: const SizedBox(),
);
},
),
),
);
}
}
class UpdateGeoUrlFormDialog extends StatefulWidget { class UpdateGeoUrlFormDialog extends StatefulWidget {
final String title; final String title;
final String url; final String url;
@@ -510,4 +293,4 @@ class _UpdateGeoUrlFormDialogState extends State<UpdateGeoUrlFormDialog> {
], ],
); );
} }
} }

View File

@@ -26,9 +26,7 @@ class ThemeFragment extends StatelessWidget {
final previewCard = Padding( final previewCard = Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: CommonCard( child: CommonCard(
onPressed: (){ onPressed: () {},
},
info: Info( info: Info(
label: appLocalizations.preview, label: appLocalizations.preview,
iconData: Icons.looks, iconData: Icons.looks,
@@ -87,7 +85,6 @@ class ThemeColorsBox extends StatefulWidget {
} }
class _ThemeColorsBoxState extends State<ThemeColorsBox> { class _ThemeColorsBoxState extends State<ThemeColorsBox> {
Widget _themeModeCheckBox({ Widget _themeModeCheckBox({
bool? isSelected, bool? isSelected,
required ThemeModeItem themeModeItem, required ThemeModeItem themeModeItem,
@@ -229,6 +226,27 @@ class _ThemeColorsBoxState extends State<ThemeColorsBox> {
), ),
), ),
), ),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Selector<Config, bool>(
selector: (_, config) => config.prueBlack,
builder: (_, value, ___) {
return ListItem.switchItem(
leading: Icon(
Icons.contrast,
color: context.colorScheme.primary,
),
title: Text(appLocalizations.prueBlackMode),
delegate: SwitchDelegate(
value: value,
onChanged: (value){
globalState.appController.config.prueBlack = value;
}
),
);
},
),
)
], ],
); );
} }

View File

@@ -66,6 +66,7 @@
"hours": "Hours", "hours": "Hours",
"days": "Days", "days": "Days",
"minutes": "Minutes", "minutes": "Minutes",
"seconds": "Seconds",
"ago": " Ago", "ago": " Ago",
"just": "Just", "just": "Just",
"qrcode": "QR code", "qrcode": "QR code",
@@ -130,12 +131,10 @@
"notSelectedTip": "The current proxy group cannot be selected.", "notSelectedTip": "The current proxy group cannot be selected.",
"tip": "tip", "tip": "tip",
"backupAndRecovery": "Backup and Recovery", "backupAndRecovery": "Backup and Recovery",
"backupAndRecoveryDesc": "Sync data by WebDAV", "backupAndRecoveryDesc": "Sync data via WebDAV or file",
"account": "Account", "account": "Account",
"backup": "Backup", "backup": "Backup",
"backupDesc": "Backup local data to WebDAV",
"recovery": "Recovery", "recovery": "Recovery",
"recoveryDesc": "Recovery data from WebDAV",
"recoveryProfiles": "Only recovery profiles", "recoveryProfiles": "Only recovery profiles",
"recoveryAll": "Recovery all data", "recoveryAll": "Recovery all data",
"recoverySuccess": "Recovery success", "recoverySuccess": "Recovery success",
@@ -216,5 +215,31 @@
"externalLink": "External link", "externalLink": "External link",
"otherContributors": "Other contributors", "otherContributors": "Other contributors",
"autoCloseConnections": "Auto lose connections", "autoCloseConnections": "Auto lose connections",
"autoCloseConnectionsDesc": "Auto close connections after change node" "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",
"keepAliveIntervalDesc": "Tcp keep alive interval",
"entries": " entries",
"local": "Local",
"remote": "Remote",
"remoteBackupDesc": "Backup local data to WebDAV",
"remoteRecoveryDesc": "Recovery data from WebDAV",
"localBackupDesc": "Backup local data to local",
"localRecoveryDesc": "Recovery data from file",
"mode": "Mode",
"time": "Time",
"source": "Source",
"allApps": "All apps",
"onlyOtherApps": "Only third-party apps",
"action": "Action",
"intelligentSelected": "Intelligent selection",
"clipboardImport": "Clipboard import",
"clipboardExport": "Export clipboard",
"layout": "Layout",
"tight": "Tight",
"standard": "Standard",
"loose": "Loose",
"profilesSort": "Profiles sort"
} }

View File

@@ -66,6 +66,7 @@
"hours": "小时", "hours": "小时",
"days": "天", "days": "天",
"minutes": "分钟", "minutes": "分钟",
"seconds": "秒",
"ago": "前", "ago": "前",
"just": "刚刚", "just": "刚刚",
"qrcode": "二维码", "qrcode": "二维码",
@@ -130,12 +131,10 @@
"notSelectedTip": "当前代理组无法选中", "notSelectedTip": "当前代理组无法选中",
"tip": "提示", "tip": "提示",
"backupAndRecovery": "备份与恢复", "backupAndRecovery": "备份与恢复",
"backupAndRecoveryDesc": "通过WebDAV同步数据", "backupAndRecoveryDesc": "通过WebDAV或者文件同步数据",
"account": "账号", "account": "账号",
"backup": "备份", "backup": "备份",
"backupDesc": "备份数据到WebDAV",
"recovery": "恢复", "recovery": "恢复",
"recoveryDesc": "从WebDAV恢复数据",
"recoveryProfiles": "仅恢复配置文件", "recoveryProfiles": "仅恢复配置文件",
"recoveryAll": "恢复所有数据", "recoveryAll": "恢复所有数据",
"recoverySuccess": "恢复成功", "recoverySuccess": "恢复成功",
@@ -216,5 +215,31 @@
"externalLink": "外部链接", "externalLink": "外部链接",
"otherContributors": "其他贡献者", "otherContributors": "其他贡献者",
"autoCloseConnections": "自动关闭连接", "autoCloseConnections": "自动关闭连接",
"autoCloseConnectionsDesc": "切换节点后自动关闭连接" "autoCloseConnectionsDesc": "切换节点后自动关闭连接",
"onlyStatisticsProxy": "仅统计代理",
"onlyStatisticsProxyDesc": "开启后,将只统计代理流量",
"deleteProfileTip": "确定要删除当前配置吗?",
"prueBlackMode": "纯黑模式",
"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": "配置排序"
} }

View File

@@ -33,6 +33,7 @@ class MessageLookup extends MessageLookupByLibrary {
"account": MessageLookupByLibrary.simpleMessage("Account"), "account": MessageLookupByLibrary.simpleMessage("Account"),
"accountTip": "accountTip":
MessageLookupByLibrary.simpleMessage("Account cannot be empty"), MessageLookupByLibrary.simpleMessage("Account cannot be empty"),
"action": MessageLookupByLibrary.simpleMessage("Action"),
"add": MessageLookupByLibrary.simpleMessage("Add"), "add": MessageLookupByLibrary.simpleMessage("Add"),
"address": MessageLookupByLibrary.simpleMessage("Address"), "address": MessageLookupByLibrary.simpleMessage("Address"),
"addressHelp": "addressHelp":
@@ -40,6 +41,7 @@ class MessageLookup extends MessageLookupByLibrary {
"addressTip": MessageLookupByLibrary.simpleMessage( "addressTip": MessageLookupByLibrary.simpleMessage(
"Please enter a valid WebDAV address"), "Please enter a valid WebDAV address"),
"ago": MessageLookupByLibrary.simpleMessage(" Ago"), "ago": MessageLookupByLibrary.simpleMessage(" Ago"),
"allApps": MessageLookupByLibrary.simpleMessage("All apps"),
"allowBypass": MessageLookupByLibrary.simpleMessage( "allowBypass": MessageLookupByLibrary.simpleMessage(
"Allow applications to bypass VPN"), "Allow applications to bypass VPN"),
"allowBypassDesc": MessageLookupByLibrary.simpleMessage( "allowBypassDesc": MessageLookupByLibrary.simpleMessage(
@@ -74,10 +76,8 @@ class MessageLookup extends MessageLookupByLibrary {
"backup": MessageLookupByLibrary.simpleMessage("Backup"), "backup": MessageLookupByLibrary.simpleMessage("Backup"),
"backupAndRecovery": "backupAndRecovery":
MessageLookupByLibrary.simpleMessage("Backup and Recovery"), MessageLookupByLibrary.simpleMessage("Backup and Recovery"),
"backupAndRecoveryDesc": "backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage("Sync data by WebDAV"), "Sync data via WebDAV or file"),
"backupDesc":
MessageLookupByLibrary.simpleMessage("Backup local data to WebDAV"),
"backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"), "backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"),
"bind": MessageLookupByLibrary.simpleMessage("Bind"), "bind": MessageLookupByLibrary.simpleMessage("Bind"),
"blacklistMode": MessageLookupByLibrary.simpleMessage("Blacklist mode"), "blacklistMode": MessageLookupByLibrary.simpleMessage("Blacklist mode"),
@@ -91,6 +91,10 @@ class MessageLookup extends MessageLookupByLibrary {
"checkUpdateError": MessageLookupByLibrary.simpleMessage( "checkUpdateError": MessageLookupByLibrary.simpleMessage(
"The current application is already the latest version"), "The current application is already the latest version"),
"checking": MessageLookupByLibrary.simpleMessage("Checking..."), "checking": MessageLookupByLibrary.simpleMessage("Checking..."),
"clipboardExport":
MessageLookupByLibrary.simpleMessage("Export clipboard"),
"clipboardImport":
MessageLookupByLibrary.simpleMessage("Clipboard import"),
"columns": MessageLookupByLibrary.simpleMessage("Columns"), "columns": MessageLookupByLibrary.simpleMessage("Columns"),
"compatible": "compatible":
MessageLookupByLibrary.simpleMessage("Compatibility mode"), MessageLookupByLibrary.simpleMessage("Compatibility mode"),
@@ -115,6 +119,8 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("Delay"), "delay": MessageLookupByLibrary.simpleMessage("Delay"),
"delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"), "delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"),
"delete": MessageLookupByLibrary.simpleMessage("Delete"), "delete": MessageLookupByLibrary.simpleMessage("Delete"),
"deleteProfileTip": MessageLookupByLibrary.simpleMessage(
"Sure you want to delete the current profile?"),
"desc": MessageLookupByLibrary.simpleMessage( "desc": MessageLookupByLibrary.simpleMessage(
"A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free."), "A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free."),
"direct": MessageLookupByLibrary.simpleMessage("Direct"), "direct": MessageLookupByLibrary.simpleMessage("Direct"),
@@ -127,6 +133,7 @@ class MessageLookup extends MessageLookupByLibrary {
"download": MessageLookupByLibrary.simpleMessage("Download"), "download": MessageLookupByLibrary.simpleMessage("Download"),
"edit": MessageLookupByLibrary.simpleMessage("Edit"), "edit": MessageLookupByLibrary.simpleMessage("Edit"),
"en": MessageLookupByLibrary.simpleMessage("English"), "en": MessageLookupByLibrary.simpleMessage("English"),
"entries": MessageLookupByLibrary.simpleMessage(" entries"),
"exclude": "exclude":
MessageLookupByLibrary.simpleMessage("Hidden from recent tasks"), MessageLookupByLibrary.simpleMessage("Hidden from recent tasks"),
"excludeDesc": MessageLookupByLibrary.simpleMessage( "excludeDesc": MessageLookupByLibrary.simpleMessage(
@@ -166,25 +173,37 @@ class MessageLookup extends MessageLookupByLibrary {
"infiniteTime": "infiniteTime":
MessageLookupByLibrary.simpleMessage("Long term effective"), MessageLookupByLibrary.simpleMessage("Long term effective"),
"init": MessageLookupByLibrary.simpleMessage("Init"), "init": MessageLookupByLibrary.simpleMessage("Init"),
"intelligentSelected":
MessageLookupByLibrary.simpleMessage("Intelligent selection"),
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"), "intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage( "ipv6Desc": MessageLookupByLibrary.simpleMessage(
"When turned on it will be able to receive IPv6 traffic"), "When turned on it will be able to receive IPv6 traffic"),
"just": MessageLookupByLibrary.simpleMessage("Just"), "just": MessageLookupByLibrary.simpleMessage("Just"),
"keepAliveIntervalDesc":
MessageLookupByLibrary.simpleMessage("Tcp keep alive interval"),
"language": MessageLookupByLibrary.simpleMessage("Language"), "language": MessageLookupByLibrary.simpleMessage("Language"),
"layout": MessageLookupByLibrary.simpleMessage("Layout"),
"light": MessageLookupByLibrary.simpleMessage("Light"), "light": MessageLookupByLibrary.simpleMessage("Light"),
"list": MessageLookupByLibrary.simpleMessage("List"), "list": MessageLookupByLibrary.simpleMessage("List"),
"local": MessageLookupByLibrary.simpleMessage("Local"),
"localBackupDesc":
MessageLookupByLibrary.simpleMessage("Backup local data to local"),
"localRecoveryDesc":
MessageLookupByLibrary.simpleMessage("Recovery data from file"),
"logLevel": MessageLookupByLibrary.simpleMessage("LogLevel"), "logLevel": MessageLookupByLibrary.simpleMessage("LogLevel"),
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"), "logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
"logcatDesc": MessageLookupByLibrary.simpleMessage( "logcatDesc": MessageLookupByLibrary.simpleMessage(
"Disabling will hide the log entry"), "Disabling will hide the log entry"),
"logs": MessageLookupByLibrary.simpleMessage("Logs"), "logs": MessageLookupByLibrary.simpleMessage("Logs"),
"logsDesc": MessageLookupByLibrary.simpleMessage("Log capture records"), "logsDesc": MessageLookupByLibrary.simpleMessage("Log capture records"),
"loose": MessageLookupByLibrary.simpleMessage("Loose"),
"min": MessageLookupByLibrary.simpleMessage("Min"), "min": MessageLookupByLibrary.simpleMessage("Min"),
"minimizeOnExit": "minimizeOnExit":
MessageLookupByLibrary.simpleMessage("Minimize on exit"), MessageLookupByLibrary.simpleMessage("Minimize on exit"),
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage( "minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
"Modify the default system exit event"), "Modify the default system exit event"),
"minutes": MessageLookupByLibrary.simpleMessage("Minutes"), "minutes": MessageLookupByLibrary.simpleMessage("Minutes"),
"mode": MessageLookupByLibrary.simpleMessage("Mode"),
"months": MessageLookupByLibrary.simpleMessage("Months"), "months": MessageLookupByLibrary.simpleMessage("Months"),
"more": MessageLookupByLibrary.simpleMessage("More"), "more": MessageLookupByLibrary.simpleMessage("More"),
"name": MessageLookupByLibrary.simpleMessage("Name"), "name": MessageLookupByLibrary.simpleMessage("Name"),
@@ -208,6 +227,12 @@ class MessageLookup extends MessageLookupByLibrary {
"No profile, Please add a profile"), "No profile, Please add a profile"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"), "nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"),
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"), "oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
"onlyOtherApps":
MessageLookupByLibrary.simpleMessage("Only third-party apps"),
"onlyStatisticsProxy":
MessageLookupByLibrary.simpleMessage("Only statistics proxy"),
"onlyStatisticsProxyDesc": MessageLookupByLibrary.simpleMessage(
"When turned on, only statistics proxy traffic"),
"other": MessageLookupByLibrary.simpleMessage("Other"), "other": MessageLookupByLibrary.simpleMessage("Other"),
"otherContributors": "otherContributors":
MessageLookupByLibrary.simpleMessage("Other contributors"), MessageLookupByLibrary.simpleMessage("Other contributors"),
@@ -243,6 +268,7 @@ class MessageLookup extends MessageLookupByLibrary {
"profileUrlNullValidationDesc": MessageLookupByLibrary.simpleMessage( "profileUrlNullValidationDesc": MessageLookupByLibrary.simpleMessage(
"Please input the profile URL"), "Please input the profile URL"),
"profiles": MessageLookupByLibrary.simpleMessage("Profiles"), "profiles": MessageLookupByLibrary.simpleMessage("Profiles"),
"profilesSort": MessageLookupByLibrary.simpleMessage("Profiles sort"),
"project": MessageLookupByLibrary.simpleMessage("Project"), "project": MessageLookupByLibrary.simpleMessage("Project"),
"proxies": MessageLookupByLibrary.simpleMessage("Proxies"), "proxies": MessageLookupByLibrary.simpleMessage("Proxies"),
"proxiesSetting": "proxiesSetting":
@@ -251,18 +277,23 @@ class MessageLookup extends MessageLookupByLibrary {
"proxyPort": MessageLookupByLibrary.simpleMessage("ProxyPort"), "proxyPort": MessageLookupByLibrary.simpleMessage("ProxyPort"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage( "proxyPortDesc": MessageLookupByLibrary.simpleMessage(
"Set the Clash listening port"), "Set the Clash listening port"),
"prueBlackMode":
MessageLookupByLibrary.simpleMessage("Prue black mode"),
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"), "qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage( "qrcodeDesc": MessageLookupByLibrary.simpleMessage(
"Scan QR code to obtain profile"), "Scan QR code to obtain profile"),
"recovery": MessageLookupByLibrary.simpleMessage("Recovery"), "recovery": MessageLookupByLibrary.simpleMessage("Recovery"),
"recoveryAll": "recoveryAll":
MessageLookupByLibrary.simpleMessage("Recovery all data"), MessageLookupByLibrary.simpleMessage("Recovery all data"),
"recoveryDesc":
MessageLookupByLibrary.simpleMessage("Recovery data from WebDAV"),
"recoveryProfiles": "recoveryProfiles":
MessageLookupByLibrary.simpleMessage("Only recovery profiles"), MessageLookupByLibrary.simpleMessage("Only recovery profiles"),
"recoverySuccess": "recoverySuccess":
MessageLookupByLibrary.simpleMessage("Recovery success"), MessageLookupByLibrary.simpleMessage("Recovery success"),
"remote": MessageLookupByLibrary.simpleMessage("Remote"),
"remoteBackupDesc":
MessageLookupByLibrary.simpleMessage("Backup local data to WebDAV"),
"remoteRecoveryDesc":
MessageLookupByLibrary.simpleMessage("Recovery data from WebDAV"),
"requests": MessageLookupByLibrary.simpleMessage("Requests"), "requests": MessageLookupByLibrary.simpleMessage("Requests"),
"requestsDesc": MessageLookupByLibrary.simpleMessage( "requestsDesc": MessageLookupByLibrary.simpleMessage(
"View recently request records"), "View recently request records"),
@@ -272,6 +303,7 @@ class MessageLookup extends MessageLookupByLibrary {
"rule": MessageLookupByLibrary.simpleMessage("Rule"), "rule": MessageLookupByLibrary.simpleMessage("Rule"),
"save": MessageLookupByLibrary.simpleMessage("Save"), "save": MessageLookupByLibrary.simpleMessage("Save"),
"search": MessageLookupByLibrary.simpleMessage("Search"), "search": MessageLookupByLibrary.simpleMessage("Search"),
"seconds": MessageLookupByLibrary.simpleMessage("Seconds"),
"selectAll": MessageLookupByLibrary.simpleMessage("Select all"), "selectAll": MessageLookupByLibrary.simpleMessage("Select all"),
"selected": MessageLookupByLibrary.simpleMessage("Selected"), "selected": MessageLookupByLibrary.simpleMessage("Selected"),
"settings": MessageLookupByLibrary.simpleMessage("Settings"), "settings": MessageLookupByLibrary.simpleMessage("Settings"),
@@ -282,6 +314,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Start in the background"), MessageLookupByLibrary.simpleMessage("Start in the background"),
"size": MessageLookupByLibrary.simpleMessage("Size"), "size": MessageLookupByLibrary.simpleMessage("Size"),
"sort": MessageLookupByLibrary.simpleMessage("Sort"), "sort": MessageLookupByLibrary.simpleMessage("Sort"),
"source": MessageLookupByLibrary.simpleMessage("Source"),
"standard": MessageLookupByLibrary.simpleMessage("Standard"),
"startVpn": MessageLookupByLibrary.simpleMessage("Staring VPN..."), "startVpn": MessageLookupByLibrary.simpleMessage("Staring VPN..."),
"stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."), "stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."),
"style": MessageLookupByLibrary.simpleMessage("Style"), "style": MessageLookupByLibrary.simpleMessage("Style"),
@@ -304,6 +338,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Set dark mode,adjust the color"), "Set dark mode,adjust the color"),
"themeMode": MessageLookupByLibrary.simpleMessage("Theme mode"), "themeMode": MessageLookupByLibrary.simpleMessage("Theme mode"),
"threeColumns": MessageLookupByLibrary.simpleMessage("Three columns"), "threeColumns": MessageLookupByLibrary.simpleMessage("Three columns"),
"tight": MessageLookupByLibrary.simpleMessage("Tight"),
"time": MessageLookupByLibrary.simpleMessage("Time"),
"tip": MessageLookupByLibrary.simpleMessage("tip"), "tip": MessageLookupByLibrary.simpleMessage("tip"),
"tools": MessageLookupByLibrary.simpleMessage("Tools"), "tools": MessageLookupByLibrary.simpleMessage("Tools"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"), "trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"),

View File

@@ -31,11 +31,13 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("选中应用将会被排除在VPN之外"), MessageLookupByLibrary.simpleMessage("选中应用将会被排除在VPN之外"),
"account": MessageLookupByLibrary.simpleMessage("账号"), "account": MessageLookupByLibrary.simpleMessage("账号"),
"accountTip": MessageLookupByLibrary.simpleMessage("账号不能为空"), "accountTip": MessageLookupByLibrary.simpleMessage("账号不能为空"),
"action": MessageLookupByLibrary.simpleMessage("操作"),
"add": MessageLookupByLibrary.simpleMessage("添加"), "add": MessageLookupByLibrary.simpleMessage("添加"),
"address": MessageLookupByLibrary.simpleMessage("地址"), "address": MessageLookupByLibrary.simpleMessage("地址"),
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"), "addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"), "addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
"ago": MessageLookupByLibrary.simpleMessage(""), "ago": MessageLookupByLibrary.simpleMessage(""),
"allApps": MessageLookupByLibrary.simpleMessage("所有应用"),
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过VPN"), "allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过VPN"),
"allowBypassDesc": "allowBypassDesc":
MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"), MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"),
@@ -62,8 +64,7 @@ class MessageLookup extends MessageLookupByLibrary {
"backup": MessageLookupByLibrary.simpleMessage("备份"), "backup": MessageLookupByLibrary.simpleMessage("备份"),
"backupAndRecovery": MessageLookupByLibrary.simpleMessage("备份与恢复"), "backupAndRecovery": MessageLookupByLibrary.simpleMessage("备份与恢复"),
"backupAndRecoveryDesc": "backupAndRecoveryDesc":
MessageLookupByLibrary.simpleMessage("通过WebDAV同步数据"), MessageLookupByLibrary.simpleMessage("通过WebDAV或者文件同步数据"),
"backupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
"backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"), "backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"),
"bind": MessageLookupByLibrary.simpleMessage("绑定"), "bind": MessageLookupByLibrary.simpleMessage("绑定"),
"blacklistMode": MessageLookupByLibrary.simpleMessage("黑名单模式"), "blacklistMode": MessageLookupByLibrary.simpleMessage("黑名单模式"),
@@ -74,6 +75,8 @@ class MessageLookup extends MessageLookupByLibrary {
"checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"), "checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"),
"checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"), "checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"),
"checking": MessageLookupByLibrary.simpleMessage("检测中..."), "checking": MessageLookupByLibrary.simpleMessage("检测中..."),
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
"columns": MessageLookupByLibrary.simpleMessage("列数"), "columns": MessageLookupByLibrary.simpleMessage("列数"),
"compatible": MessageLookupByLibrary.simpleMessage("兼容模式"), "compatible": MessageLookupByLibrary.simpleMessage("兼容模式"),
"compatibleDesc": "compatibleDesc":
@@ -96,6 +99,7 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("延迟"), "delay": MessageLookupByLibrary.simpleMessage("延迟"),
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"), "delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
"delete": MessageLookupByLibrary.simpleMessage("删除"), "delete": MessageLookupByLibrary.simpleMessage("删除"),
"deleteProfileTip": MessageLookupByLibrary.simpleMessage("确定要删除当前配置吗?"),
"desc": MessageLookupByLibrary.simpleMessage( "desc": MessageLookupByLibrary.simpleMessage(
"基于ClashMeta的多平台代理客户端简单易用开源无广告。"), "基于ClashMeta的多平台代理客户端简单易用开源无广告。"),
"direct": MessageLookupByLibrary.simpleMessage("直连"), "direct": MessageLookupByLibrary.simpleMessage("直连"),
@@ -105,6 +109,7 @@ class MessageLookup extends MessageLookupByLibrary {
"download": MessageLookupByLibrary.simpleMessage("下载"), "download": MessageLookupByLibrary.simpleMessage("下载"),
"edit": MessageLookupByLibrary.simpleMessage("编辑"), "edit": MessageLookupByLibrary.simpleMessage("编辑"),
"en": MessageLookupByLibrary.simpleMessage("英语"), "en": MessageLookupByLibrary.simpleMessage("英语"),
"entries": MessageLookupByLibrary.simpleMessage("个条目"),
"exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"), "exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"),
"excludeDesc": "excludeDesc":
MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"), MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"),
@@ -135,22 +140,31 @@ class MessageLookup extends MessageLookupByLibrary {
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"), "importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"), "infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
"init": MessageLookupByLibrary.simpleMessage("初始化"), "init": MessageLookupByLibrary.simpleMessage("初始化"),
"intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"),
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"), "intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"), "ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
"just": MessageLookupByLibrary.simpleMessage("刚刚"), "just": MessageLookupByLibrary.simpleMessage("刚刚"),
"keepAliveIntervalDesc":
MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
"language": MessageLookupByLibrary.simpleMessage("语言"), "language": MessageLookupByLibrary.simpleMessage("语言"),
"layout": MessageLookupByLibrary.simpleMessage("布局"),
"light": MessageLookupByLibrary.simpleMessage("浅色"), "light": MessageLookupByLibrary.simpleMessage("浅色"),
"list": MessageLookupByLibrary.simpleMessage("列表"), "list": MessageLookupByLibrary.simpleMessage("列表"),
"local": MessageLookupByLibrary.simpleMessage("本地"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),
"logLevel": MessageLookupByLibrary.simpleMessage("日志等级"), "logLevel": MessageLookupByLibrary.simpleMessage("日志等级"),
"logcat": MessageLookupByLibrary.simpleMessage("日志捕获"), "logcat": MessageLookupByLibrary.simpleMessage("日志捕获"),
"logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"), "logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"),
"logs": MessageLookupByLibrary.simpleMessage("日志"), "logs": MessageLookupByLibrary.simpleMessage("日志"),
"logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"), "logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"),
"loose": MessageLookupByLibrary.simpleMessage("紧凑"),
"min": MessageLookupByLibrary.simpleMessage("最小"), "min": MessageLookupByLibrary.simpleMessage("最小"),
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"), "minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
"minimizeOnExitDesc": "minimizeOnExitDesc":
MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"), MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
"minutes": MessageLookupByLibrary.simpleMessage("分钟"), "minutes": MessageLookupByLibrary.simpleMessage("分钟"),
"mode": MessageLookupByLibrary.simpleMessage("模式"),
"months": MessageLookupByLibrary.simpleMessage(""), "months": MessageLookupByLibrary.simpleMessage(""),
"more": MessageLookupByLibrary.simpleMessage("更多"), "more": MessageLookupByLibrary.simpleMessage("更多"),
"name": MessageLookupByLibrary.simpleMessage("名称"), "name": MessageLookupByLibrary.simpleMessage("名称"),
@@ -170,6 +184,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"), MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"), "nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"), "oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("仅第三方应用"),
"onlyStatisticsProxy": MessageLookupByLibrary.simpleMessage("仅统计代理"),
"onlyStatisticsProxyDesc":
MessageLookupByLibrary.simpleMessage("开启后,将只统计代理流量"),
"other": MessageLookupByLibrary.simpleMessage("其他"), "other": MessageLookupByLibrary.simpleMessage("其他"),
"otherContributors": MessageLookupByLibrary.simpleMessage("其他贡献者"), "otherContributors": MessageLookupByLibrary.simpleMessage("其他贡献者"),
"outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"), "outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"),
@@ -198,19 +216,24 @@ class MessageLookup extends MessageLookupByLibrary {
"profileUrlNullValidationDesc": "profileUrlNullValidationDesc":
MessageLookupByLibrary.simpleMessage("请输入配置URL"), MessageLookupByLibrary.simpleMessage("请输入配置URL"),
"profiles": MessageLookupByLibrary.simpleMessage("配置"), "profiles": MessageLookupByLibrary.simpleMessage("配置"),
"profilesSort": MessageLookupByLibrary.simpleMessage("配置排序"),
"project": MessageLookupByLibrary.simpleMessage("项目"), "project": MessageLookupByLibrary.simpleMessage("项目"),
"proxies": MessageLookupByLibrary.simpleMessage("代理"), "proxies": MessageLookupByLibrary.simpleMessage("代理"),
"proxiesSetting": MessageLookupByLibrary.simpleMessage("代理设置"), "proxiesSetting": MessageLookupByLibrary.simpleMessage("代理设置"),
"proxyGroup": MessageLookupByLibrary.simpleMessage("代理组"), "proxyGroup": MessageLookupByLibrary.simpleMessage("代理组"),
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"), "proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"), "proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
"prueBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"), "qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"), "qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
"recovery": MessageLookupByLibrary.simpleMessage("恢复"), "recovery": MessageLookupByLibrary.simpleMessage("恢复"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"), "recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
"recoveryDesc": MessageLookupByLibrary.simpleMessage("从WebDAV恢复数据"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"), "recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"), "recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
"remote": MessageLookupByLibrary.simpleMessage("远程"),
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
"remoteRecoveryDesc":
MessageLookupByLibrary.simpleMessage("通过WebDAV恢复数据"),
"requests": MessageLookupByLibrary.simpleMessage("请求"), "requests": MessageLookupByLibrary.simpleMessage("请求"),
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"), "requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"),
"resources": MessageLookupByLibrary.simpleMessage("资源"), "resources": MessageLookupByLibrary.simpleMessage("资源"),
@@ -218,6 +241,7 @@ class MessageLookup extends MessageLookupByLibrary {
"rule": MessageLookupByLibrary.simpleMessage("规则"), "rule": MessageLookupByLibrary.simpleMessage("规则"),
"save": MessageLookupByLibrary.simpleMessage("保存"), "save": MessageLookupByLibrary.simpleMessage("保存"),
"search": MessageLookupByLibrary.simpleMessage("搜索"), "search": MessageLookupByLibrary.simpleMessage("搜索"),
"seconds": MessageLookupByLibrary.simpleMessage(""),
"selectAll": MessageLookupByLibrary.simpleMessage("全选"), "selectAll": MessageLookupByLibrary.simpleMessage("全选"),
"selected": MessageLookupByLibrary.simpleMessage("已选择"), "selected": MessageLookupByLibrary.simpleMessage("已选择"),
"settings": MessageLookupByLibrary.simpleMessage("设置"), "settings": MessageLookupByLibrary.simpleMessage("设置"),
@@ -227,6 +251,8 @@ class MessageLookup extends MessageLookupByLibrary {
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("后台启动"), "silentLaunchDesc": MessageLookupByLibrary.simpleMessage("后台启动"),
"size": MessageLookupByLibrary.simpleMessage("尺寸"), "size": MessageLookupByLibrary.simpleMessage("尺寸"),
"sort": MessageLookupByLibrary.simpleMessage("排序"), "sort": MessageLookupByLibrary.simpleMessage("排序"),
"source": MessageLookupByLibrary.simpleMessage("来源"),
"standard": MessageLookupByLibrary.simpleMessage("标准"),
"startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."), "startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."),
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."), "stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
"style": MessageLookupByLibrary.simpleMessage("风格"), "style": MessageLookupByLibrary.simpleMessage("风格"),
@@ -247,6 +273,8 @@ class MessageLookup extends MessageLookupByLibrary {
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"), "themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
"themeMode": MessageLookupByLibrary.simpleMessage("主题模式"), "themeMode": MessageLookupByLibrary.simpleMessage("主题模式"),
"threeColumns": MessageLookupByLibrary.simpleMessage("三列"), "threeColumns": MessageLookupByLibrary.simpleMessage("三列"),
"tight": MessageLookupByLibrary.simpleMessage("宽松"),
"time": MessageLookupByLibrary.simpleMessage("时间"),
"tip": MessageLookupByLibrary.simpleMessage("提示"), "tip": MessageLookupByLibrary.simpleMessage("提示"),
"tools": MessageLookupByLibrary.simpleMessage("工具"), "tools": MessageLookupByLibrary.simpleMessage("工具"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"), "trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),

View File

@@ -720,6 +720,16 @@ class AppLocalizations {
); );
} }
/// `Seconds`
String get seconds {
return Intl.message(
'Seconds',
name: 'seconds',
desc: '',
args: [],
);
}
/// ` Ago` /// ` Ago`
String get ago { String get ago {
return Intl.message( return Intl.message(
@@ -1360,10 +1370,10 @@ class AppLocalizations {
); );
} }
/// `Sync data by WebDAV` /// `Sync data via WebDAV or file`
String get backupAndRecoveryDesc { String get backupAndRecoveryDesc {
return Intl.message( return Intl.message(
'Sync data by WebDAV', 'Sync data via WebDAV or file',
name: 'backupAndRecoveryDesc', name: 'backupAndRecoveryDesc',
desc: '', desc: '',
args: [], args: [],
@@ -1390,16 +1400,6 @@ class AppLocalizations {
); );
} }
/// `Backup local data to WebDAV`
String get backupDesc {
return Intl.message(
'Backup local data to WebDAV',
name: 'backupDesc',
desc: '',
args: [],
);
}
/// `Recovery` /// `Recovery`
String get recovery { String get recovery {
return Intl.message( return Intl.message(
@@ -1410,16 +1410,6 @@ class AppLocalizations {
); );
} }
/// `Recovery data from WebDAV`
String get recoveryDesc {
return Intl.message(
'Recovery data from WebDAV',
name: 'recoveryDesc',
desc: '',
args: [],
);
}
/// `Only recovery profiles` /// `Only recovery profiles`
String get recoveryProfiles { String get recoveryProfiles {
return Intl.message( return Intl.message(
@@ -2229,6 +2219,266 @@ class AppLocalizations {
args: [], args: [],
); );
} }
/// `Only statistics proxy`
String get onlyStatisticsProxy {
return Intl.message(
'Only statistics proxy',
name: 'onlyStatisticsProxy',
desc: '',
args: [],
);
}
/// `When turned on, only statistics proxy traffic`
String get onlyStatisticsProxyDesc {
return Intl.message(
'When turned on, only statistics proxy traffic',
name: 'onlyStatisticsProxyDesc',
desc: '',
args: [],
);
}
/// `Sure you want to delete the current profile?`
String get deleteProfileTip {
return Intl.message(
'Sure you want to delete the current profile?',
name: 'deleteProfileTip',
desc: '',
args: [],
);
}
/// `Prue black mode`
String get prueBlackMode {
return Intl.message(
'Prue black mode',
name: 'prueBlackMode',
desc: '',
args: [],
);
}
/// `Tcp keep alive interval`
String get keepAliveIntervalDesc {
return Intl.message(
'Tcp keep alive interval',
name: 'keepAliveIntervalDesc',
desc: '',
args: [],
);
}
/// ` entries`
String get entries {
return Intl.message(
' entries',
name: 'entries',
desc: '',
args: [],
);
}
/// `Local`
String get local {
return Intl.message(
'Local',
name: 'local',
desc: '',
args: [],
);
}
/// `Remote`
String get remote {
return Intl.message(
'Remote',
name: 'remote',
desc: '',
args: [],
);
}
/// `Backup local data to WebDAV`
String get remoteBackupDesc {
return Intl.message(
'Backup local data to WebDAV',
name: 'remoteBackupDesc',
desc: '',
args: [],
);
}
/// `Recovery data from WebDAV`
String get remoteRecoveryDesc {
return Intl.message(
'Recovery data from WebDAV',
name: 'remoteRecoveryDesc',
desc: '',
args: [],
);
}
/// `Backup local data to local`
String get localBackupDesc {
return Intl.message(
'Backup local data to local',
name: 'localBackupDesc',
desc: '',
args: [],
);
}
/// `Recovery data from file`
String get localRecoveryDesc {
return Intl.message(
'Recovery data from file',
name: 'localRecoveryDesc',
desc: '',
args: [],
);
}
/// `Mode`
String get mode {
return Intl.message(
'Mode',
name: 'mode',
desc: '',
args: [],
);
}
/// `Time`
String get time {
return Intl.message(
'Time',
name: 'time',
desc: '',
args: [],
);
}
/// `Source`
String get source {
return Intl.message(
'Source',
name: 'source',
desc: '',
args: [],
);
}
/// `All apps`
String get allApps {
return Intl.message(
'All apps',
name: 'allApps',
desc: '',
args: [],
);
}
/// `Only third-party apps`
String get onlyOtherApps {
return Intl.message(
'Only third-party apps',
name: 'onlyOtherApps',
desc: '',
args: [],
);
}
/// `Action`
String get action {
return Intl.message(
'Action',
name: 'action',
desc: '',
args: [],
);
}
/// `Intelligent selection`
String get intelligentSelected {
return Intl.message(
'Intelligent selection',
name: 'intelligentSelected',
desc: '',
args: [],
);
}
/// `Clipboard import`
String get clipboardImport {
return Intl.message(
'Clipboard import',
name: 'clipboardImport',
desc: '',
args: [],
);
}
/// `Export clipboard`
String get clipboardExport {
return Intl.message(
'Export clipboard',
name: 'clipboardExport',
desc: '',
args: [],
);
}
/// `Layout`
String get layout {
return Intl.message(
'Layout',
name: 'layout',
desc: '',
args: [],
);
}
/// `Tight`
String get tight {
return Intl.message(
'Tight',
name: 'tight',
desc: '',
args: [],
);
}
/// `Standard`
String get standard {
return Intl.message(
'Standard',
name: 'standard',
desc: '',
args: [],
);
}
/// `Loose`
String get loose {
return Intl.message(
'Loose',
name: 'loose',
desc: '',
args: [],
);
}
/// `Profiles sort`
String get profilesSort {
return Intl.message(
'Profiles sort',
name: 'profilesSort',
desc: '',
args: [],
);
}
} }
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> { class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -130,13 +130,13 @@ class ServiceMessageHandler with ServiceMessageListener {
final Function(Fd fd) _onProtect; final Function(Fd fd) _onProtect;
final Function(Process process) _onProcess; final Function(Process process) _onProcess;
final Function(String runTime) _onStarted; final Function(String runTime) _onStarted;
final Function(String groupName) _onLoaded; final Function(String providerName) _onLoaded;
const ServiceMessageHandler({ const ServiceMessageHandler({
required Function(Fd fd) onProtect, required Function(Fd fd) onProtect,
required Function(Process process) onProcess, required Function(Process process) onProcess,
required Function(String runTime) onStarted, required Function(String runTime) onStarted,
required Function(String groupName) onLoaded, required Function(String providerName) onLoaded,
}) : _onProtect = onProtect, }) : _onProtect = onProtect,
_onProcess = onProcess, _onProcess = onProcess,
_onStarted = onStarted, _onStarted = onStarted,
@@ -158,8 +158,8 @@ class ServiceMessageHandler with ServiceMessageListener {
} }
@override @override
onLoaded(String groupName) { onLoaded(String providerName) {
_onLoaded(groupName); _onLoaded(providerName);
} }
} }

View File

@@ -8,6 +8,7 @@ import 'connection.dart';
import 'ffi.dart'; import 'ffi.dart';
import 'log.dart'; import 'log.dart';
import 'navigation.dart'; import 'navigation.dart';
import 'package.dart';
import 'profile.dart'; import 'profile.dart';
import 'proxy.dart'; import 'proxy.dart';
import 'system_color_scheme.dart'; import 'system_color_scheme.dart';
@@ -35,6 +36,8 @@ class AppState with ChangeNotifier {
double _viewWidth; double _viewWidth;
List<Connection> _requests; List<Connection> _requests;
num _checkIpNum; num _checkIpNum;
List<ExternalProvider> _providers;
List<Package> _packages;
AppState({ AppState({
required Mode mode, required Mode mode,
@@ -54,8 +57,10 @@ class AppState with ChangeNotifier {
_totalTraffic = Traffic(), _totalTraffic = Traffic(),
_delayMap = {}, _delayMap = {},
_groups = [], _groups = [],
_providers = [],
_packages = [],
_isCompatible = isCompatible, _isCompatible = isCompatible,
_systemColorSchemes = SystemColorSchemes(); _systemColorSchemes = const SystemColorSchemes();
String get currentLabel => _currentLabel; String get currentLabel => _currentLabel;
@@ -109,7 +114,7 @@ class AppState with ChangeNotifier {
} }
} }
String getDesc(String type, String? proxyName) { String getDesc(String type, String proxyName) {
final groupTypeNamesList = GroupType.values.map((e) => e.name).toList(); final groupTypeNamesList = GroupType.values.map((e) => e.name).toList();
if (!groupTypeNamesList.contains(type)) { if (!groupTypeNamesList.contains(type)) {
return type; return type;
@@ -120,15 +125,17 @@ class AppState with ChangeNotifier {
} }
} }
String? getRealProxyName(String? proxyName) { String getRealProxyName(String proxyName) {
if (proxyName == null) return null; if (proxyName.isEmpty) return proxyName;
final index = groups.indexWhere((element) => element.name == proxyName); final index = groups.indexWhere((element) => element.name == proxyName);
if (index == -1) return proxyName; if (index == -1) return proxyName;
final group = groups[index]; final group = groups[index];
return getRealProxyName((selectedMap.containsKey(proxyName) final currentSelectedName =
? selectedMap[proxyName] group.getCurrentSelectedName(selectedMap[proxyName] ?? '');
: group.now)) ?? if (currentSelectedName.isEmpty) return proxyName;
proxyName; return getRealProxyName(
currentSelectedName,
);
} }
String? get showProxyName { String? get showProxyName {
@@ -140,7 +147,7 @@ class AppState with ChangeNotifier {
return selectedMap[firstGroupName] ?? firstGroup.now; return selectedMap[firstGroupName] ?? firstGroup.now;
} }
int? getDelay(String? proxyName) { int? getDelay(String proxyName) {
return _delayMap[getRealProxyName(proxyName)]; return _delayMap[getRealProxyName(proxyName)];
} }
@@ -293,6 +300,7 @@ class AppState with ChangeNotifier {
.toList(); .toList();
case Mode.rule: case Mode.rule:
return groups return groups
.where((item) => item.hidden == false)
.where((element) => element.name != GroupName.GLOBAL.name) .where((element) => element.name != GroupName.GLOBAL.name)
.toList(); .toList();
} }
@@ -327,6 +335,32 @@ class AppState with ChangeNotifier {
} }
} }
List<Package> get packages => _packages;
set packages(List<Package> value) {
if (!const ListEquality<Package>().equals(_packages, value)) {
_packages = value;
notifyListeners();
}
}
List<ExternalProvider> get providers => _providers;
set providers(List<ExternalProvider> value) {
if (!const ListEquality<ExternalProvider>().equals(_providers, value)) {
_providers = value;
notifyListeners();
}
}
setProvider(ExternalProvider? provider) {
if(provider == null) return;
final index = _providers.indexWhere((item) => item.name == provider.name);
if (index == -1) return;
_providers = List.from(_providers)..[index] = provider;
notifyListeners();
}
Group? getGroupWithName(String groupName) { Group? getGroupWithName(String groupName) {
final index = final index =
currentGroups.indexWhere((element) => element.name == groupName); currentGroups.indexWhere((element) => element.name == groupName);

View File

@@ -119,6 +119,7 @@ class ClashConfig extends ChangeNotifier {
String _externalController; String _externalController;
Mode _mode; Mode _mode;
FindProcessMode _findProcessMode; FindProcessMode _findProcessMode;
int _keepAliveInterval;
bool _unifiedDelay; bool _unifiedDelay;
bool _tcpConcurrent; bool _tcpConcurrent;
Tun _tun; Tun _tun;
@@ -139,6 +140,7 @@ class ClashConfig extends ChangeNotifier {
_unifiedDelay = false, _unifiedDelay = false,
_geodataLoader = geodataLoaderMemconservative, _geodataLoader = geodataLoaderMemconservative,
_externalController = '', _externalController = '',
_keepAliveInterval = 30,
_dns = Dns(), _dns = Dns(),
_geoXUrl = defaultGeoXMap, _geoXUrl = defaultGeoXMap,
_rules = []; _rules = [];
@@ -203,6 +205,16 @@ class ClashConfig extends ChangeNotifier {
} }
} }
@JsonKey(name: "keep-alive-interval", defaultValue: 30)
int get keepAliveInterval => _keepAliveInterval;
set keepAliveInterval(int value) {
if (_keepAliveInterval != value) {
_keepAliveInterval = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false) @JsonKey(defaultValue: false)
bool get ipv6 => _ipv6; bool get ipv6 => _ipv6;

View File

@@ -18,6 +18,7 @@ class AccessControl with _$AccessControl {
@Default(AccessControlMode.rejectSelected) AccessControlMode mode, @Default(AccessControlMode.rejectSelected) AccessControlMode mode,
@Default([]) List<String> acceptList, @Default([]) List<String> acceptList,
@Default([]) List<String> rejectList, @Default([]) List<String> rejectList,
@Default(AccessSortType.none) AccessSortType sort,
@Default(true) bool isFilterSystemApp, @Default(true) bool isFilterSystemApp,
}) = _AccessControl; }) = _AccessControl;
@@ -25,15 +26,26 @@ class AccessControl with _$AccessControl {
_$AccessControlFromJson(json); _$AccessControlFromJson(json);
} }
extension AccessControlExt on AccessControl {
List<String> get currentList => switch (mode) {
AccessControlMode.acceptSelected => acceptList,
AccessControlMode.rejectSelected => rejectList,
};
}
@freezed @freezed
class Props with _$Props { class CoreState with _$CoreState {
const factory Props({ const factory CoreState({
AccessControl? accessControl, AccessControl? accessControl,
required String currentProfileName,
required bool allowBypass, required bool allowBypass,
required bool systemProxy, required bool systemProxy,
}) = _Props; required int mixedPort,
required bool onlyProxy,
}) = _CoreState;
factory Props.fromJson(Map<String, Object?> json) => _$PropsFromJson(json); factory CoreState.fromJson(Map<String, Object?> json) =>
_$CoreStateFromJson(json);
} }
@freezed @freezed
@@ -76,9 +88,11 @@ class Config extends ChangeNotifier {
bool _isCloseConnections; bool _isCloseConnections;
ProxiesType _proxiesType; ProxiesType _proxiesType;
ProxyCardType _proxyCardType; ProxyCardType _proxyCardType;
int _proxiesColumns; ProxiesLayout _proxiesLayout;
String _testUrl; String _testUrl;
WindowProps _windowProps; WindowProps _windowProps;
bool _onlyProxy;
bool _prueBlack;
Config() Config()
: _profiles = [], : _profiles = [],
@@ -103,7 +117,9 @@ class Config extends ChangeNotifier {
_proxyCardType = ProxyCardType.expand, _proxyCardType = ProxyCardType.expand,
_windowProps = defaultWindowProps, _windowProps = defaultWindowProps,
_proxiesType = ProxiesType.tab, _proxiesType = ProxiesType.tab,
_proxiesColumns = 2; _prueBlack = false,
_onlyProxy = false,
_proxiesLayout = ProxiesLayout.standard;
deleteProfileById(String id) { deleteProfileById(String id) {
_profiles = profiles.where((element) => element.id != id).toList(); _profiles = profiles.where((element) => element.id != id).toList();
@@ -305,6 +321,16 @@ class Config extends ChangeNotifier {
} }
} }
@JsonKey(defaultValue: ProxiesLayout.standard)
ProxiesLayout get proxiesLayout => _proxiesLayout;
set proxiesLayout(ProxiesLayout value) {
if (_proxiesLayout != value) {
_proxiesLayout = value;
notifyListeners();
}
}
@JsonKey(defaultValue: true) @JsonKey(defaultValue: true)
bool get isMinimizeOnExit => _isMinimizeOnExit; bool get isMinimizeOnExit => _isMinimizeOnExit;
@@ -407,6 +433,30 @@ class Config extends ChangeNotifier {
} }
} }
@JsonKey(defaultValue: false)
bool get onlyProxy {
return _onlyProxy;
}
set onlyProxy(bool value) {
if (_onlyProxy != value) {
_onlyProxy = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get prueBlack {
return _prueBlack;
}
set prueBlack(bool value) {
if (_prueBlack != value) {
_prueBlack = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false) @JsonKey(defaultValue: false)
bool get isCloseConnections { bool get isCloseConnections {
return _isCloseConnections; return _isCloseConnections;
@@ -442,16 +492,6 @@ class Config extends ChangeNotifier {
} }
} }
@JsonKey(defaultValue: 2)
int get proxiesColumns => _proxiesColumns;
set proxiesColumns(int value) {
if (_proxiesColumns != value) {
_proxiesColumns = value;
notifyListeners();
}
}
@JsonKey(name: "test-url", defaultValue: defaultTestUrl) @JsonKey(name: "test-url", defaultValue: defaultTestUrl)
String get testUrl => _testUrl; String get testUrl => _testUrl;
@@ -513,6 +553,7 @@ class Config extends ChangeNotifier {
_accessControl = config._accessControl; _accessControl = config._accessControl;
_isAnimateToPage = config._isAnimateToPage; _isAnimateToPage = config._isAnimateToPage;
_autoCheckUpdate = config._autoCheckUpdate; _autoCheckUpdate = config._autoCheckUpdate;
_prueBlack = config._prueBlack;
_testUrl = config._testUrl; _testUrl = config._testUrl;
_isExclude = config._isExclude; _isExclude = config._isExclude;
_windowProps = config._windowProps; _windowProps = config._windowProps;

View File

@@ -24,7 +24,7 @@ class ConfigExtendedParams with _$ConfigExtendedParams {
@freezed @freezed
class UpdateConfigParams with _$UpdateConfigParams { class UpdateConfigParams with _$UpdateConfigParams {
const factory UpdateConfigParams({ const factory UpdateConfigParams({
@JsonKey(name: "profile-path") String? profilePath, @JsonKey(name: "profile-id") required String profileId,
required ClashConfig config, required ClashConfig config,
required ConfigExtendedParams params, required ConfigExtendedParams params,
}) = _UpdateConfigParams; }) = _UpdateConfigParams;
@@ -123,6 +123,9 @@ class ExternalProvider with _$ExternalProvider {
const factory ExternalProvider({ const factory ExternalProvider({
required String name, required String name,
required String type, required String type,
required String path,
required int count,
@Default(false) bool isUpdating,
@JsonKey(name: "vehicle-type") required String vehicleType, @JsonKey(name: "vehicle-type") required String vehicleType,
@JsonKey(name: "update-at") required DateTime updateAt, @JsonKey(name: "update-at") required DateTime updateAt,
}) = _ExternalProvider; }) = _ExternalProvider;
@@ -140,7 +143,7 @@ abstract mixin class AppMessageListener {
void onStarted(String runTime) {} void onStarted(String runTime) {}
void onLoaded(String groupName) {} void onLoaded(String providerName) {}
} }
abstract mixin class ServiceMessageListener { abstract mixin class ServiceMessageListener {
@@ -150,7 +153,5 @@ abstract mixin class ServiceMessageListener {
onStarted(String runTime) {} onStarted(String runTime) {}
onLoaded(String groupName) {} onLoaded(String providerName) {}
} }

21
lib/models/file.dart Normal file
View File

@@ -0,0 +1,21 @@
import 'package:fl_clash/common/datetime.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'traffic.dart';
part 'generated/file.freezed.dart';
@freezed
class FileInfo with _$FileInfo {
const factory FileInfo({
required int size,
required DateTime lastModified,
}) = _FileInfo;
}
extension FileInfoExt on FileInfo{
String get desc => "${TrafficValue(value: size).show} · ${lastModified.lastUpdateTimeDesc}";
}

View File

@@ -45,6 +45,7 @@ ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig()
..logLevel = ..logLevel =
$enumDecodeNullable(_$LogLevelEnumMap, json['log-level']) ?? LogLevel.info $enumDecodeNullable(_$LogLevelEnumMap, json['log-level']) ?? LogLevel.info
..externalController = json['external-controller'] as String? ?? '' ..externalController = json['external-controller'] as String? ?? ''
..keepAliveInterval = (json['keep-alive-interval'] as num?)?.toInt() ?? 30
..ipv6 = json['ipv6'] as bool? ?? false ..ipv6 = json['ipv6'] as bool? ?? false
..geodataLoader = json['geodata-loader'] as String? ?? 'memconservative' ..geodataLoader = json['geodata-loader'] as String? ?? 'memconservative'
..unifiedDelay = json['unified-delay'] as bool? ?? false ..unifiedDelay = json['unified-delay'] as bool? ?? false
@@ -75,6 +76,7 @@ Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) =>
'allow-lan': instance.allowLan, 'allow-lan': instance.allowLan,
'log-level': _$LogLevelEnumMap[instance.logLevel]!, 'log-level': _$LogLevelEnumMap[instance.logLevel]!,
'external-controller': instance.externalController, 'external-controller': instance.externalController,
'keep-alive-interval': instance.keepAliveInterval,
'ipv6': instance.ipv6, 'ipv6': instance.ipv6,
'geodata-loader': instance.geodataLoader, 'geodata-loader': instance.geodataLoader,
'unified-delay': instance.unifiedDelay, 'unified-delay': instance.unifiedDelay,

View File

@@ -23,6 +23,7 @@ mixin _$AccessControl {
AccessControlMode get mode => throw _privateConstructorUsedError; AccessControlMode get mode => throw _privateConstructorUsedError;
List<String> get acceptList => throw _privateConstructorUsedError; List<String> get acceptList => throw _privateConstructorUsedError;
List<String> get rejectList => throw _privateConstructorUsedError; List<String> get rejectList => throw _privateConstructorUsedError;
AccessSortType get sort => throw _privateConstructorUsedError;
bool get isFilterSystemApp => throw _privateConstructorUsedError; bool get isFilterSystemApp => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -41,6 +42,7 @@ abstract class $AccessControlCopyWith<$Res> {
{AccessControlMode mode, {AccessControlMode mode,
List<String> acceptList, List<String> acceptList,
List<String> rejectList, List<String> rejectList,
AccessSortType sort,
bool isFilterSystemApp}); bool isFilterSystemApp});
} }
@@ -60,6 +62,7 @@ class _$AccessControlCopyWithImpl<$Res, $Val extends AccessControl>
Object? mode = null, Object? mode = null,
Object? acceptList = null, Object? acceptList = null,
Object? rejectList = null, Object? rejectList = null,
Object? sort = null,
Object? isFilterSystemApp = null, Object? isFilterSystemApp = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
@@ -75,6 +78,10 @@ class _$AccessControlCopyWithImpl<$Res, $Val extends AccessControl>
? _value.rejectList ? _value.rejectList
: rejectList // ignore: cast_nullable_to_non_nullable : rejectList // ignore: cast_nullable_to_non_nullable
as List<String>, as List<String>,
sort: null == sort
? _value.sort
: sort // ignore: cast_nullable_to_non_nullable
as AccessSortType,
isFilterSystemApp: null == isFilterSystemApp isFilterSystemApp: null == isFilterSystemApp
? _value.isFilterSystemApp ? _value.isFilterSystemApp
: isFilterSystemApp // ignore: cast_nullable_to_non_nullable : isFilterSystemApp // ignore: cast_nullable_to_non_nullable
@@ -95,6 +102,7 @@ abstract class _$$AccessControlImplCopyWith<$Res>
{AccessControlMode mode, {AccessControlMode mode,
List<String> acceptList, List<String> acceptList,
List<String> rejectList, List<String> rejectList,
AccessSortType sort,
bool isFilterSystemApp}); bool isFilterSystemApp});
} }
@@ -112,6 +120,7 @@ class __$$AccessControlImplCopyWithImpl<$Res>
Object? mode = null, Object? mode = null,
Object? acceptList = null, Object? acceptList = null,
Object? rejectList = null, Object? rejectList = null,
Object? sort = null,
Object? isFilterSystemApp = null, Object? isFilterSystemApp = null,
}) { }) {
return _then(_$AccessControlImpl( return _then(_$AccessControlImpl(
@@ -127,6 +136,10 @@ class __$$AccessControlImplCopyWithImpl<$Res>
? _value._rejectList ? _value._rejectList
: rejectList // ignore: cast_nullable_to_non_nullable : rejectList // ignore: cast_nullable_to_non_nullable
as List<String>, as List<String>,
sort: null == sort
? _value.sort
: sort // ignore: cast_nullable_to_non_nullable
as AccessSortType,
isFilterSystemApp: null == isFilterSystemApp isFilterSystemApp: null == isFilterSystemApp
? _value.isFilterSystemApp ? _value.isFilterSystemApp
: isFilterSystemApp // ignore: cast_nullable_to_non_nullable : isFilterSystemApp // ignore: cast_nullable_to_non_nullable
@@ -142,6 +155,7 @@ class _$AccessControlImpl implements _AccessControl {
{this.mode = AccessControlMode.rejectSelected, {this.mode = AccessControlMode.rejectSelected,
final List<String> acceptList = const [], final List<String> acceptList = const [],
final List<String> rejectList = const [], final List<String> rejectList = const [],
this.sort = AccessSortType.none,
this.isFilterSystemApp = true}) this.isFilterSystemApp = true})
: _acceptList = acceptList, : _acceptList = acceptList,
_rejectList = rejectList; _rejectList = rejectList;
@@ -170,13 +184,16 @@ class _$AccessControlImpl implements _AccessControl {
return EqualUnmodifiableListView(_rejectList); return EqualUnmodifiableListView(_rejectList);
} }
@override
@JsonKey()
final AccessSortType sort;
@override @override
@JsonKey() @JsonKey()
final bool isFilterSystemApp; final bool isFilterSystemApp;
@override @override
String toString() { String toString() {
return 'AccessControl(mode: $mode, acceptList: $acceptList, rejectList: $rejectList, isFilterSystemApp: $isFilterSystemApp)'; return 'AccessControl(mode: $mode, acceptList: $acceptList, rejectList: $rejectList, sort: $sort, isFilterSystemApp: $isFilterSystemApp)';
} }
@override @override
@@ -189,6 +206,7 @@ class _$AccessControlImpl implements _AccessControl {
.equals(other._acceptList, _acceptList) && .equals(other._acceptList, _acceptList) &&
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other._rejectList, _rejectList) && .equals(other._rejectList, _rejectList) &&
(identical(other.sort, sort) || other.sort == sort) &&
(identical(other.isFilterSystemApp, isFilterSystemApp) || (identical(other.isFilterSystemApp, isFilterSystemApp) ||
other.isFilterSystemApp == isFilterSystemApp)); other.isFilterSystemApp == isFilterSystemApp));
} }
@@ -200,6 +218,7 @@ class _$AccessControlImpl implements _AccessControl {
mode, mode,
const DeepCollectionEquality().hash(_acceptList), const DeepCollectionEquality().hash(_acceptList),
const DeepCollectionEquality().hash(_rejectList), const DeepCollectionEquality().hash(_rejectList),
sort,
isFilterSystemApp); isFilterSystemApp);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@@ -221,6 +240,7 @@ abstract class _AccessControl implements AccessControl {
{final AccessControlMode mode, {final AccessControlMode mode,
final List<String> acceptList, final List<String> acceptList,
final List<String> rejectList, final List<String> rejectList,
final AccessSortType sort,
final bool isFilterSystemApp}) = _$AccessControlImpl; final bool isFilterSystemApp}) = _$AccessControlImpl;
factory _AccessControl.fromJson(Map<String, dynamic> json) = factory _AccessControl.fromJson(Map<String, dynamic> json) =
@@ -233,6 +253,8 @@ abstract class _AccessControl implements AccessControl {
@override @override
List<String> get rejectList; List<String> get rejectList;
@override @override
AccessSortType get sort;
@override
bool get isFilterSystemApp; bool get isFilterSystemApp;
@override @override
@JsonKey(ignore: true) @JsonKey(ignore: true)
@@ -240,35 +262,45 @@ abstract class _AccessControl implements AccessControl {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
Props _$PropsFromJson(Map<String, dynamic> json) { CoreState _$CoreStateFromJson(Map<String, dynamic> json) {
return _Props.fromJson(json); return _CoreState.fromJson(json);
} }
/// @nodoc /// @nodoc
mixin _$Props { mixin _$CoreState {
AccessControl? get accessControl => throw _privateConstructorUsedError; AccessControl? get accessControl => throw _privateConstructorUsedError;
String get currentProfileName => throw _privateConstructorUsedError;
bool get allowBypass => throw _privateConstructorUsedError; bool get allowBypass => throw _privateConstructorUsedError;
bool get systemProxy => throw _privateConstructorUsedError; bool get systemProxy => throw _privateConstructorUsedError;
int get mixedPort => throw _privateConstructorUsedError;
bool get onlyProxy => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
$PropsCopyWith<Props> get copyWith => throw _privateConstructorUsedError; $CoreStateCopyWith<CoreState> get copyWith =>
throw _privateConstructorUsedError;
} }
/// @nodoc /// @nodoc
abstract class $PropsCopyWith<$Res> { abstract class $CoreStateCopyWith<$Res> {
factory $PropsCopyWith(Props value, $Res Function(Props) then) = factory $CoreStateCopyWith(CoreState value, $Res Function(CoreState) then) =
_$PropsCopyWithImpl<$Res, Props>; _$CoreStateCopyWithImpl<$Res, CoreState>;
@useResult @useResult
$Res call({AccessControl? accessControl, bool allowBypass, bool systemProxy}); $Res call(
{AccessControl? accessControl,
String currentProfileName,
bool allowBypass,
bool systemProxy,
int mixedPort,
bool onlyProxy});
$AccessControlCopyWith<$Res>? get accessControl; $AccessControlCopyWith<$Res>? get accessControl;
} }
/// @nodoc /// @nodoc
class _$PropsCopyWithImpl<$Res, $Val extends Props> class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
implements $PropsCopyWith<$Res> { implements $CoreStateCopyWith<$Res> {
_$PropsCopyWithImpl(this._value, this._then); _$CoreStateCopyWithImpl(this._value, this._then);
// ignore: unused_field // ignore: unused_field
final $Val _value; final $Val _value;
@@ -279,14 +311,21 @@ class _$PropsCopyWithImpl<$Res, $Val extends Props>
@override @override
$Res call({ $Res call({
Object? accessControl = freezed, Object? accessControl = freezed,
Object? currentProfileName = null,
Object? allowBypass = null, Object? allowBypass = null,
Object? systemProxy = null, Object? systemProxy = null,
Object? mixedPort = null,
Object? onlyProxy = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
accessControl: freezed == accessControl accessControl: freezed == accessControl
? _value.accessControl ? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable : accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?, as AccessControl?,
currentProfileName: null == currentProfileName
? _value.currentProfileName
: currentProfileName // ignore: cast_nullable_to_non_nullable
as String,
allowBypass: null == allowBypass allowBypass: null == allowBypass
? _value.allowBypass ? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable : allowBypass // ignore: cast_nullable_to_non_nullable
@@ -295,6 +334,14 @@ class _$PropsCopyWithImpl<$Res, $Val extends Props>
? _value.systemProxy ? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable : systemProxy // ignore: cast_nullable_to_non_nullable
as bool, as bool,
mixedPort: null == mixedPort
? _value.mixedPort
: mixedPort // ignore: cast_nullable_to_non_nullable
as int,
onlyProxy: null == onlyProxy
? _value.onlyProxy
: onlyProxy // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val); ) as $Val);
} }
@@ -312,38 +359,52 @@ class _$PropsCopyWithImpl<$Res, $Val extends Props>
} }
/// @nodoc /// @nodoc
abstract class _$$PropsImplCopyWith<$Res> implements $PropsCopyWith<$Res> { abstract class _$$CoreStateImplCopyWith<$Res>
factory _$$PropsImplCopyWith( implements $CoreStateCopyWith<$Res> {
_$PropsImpl value, $Res Function(_$PropsImpl) then) = factory _$$CoreStateImplCopyWith(
__$$PropsImplCopyWithImpl<$Res>; _$CoreStateImpl value, $Res Function(_$CoreStateImpl) then) =
__$$CoreStateImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({AccessControl? accessControl, bool allowBypass, bool systemProxy}); $Res call(
{AccessControl? accessControl,
String currentProfileName,
bool allowBypass,
bool systemProxy,
int mixedPort,
bool onlyProxy});
@override @override
$AccessControlCopyWith<$Res>? get accessControl; $AccessControlCopyWith<$Res>? get accessControl;
} }
/// @nodoc /// @nodoc
class __$$PropsImplCopyWithImpl<$Res> class __$$CoreStateImplCopyWithImpl<$Res>
extends _$PropsCopyWithImpl<$Res, _$PropsImpl> extends _$CoreStateCopyWithImpl<$Res, _$CoreStateImpl>
implements _$$PropsImplCopyWith<$Res> { implements _$$CoreStateImplCopyWith<$Res> {
__$$PropsImplCopyWithImpl( __$$CoreStateImplCopyWithImpl(
_$PropsImpl _value, $Res Function(_$PropsImpl) _then) _$CoreStateImpl _value, $Res Function(_$CoreStateImpl) _then)
: super(_value, _then); : super(_value, _then);
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? accessControl = freezed, Object? accessControl = freezed,
Object? currentProfileName = null,
Object? allowBypass = null, Object? allowBypass = null,
Object? systemProxy = null, Object? systemProxy = null,
Object? mixedPort = null,
Object? onlyProxy = null,
}) { }) {
return _then(_$PropsImpl( return _then(_$CoreStateImpl(
accessControl: freezed == accessControl accessControl: freezed == accessControl
? _value.accessControl ? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable : accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?, as AccessControl?,
currentProfileName: null == currentProfileName
? _value.currentProfileName
: currentProfileName // ignore: cast_nullable_to_non_nullable
as String,
allowBypass: null == allowBypass allowBypass: null == allowBypass
? _value.allowBypass ? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable : allowBypass // ignore: cast_nullable_to_non_nullable
@@ -352,82 +413,115 @@ class __$$PropsImplCopyWithImpl<$Res>
? _value.systemProxy ? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable : systemProxy // ignore: cast_nullable_to_non_nullable
as bool, as bool,
mixedPort: null == mixedPort
? _value.mixedPort
: mixedPort // ignore: cast_nullable_to_non_nullable
as int,
onlyProxy: null == onlyProxy
? _value.onlyProxy
: onlyProxy // ignore: cast_nullable_to_non_nullable
as bool,
)); ));
} }
} }
/// @nodoc /// @nodoc
@JsonSerializable() @JsonSerializable()
class _$PropsImpl implements _Props { class _$CoreStateImpl implements _CoreState {
const _$PropsImpl( const _$CoreStateImpl(
{this.accessControl, {this.accessControl,
required this.currentProfileName,
required this.allowBypass, required this.allowBypass,
required this.systemProxy}); required this.systemProxy,
required this.mixedPort,
required this.onlyProxy});
factory _$PropsImpl.fromJson(Map<String, dynamic> json) => factory _$CoreStateImpl.fromJson(Map<String, dynamic> json) =>
_$$PropsImplFromJson(json); _$$CoreStateImplFromJson(json);
@override @override
final AccessControl? accessControl; final AccessControl? accessControl;
@override @override
final String currentProfileName;
@override
final bool allowBypass; final bool allowBypass;
@override @override
final bool systemProxy; final bool systemProxy;
@override
final int mixedPort;
@override
final bool onlyProxy;
@override @override
String toString() { String toString() {
return 'Props(accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy)'; return 'CoreState(accessControl: $accessControl, currentProfileName: $currentProfileName, allowBypass: $allowBypass, systemProxy: $systemProxy, mixedPort: $mixedPort, onlyProxy: $onlyProxy)';
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$PropsImpl && other is _$CoreStateImpl &&
(identical(other.accessControl, accessControl) || (identical(other.accessControl, accessControl) ||
other.accessControl == accessControl) && other.accessControl == accessControl) &&
(identical(other.currentProfileName, currentProfileName) ||
other.currentProfileName == currentProfileName) &&
(identical(other.allowBypass, allowBypass) || (identical(other.allowBypass, allowBypass) ||
other.allowBypass == allowBypass) && other.allowBypass == allowBypass) &&
(identical(other.systemProxy, systemProxy) || (identical(other.systemProxy, systemProxy) ||
other.systemProxy == systemProxy)); other.systemProxy == systemProxy) &&
(identical(other.mixedPort, mixedPort) ||
other.mixedPort == mixedPort) &&
(identical(other.onlyProxy, onlyProxy) ||
other.onlyProxy == onlyProxy));
} }
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
int get hashCode => int get hashCode => Object.hash(runtimeType, accessControl,
Object.hash(runtimeType, accessControl, allowBypass, systemProxy); currentProfileName, allowBypass, systemProxy, mixedPort, onlyProxy);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$PropsImplCopyWith<_$PropsImpl> get copyWith => _$$CoreStateImplCopyWith<_$CoreStateImpl> get copyWith =>
__$$PropsImplCopyWithImpl<_$PropsImpl>(this, _$identity); __$$CoreStateImplCopyWithImpl<_$CoreStateImpl>(this, _$identity);
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return _$$PropsImplToJson( return _$$CoreStateImplToJson(
this, this,
); );
} }
} }
abstract class _Props implements Props { abstract class _CoreState implements CoreState {
const factory _Props( const factory _CoreState(
{final AccessControl? accessControl, {final AccessControl? accessControl,
required final String currentProfileName,
required final bool allowBypass, required final bool allowBypass,
required final bool systemProxy}) = _$PropsImpl; required final bool systemProxy,
required final int mixedPort,
required final bool onlyProxy}) = _$CoreStateImpl;
factory _Props.fromJson(Map<String, dynamic> json) = _$PropsImpl.fromJson; factory _CoreState.fromJson(Map<String, dynamic> json) =
_$CoreStateImpl.fromJson;
@override @override
AccessControl? get accessControl; AccessControl? get accessControl;
@override @override
String get currentProfileName;
@override
bool get allowBypass; bool get allowBypass;
@override @override
bool get systemProxy; bool get systemProxy;
@override @override
int get mixedPort;
@override
bool get onlyProxy;
@override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$PropsImplCopyWith<_$PropsImpl> get copyWith => _$$CoreStateImplCopyWith<_$CoreStateImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@@ -23,6 +23,9 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
..proxiesSortType = ..proxiesSortType =
$enumDecodeNullable(_$ProxiesSortTypeEnumMap, json['proxiesSortType']) ?? $enumDecodeNullable(_$ProxiesSortTypeEnumMap, json['proxiesSortType']) ??
ProxiesSortType.none ProxiesSortType.none
..proxiesLayout =
$enumDecodeNullable(_$ProxiesLayoutEnumMap, json['proxiesLayout']) ??
ProxiesLayout.standard
..isMinimizeOnExit = json['isMinimizeOnExit'] as bool? ?? true ..isMinimizeOnExit = json['isMinimizeOnExit'] as bool? ?? true
..isAccessControl = json['isAccessControl'] as bool? ?? false ..isAccessControl = json['isAccessControl'] as bool? ?? false
..accessControl = ..accessControl =
@@ -35,6 +38,8 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true ..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true
..allowBypass = json['allowBypass'] as bool? ?? true ..allowBypass = json['allowBypass'] as bool? ?? true
..systemProxy = json['systemProxy'] as bool? ?? false ..systemProxy = json['systemProxy'] as bool? ?? false
..onlyProxy = json['onlyProxy'] as bool? ?? false
..prueBlack = json['prueBlack'] as bool? ?? false
..isCloseConnections = json['isCloseConnections'] as bool? ?? false ..isCloseConnections = json['isCloseConnections'] as bool? ?? false
..proxiesType = $enumDecodeNullable(_$ProxiesTypeEnumMap, json['proxiesType'], ..proxiesType = $enumDecodeNullable(_$ProxiesTypeEnumMap, json['proxiesType'],
unknownValue: ProxiesType.tab) ?? unknownValue: ProxiesType.tab) ??
@@ -42,7 +47,6 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
..proxyCardType = ..proxyCardType =
$enumDecodeNullable(_$ProxyCardTypeEnumMap, json['proxyCardType']) ?? $enumDecodeNullable(_$ProxyCardTypeEnumMap, json['proxyCardType']) ??
ProxyCardType.expand ProxyCardType.expand
..proxiesColumns = (json['proxiesColumns'] as num?)?.toInt() ?? 2
..testUrl = ..testUrl =
json['test-url'] as String? ?? 'https://www.gstatic.com/generate_204' json['test-url'] as String? ?? 'https://www.gstatic.com/generate_204'
..isExclude = json['isExclude'] as bool? ?? false ..isExclude = json['isExclude'] as bool? ?? false
@@ -60,6 +64,7 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'locale': instance.locale, 'locale': instance.locale,
'primaryColor': instance.primaryColor, 'primaryColor': instance.primaryColor,
'proxiesSortType': _$ProxiesSortTypeEnumMap[instance.proxiesSortType]!, 'proxiesSortType': _$ProxiesSortTypeEnumMap[instance.proxiesSortType]!,
'proxiesLayout': _$ProxiesLayoutEnumMap[instance.proxiesLayout]!,
'isMinimizeOnExit': instance.isMinimizeOnExit, 'isMinimizeOnExit': instance.isMinimizeOnExit,
'isAccessControl': instance.isAccessControl, 'isAccessControl': instance.isAccessControl,
'accessControl': instance.accessControl, 'accessControl': instance.accessControl,
@@ -69,10 +74,11 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'autoCheckUpdate': instance.autoCheckUpdate, 'autoCheckUpdate': instance.autoCheckUpdate,
'allowBypass': instance.allowBypass, 'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy, 'systemProxy': instance.systemProxy,
'onlyProxy': instance.onlyProxy,
'prueBlack': instance.prueBlack,
'isCloseConnections': instance.isCloseConnections, 'isCloseConnections': instance.isCloseConnections,
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!, 'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!, 'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
'proxiesColumns': instance.proxiesColumns,
'test-url': instance.testUrl, 'test-url': instance.testUrl,
'isExclude': instance.isExclude, 'isExclude': instance.isExclude,
'windowProps': instance.windowProps, 'windowProps': instance.windowProps,
@@ -90,6 +96,12 @@ const _$ProxiesSortTypeEnumMap = {
ProxiesSortType.name: 'name', ProxiesSortType.name: 'name',
}; };
const _$ProxiesLayoutEnumMap = {
ProxiesLayout.loose: 'loose',
ProxiesLayout.standard: 'standard',
ProxiesLayout.tight: 'tight',
};
const _$ProxiesTypeEnumMap = { const _$ProxiesTypeEnumMap = {
ProxiesType.tab: 'tab', ProxiesType.tab: 'tab',
ProxiesType.list: 'list', ProxiesType.list: 'list',
@@ -113,6 +125,8 @@ _$AccessControlImpl _$$AccessControlImplFromJson(Map<String, dynamic> json) =>
?.map((e) => e as String) ?.map((e) => e as String)
.toList() ?? .toList() ??
const [], const [],
sort: $enumDecodeNullable(_$AccessSortTypeEnumMap, json['sort']) ??
AccessSortType.none,
isFilterSystemApp: json['isFilterSystemApp'] as bool? ?? true, isFilterSystemApp: json['isFilterSystemApp'] as bool? ?? true,
); );
@@ -121,6 +135,7 @@ Map<String, dynamic> _$$AccessControlImplToJson(_$AccessControlImpl instance) =>
'mode': _$AccessControlModeEnumMap[instance.mode]!, 'mode': _$AccessControlModeEnumMap[instance.mode]!,
'acceptList': instance.acceptList, 'acceptList': instance.acceptList,
'rejectList': instance.rejectList, 'rejectList': instance.rejectList,
'sort': _$AccessSortTypeEnumMap[instance.sort]!,
'isFilterSystemApp': instance.isFilterSystemApp, 'isFilterSystemApp': instance.isFilterSystemApp,
}; };
@@ -129,20 +144,33 @@ const _$AccessControlModeEnumMap = {
AccessControlMode.rejectSelected: 'rejectSelected', AccessControlMode.rejectSelected: 'rejectSelected',
}; };
_$PropsImpl _$$PropsImplFromJson(Map<String, dynamic> json) => _$PropsImpl( const _$AccessSortTypeEnumMap = {
AccessSortType.none: 'none',
AccessSortType.name: 'name',
AccessSortType.time: 'time',
};
_$CoreStateImpl _$$CoreStateImplFromJson(Map<String, dynamic> json) =>
_$CoreStateImpl(
accessControl: json['accessControl'] == null accessControl: json['accessControl'] == null
? null ? null
: AccessControl.fromJson( : AccessControl.fromJson(
json['accessControl'] as Map<String, dynamic>), json['accessControl'] as Map<String, dynamic>),
currentProfileName: json['currentProfileName'] as String,
allowBypass: json['allowBypass'] as bool, allowBypass: json['allowBypass'] as bool,
systemProxy: json['systemProxy'] as bool, systemProxy: json['systemProxy'] as bool,
mixedPort: (json['mixedPort'] as num).toInt(),
onlyProxy: json['onlyProxy'] as bool,
); );
Map<String, dynamic> _$$PropsImplToJson(_$PropsImpl instance) => Map<String, dynamic> _$$CoreStateImplToJson(_$CoreStateImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'accessControl': instance.accessControl, 'accessControl': instance.accessControl,
'currentProfileName': instance.currentProfileName,
'allowBypass': instance.allowBypass, 'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy, 'systemProxy': instance.systemProxy,
'mixedPort': instance.mixedPort,
'onlyProxy': instance.onlyProxy,
}; };
_$WindowPropsImpl _$$WindowPropsImplFromJson(Map<String, dynamic> json) => _$WindowPropsImpl _$$WindowPropsImplFromJson(Map<String, dynamic> json) =>

View File

@@ -248,8 +248,8 @@ UpdateConfigParams _$UpdateConfigParamsFromJson(Map<String, dynamic> json) {
/// @nodoc /// @nodoc
mixin _$UpdateConfigParams { mixin _$UpdateConfigParams {
@JsonKey(name: "profile-path") @JsonKey(name: "profile-id")
String? get profilePath => throw _privateConstructorUsedError; String get profileId => throw _privateConstructorUsedError;
ClashConfig get config => throw _privateConstructorUsedError; ClashConfig get config => throw _privateConstructorUsedError;
ConfigExtendedParams get params => throw _privateConstructorUsedError; ConfigExtendedParams get params => throw _privateConstructorUsedError;
@@ -266,7 +266,7 @@ abstract class $UpdateConfigParamsCopyWith<$Res> {
_$UpdateConfigParamsCopyWithImpl<$Res, UpdateConfigParams>; _$UpdateConfigParamsCopyWithImpl<$Res, UpdateConfigParams>;
@useResult @useResult
$Res call( $Res call(
{@JsonKey(name: "profile-path") String? profilePath, {@JsonKey(name: "profile-id") String profileId,
ClashConfig config, ClashConfig config,
ConfigExtendedParams params}); ConfigExtendedParams params});
@@ -286,15 +286,15 @@ class _$UpdateConfigParamsCopyWithImpl<$Res, $Val extends UpdateConfigParams>
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? profilePath = freezed, Object? profileId = null,
Object? config = null, Object? config = null,
Object? params = null, Object? params = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
profilePath: freezed == profilePath profileId: null == profileId
? _value.profilePath ? _value.profileId
: profilePath // ignore: cast_nullable_to_non_nullable : profileId // ignore: cast_nullable_to_non_nullable
as String?, as String,
config: null == config config: null == config
? _value.config ? _value.config
: config // ignore: cast_nullable_to_non_nullable : config // ignore: cast_nullable_to_non_nullable
@@ -324,7 +324,7 @@ abstract class _$$UpdateConfigParamsImplCopyWith<$Res>
@override @override
@useResult @useResult
$Res call( $Res call(
{@JsonKey(name: "profile-path") String? profilePath, {@JsonKey(name: "profile-id") String profileId,
ClashConfig config, ClashConfig config,
ConfigExtendedParams params}); ConfigExtendedParams params});
@@ -343,15 +343,15 @@ class __$$UpdateConfigParamsImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? profilePath = freezed, Object? profileId = null,
Object? config = null, Object? config = null,
Object? params = null, Object? params = null,
}) { }) {
return _then(_$UpdateConfigParamsImpl( return _then(_$UpdateConfigParamsImpl(
profilePath: freezed == profilePath profileId: null == profileId
? _value.profilePath ? _value.profileId
: profilePath // ignore: cast_nullable_to_non_nullable : profileId // ignore: cast_nullable_to_non_nullable
as String?, as String,
config: null == config config: null == config
? _value.config ? _value.config
: config // ignore: cast_nullable_to_non_nullable : config // ignore: cast_nullable_to_non_nullable
@@ -368,7 +368,7 @@ class __$$UpdateConfigParamsImplCopyWithImpl<$Res>
@JsonSerializable() @JsonSerializable()
class _$UpdateConfigParamsImpl implements _UpdateConfigParams { class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
const _$UpdateConfigParamsImpl( const _$UpdateConfigParamsImpl(
{@JsonKey(name: "profile-path") this.profilePath, {@JsonKey(name: "profile-id") required this.profileId,
required this.config, required this.config,
required this.params}); required this.params});
@@ -376,8 +376,8 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
_$$UpdateConfigParamsImplFromJson(json); _$$UpdateConfigParamsImplFromJson(json);
@override @override
@JsonKey(name: "profile-path") @JsonKey(name: "profile-id")
final String? profilePath; final String profileId;
@override @override
final ClashConfig config; final ClashConfig config;
@override @override
@@ -385,7 +385,7 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
@override @override
String toString() { String toString() {
return 'UpdateConfigParams(profilePath: $profilePath, config: $config, params: $params)'; return 'UpdateConfigParams(profileId: $profileId, config: $config, params: $params)';
} }
@override @override
@@ -393,15 +393,15 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$UpdateConfigParamsImpl && other is _$UpdateConfigParamsImpl &&
(identical(other.profilePath, profilePath) || (identical(other.profileId, profileId) ||
other.profilePath == profilePath) && other.profileId == profileId) &&
(identical(other.config, config) || other.config == config) && (identical(other.config, config) || other.config == config) &&
(identical(other.params, params) || other.params == params)); (identical(other.params, params) || other.params == params));
} }
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash(runtimeType, profilePath, config, params); int get hashCode => Object.hash(runtimeType, profileId, config, params);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -420,7 +420,7 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
abstract class _UpdateConfigParams implements UpdateConfigParams { abstract class _UpdateConfigParams implements UpdateConfigParams {
const factory _UpdateConfigParams( const factory _UpdateConfigParams(
{@JsonKey(name: "profile-path") final String? profilePath, {@JsonKey(name: "profile-id") required final String profileId,
required final ClashConfig config, required final ClashConfig config,
required final ConfigExtendedParams params}) = _$UpdateConfigParamsImpl; required final ConfigExtendedParams params}) = _$UpdateConfigParamsImpl;
@@ -428,8 +428,8 @@ abstract class _UpdateConfigParams implements UpdateConfigParams {
_$UpdateConfigParamsImpl.fromJson; _$UpdateConfigParamsImpl.fromJson;
@override @override
@JsonKey(name: "profile-path") @JsonKey(name: "profile-id")
String? get profilePath; String get profileId;
@override @override
ClashConfig get config; ClashConfig get config;
@override @override
@@ -1687,6 +1687,9 @@ ExternalProvider _$ExternalProviderFromJson(Map<String, dynamic> json) {
mixin _$ExternalProvider { mixin _$ExternalProvider {
String get name => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError;
String get type => throw _privateConstructorUsedError; String get type => throw _privateConstructorUsedError;
String get path => throw _privateConstructorUsedError;
int get count => throw _privateConstructorUsedError;
bool get isUpdating => throw _privateConstructorUsedError;
@JsonKey(name: "vehicle-type") @JsonKey(name: "vehicle-type")
String get vehicleType => throw _privateConstructorUsedError; String get vehicleType => throw _privateConstructorUsedError;
@JsonKey(name: "update-at") @JsonKey(name: "update-at")
@@ -1707,6 +1710,9 @@ abstract class $ExternalProviderCopyWith<$Res> {
$Res call( $Res call(
{String name, {String name,
String type, String type,
String path,
int count,
bool isUpdating,
@JsonKey(name: "vehicle-type") String vehicleType, @JsonKey(name: "vehicle-type") String vehicleType,
@JsonKey(name: "update-at") DateTime updateAt}); @JsonKey(name: "update-at") DateTime updateAt});
} }
@@ -1726,6 +1732,9 @@ class _$ExternalProviderCopyWithImpl<$Res, $Val extends ExternalProvider>
$Res call({ $Res call({
Object? name = null, Object? name = null,
Object? type = null, Object? type = null,
Object? path = null,
Object? count = null,
Object? isUpdating = null,
Object? vehicleType = null, Object? vehicleType = null,
Object? updateAt = null, Object? updateAt = null,
}) { }) {
@@ -1738,6 +1747,18 @@ class _$ExternalProviderCopyWithImpl<$Res, $Val extends ExternalProvider>
? _value.type ? _value.type
: type // ignore: cast_nullable_to_non_nullable : type // ignore: cast_nullable_to_non_nullable
as String, as String,
path: null == path
? _value.path
: path // ignore: cast_nullable_to_non_nullable
as String,
count: null == count
? _value.count
: count // ignore: cast_nullable_to_non_nullable
as int,
isUpdating: null == isUpdating
? _value.isUpdating
: isUpdating // ignore: cast_nullable_to_non_nullable
as bool,
vehicleType: null == vehicleType vehicleType: null == vehicleType
? _value.vehicleType ? _value.vehicleType
: vehicleType // ignore: cast_nullable_to_non_nullable : vehicleType // ignore: cast_nullable_to_non_nullable
@@ -1761,6 +1782,9 @@ abstract class _$$ExternalProviderImplCopyWith<$Res>
$Res call( $Res call(
{String name, {String name,
String type, String type,
String path,
int count,
bool isUpdating,
@JsonKey(name: "vehicle-type") String vehicleType, @JsonKey(name: "vehicle-type") String vehicleType,
@JsonKey(name: "update-at") DateTime updateAt}); @JsonKey(name: "update-at") DateTime updateAt});
} }
@@ -1778,6 +1802,9 @@ class __$$ExternalProviderImplCopyWithImpl<$Res>
$Res call({ $Res call({
Object? name = null, Object? name = null,
Object? type = null, Object? type = null,
Object? path = null,
Object? count = null,
Object? isUpdating = null,
Object? vehicleType = null, Object? vehicleType = null,
Object? updateAt = null, Object? updateAt = null,
}) { }) {
@@ -1790,6 +1817,18 @@ class __$$ExternalProviderImplCopyWithImpl<$Res>
? _value.type ? _value.type
: type // ignore: cast_nullable_to_non_nullable : type // ignore: cast_nullable_to_non_nullable
as String, as String,
path: null == path
? _value.path
: path // ignore: cast_nullable_to_non_nullable
as String,
count: null == count
? _value.count
: count // ignore: cast_nullable_to_non_nullable
as int,
isUpdating: null == isUpdating
? _value.isUpdating
: isUpdating // ignore: cast_nullable_to_non_nullable
as bool,
vehicleType: null == vehicleType vehicleType: null == vehicleType
? _value.vehicleType ? _value.vehicleType
: vehicleType // ignore: cast_nullable_to_non_nullable : vehicleType // ignore: cast_nullable_to_non_nullable
@@ -1808,6 +1847,9 @@ class _$ExternalProviderImpl implements _ExternalProvider {
const _$ExternalProviderImpl( const _$ExternalProviderImpl(
{required this.name, {required this.name,
required this.type, required this.type,
required this.path,
required this.count,
this.isUpdating = false,
@JsonKey(name: "vehicle-type") required this.vehicleType, @JsonKey(name: "vehicle-type") required this.vehicleType,
@JsonKey(name: "update-at") required this.updateAt}); @JsonKey(name: "update-at") required this.updateAt});
@@ -1819,6 +1861,13 @@ class _$ExternalProviderImpl implements _ExternalProvider {
@override @override
final String type; final String type;
@override @override
final String path;
@override
final int count;
@override
@JsonKey()
final bool isUpdating;
@override
@JsonKey(name: "vehicle-type") @JsonKey(name: "vehicle-type")
final String vehicleType; final String vehicleType;
@override @override
@@ -1827,7 +1876,7 @@ class _$ExternalProviderImpl implements _ExternalProvider {
@override @override
String toString() { String toString() {
return 'ExternalProvider(name: $name, type: $type, vehicleType: $vehicleType, updateAt: $updateAt)'; return 'ExternalProvider(name: $name, type: $type, path: $path, count: $count, isUpdating: $isUpdating, vehicleType: $vehicleType, updateAt: $updateAt)';
} }
@override @override
@@ -1837,6 +1886,10 @@ class _$ExternalProviderImpl implements _ExternalProvider {
other is _$ExternalProviderImpl && other is _$ExternalProviderImpl &&
(identical(other.name, name) || other.name == name) && (identical(other.name, name) || other.name == name) &&
(identical(other.type, type) || other.type == type) && (identical(other.type, type) || other.type == type) &&
(identical(other.path, path) || other.path == path) &&
(identical(other.count, count) || other.count == count) &&
(identical(other.isUpdating, isUpdating) ||
other.isUpdating == isUpdating) &&
(identical(other.vehicleType, vehicleType) || (identical(other.vehicleType, vehicleType) ||
other.vehicleType == vehicleType) && other.vehicleType == vehicleType) &&
(identical(other.updateAt, updateAt) || (identical(other.updateAt, updateAt) ||
@@ -1845,8 +1898,8 @@ class _$ExternalProviderImpl implements _ExternalProvider {
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
int get hashCode => int get hashCode => Object.hash(
Object.hash(runtimeType, name, type, vehicleType, updateAt); runtimeType, name, type, path, count, isUpdating, vehicleType, updateAt);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -1867,6 +1920,9 @@ abstract class _ExternalProvider implements ExternalProvider {
const factory _ExternalProvider( const factory _ExternalProvider(
{required final String name, {required final String name,
required final String type, required final String type,
required final String path,
required final int count,
final bool isUpdating,
@JsonKey(name: "vehicle-type") required final String vehicleType, @JsonKey(name: "vehicle-type") required final String vehicleType,
@JsonKey(name: "update-at") required final DateTime updateAt}) = @JsonKey(name: "update-at") required final DateTime updateAt}) =
_$ExternalProviderImpl; _$ExternalProviderImpl;
@@ -1879,6 +1935,12 @@ abstract class _ExternalProvider implements ExternalProvider {
@override @override
String get type; String get type;
@override @override
String get path;
@override
int get count;
@override
bool get isUpdating;
@override
@JsonKey(name: "vehicle-type") @JsonKey(name: "vehicle-type")
String get vehicleType; String get vehicleType;
@override @override

View File

@@ -27,7 +27,7 @@ Map<String, dynamic> _$$ConfigExtendedParamsImplToJson(
_$UpdateConfigParamsImpl _$$UpdateConfigParamsImplFromJson( _$UpdateConfigParamsImpl _$$UpdateConfigParamsImplFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
_$UpdateConfigParamsImpl( _$UpdateConfigParamsImpl(
profilePath: json['profile-path'] as String?, profileId: json['profile-id'] as String,
config: ClashConfig.fromJson(json['config'] as Map<String, dynamic>), config: ClashConfig.fromJson(json['config'] as Map<String, dynamic>),
params: params:
ConfigExtendedParams.fromJson(json['params'] as Map<String, dynamic>), ConfigExtendedParams.fromJson(json['params'] as Map<String, dynamic>),
@@ -36,7 +36,7 @@ _$UpdateConfigParamsImpl _$$UpdateConfigParamsImplFromJson(
Map<String, dynamic> _$$UpdateConfigParamsImplToJson( Map<String, dynamic> _$$UpdateConfigParamsImplToJson(
_$UpdateConfigParamsImpl instance) => _$UpdateConfigParamsImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'profile-path': instance.profilePath, 'profile-id': instance.profileId,
'config': instance.config, 'config': instance.config,
'params': instance.params, 'params': instance.params,
}; };
@@ -156,6 +156,9 @@ _$ExternalProviderImpl _$$ExternalProviderImplFromJson(
_$ExternalProviderImpl( _$ExternalProviderImpl(
name: json['name'] as String, name: json['name'] as String,
type: json['type'] as String, type: json['type'] as String,
path: json['path'] as String,
count: (json['count'] as num).toInt(),
isUpdating: json['isUpdating'] as bool? ?? false,
vehicleType: json['vehicle-type'] as String, vehicleType: json['vehicle-type'] as String,
updateAt: DateTime.parse(json['update-at'] as String), updateAt: DateTime.parse(json['update-at'] as String),
); );
@@ -165,6 +168,9 @@ Map<String, dynamic> _$$ExternalProviderImplToJson(
<String, dynamic>{ <String, dynamic>{
'name': instance.name, 'name': instance.name,
'type': instance.type, 'type': instance.type,
'path': instance.path,
'count': instance.count,
'isUpdating': instance.isUpdating,
'vehicle-type': instance.vehicleType, 'vehicle-type': instance.vehicleType,
'update-at': instance.updateAt.toIso8601String(), 'update-at': instance.updateAt.toIso8601String(),
}; };

View File

@@ -0,0 +1,150 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of '../file.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$FileInfo {
int get size => throw _privateConstructorUsedError;
DateTime get lastModified => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$FileInfoCopyWith<FileInfo> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $FileInfoCopyWith<$Res> {
factory $FileInfoCopyWith(FileInfo value, $Res Function(FileInfo) then) =
_$FileInfoCopyWithImpl<$Res, FileInfo>;
@useResult
$Res call({int size, DateTime lastModified});
}
/// @nodoc
class _$FileInfoCopyWithImpl<$Res, $Val extends FileInfo>
implements $FileInfoCopyWith<$Res> {
_$FileInfoCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? size = null,
Object? lastModified = null,
}) {
return _then(_value.copyWith(
size: null == size
? _value.size
: size // ignore: cast_nullable_to_non_nullable
as int,
lastModified: null == lastModified
? _value.lastModified
: lastModified // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$FileInfoImplCopyWith<$Res>
implements $FileInfoCopyWith<$Res> {
factory _$$FileInfoImplCopyWith(
_$FileInfoImpl value, $Res Function(_$FileInfoImpl) then) =
__$$FileInfoImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({int size, DateTime lastModified});
}
/// @nodoc
class __$$FileInfoImplCopyWithImpl<$Res>
extends _$FileInfoCopyWithImpl<$Res, _$FileInfoImpl>
implements _$$FileInfoImplCopyWith<$Res> {
__$$FileInfoImplCopyWithImpl(
_$FileInfoImpl _value, $Res Function(_$FileInfoImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? size = null,
Object? lastModified = null,
}) {
return _then(_$FileInfoImpl(
size: null == size
? _value.size
: size // ignore: cast_nullable_to_non_nullable
as int,
lastModified: null == lastModified
? _value.lastModified
: lastModified // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}
/// @nodoc
class _$FileInfoImpl implements _FileInfo {
const _$FileInfoImpl({required this.size, required this.lastModified});
@override
final int size;
@override
final DateTime lastModified;
@override
String toString() {
return 'FileInfo(size: $size, lastModified: $lastModified)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FileInfoImpl &&
(identical(other.size, size) || other.size == size) &&
(identical(other.lastModified, lastModified) ||
other.lastModified == lastModified));
}
@override
int get hashCode => Object.hash(runtimeType, size, lastModified);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$FileInfoImplCopyWith<_$FileInfoImpl> get copyWith =>
__$$FileInfoImplCopyWithImpl<_$FileInfoImpl>(this, _$identity);
}
abstract class _FileInfo implements FileInfo {
const factory _FileInfo(
{required final int size,
required final DateTime lastModified}) = _$FileInfoImpl;
@override
int get size;
@override
DateTime get lastModified;
@override
@JsonKey(ignore: true)
_$$FileInfoImplCopyWith<_$FileInfoImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -23,6 +23,7 @@ mixin _$Package {
String get packageName => throw _privateConstructorUsedError; String get packageName => throw _privateConstructorUsedError;
String get label => throw _privateConstructorUsedError; String get label => throw _privateConstructorUsedError;
bool get isSystem => throw _privateConstructorUsedError; bool get isSystem => throw _privateConstructorUsedError;
int get firstInstallTime => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
@@ -34,7 +35,8 @@ abstract class $PackageCopyWith<$Res> {
factory $PackageCopyWith(Package value, $Res Function(Package) then) = factory $PackageCopyWith(Package value, $Res Function(Package) then) =
_$PackageCopyWithImpl<$Res, Package>; _$PackageCopyWithImpl<$Res, Package>;
@useResult @useResult
$Res call({String packageName, String label, bool isSystem}); $Res call(
{String packageName, String label, bool isSystem, int firstInstallTime});
} }
/// @nodoc /// @nodoc
@@ -53,6 +55,7 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
Object? packageName = null, Object? packageName = null,
Object? label = null, Object? label = null,
Object? isSystem = null, Object? isSystem = null,
Object? firstInstallTime = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
packageName: null == packageName packageName: null == packageName
@@ -67,6 +70,10 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
? _value.isSystem ? _value.isSystem
: isSystem // ignore: cast_nullable_to_non_nullable : isSystem // ignore: cast_nullable_to_non_nullable
as bool, as bool,
firstInstallTime: null == firstInstallTime
? _value.firstInstallTime
: firstInstallTime // ignore: cast_nullable_to_non_nullable
as int,
) as $Val); ) as $Val);
} }
} }
@@ -78,7 +85,8 @@ abstract class _$$PackageImplCopyWith<$Res> implements $PackageCopyWith<$Res> {
__$$PackageImplCopyWithImpl<$Res>; __$$PackageImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({String packageName, String label, bool isSystem}); $Res call(
{String packageName, String label, bool isSystem, int firstInstallTime});
} }
/// @nodoc /// @nodoc
@@ -95,6 +103,7 @@ class __$$PackageImplCopyWithImpl<$Res>
Object? packageName = null, Object? packageName = null,
Object? label = null, Object? label = null,
Object? isSystem = null, Object? isSystem = null,
Object? firstInstallTime = null,
}) { }) {
return _then(_$PackageImpl( return _then(_$PackageImpl(
packageName: null == packageName packageName: null == packageName
@@ -109,6 +118,10 @@ class __$$PackageImplCopyWithImpl<$Res>
? _value.isSystem ? _value.isSystem
: isSystem // ignore: cast_nullable_to_non_nullable : isSystem // ignore: cast_nullable_to_non_nullable
as bool, as bool,
firstInstallTime: null == firstInstallTime
? _value.firstInstallTime
: firstInstallTime // ignore: cast_nullable_to_non_nullable
as int,
)); ));
} }
} }
@@ -117,7 +130,10 @@ class __$$PackageImplCopyWithImpl<$Res>
@JsonSerializable() @JsonSerializable()
class _$PackageImpl implements _Package { class _$PackageImpl implements _Package {
const _$PackageImpl( const _$PackageImpl(
{required this.packageName, required this.label, required this.isSystem}); {required this.packageName,
required this.label,
required this.isSystem,
required this.firstInstallTime});
factory _$PackageImpl.fromJson(Map<String, dynamic> json) => factory _$PackageImpl.fromJson(Map<String, dynamic> json) =>
_$$PackageImplFromJson(json); _$$PackageImplFromJson(json);
@@ -128,10 +144,12 @@ class _$PackageImpl implements _Package {
final String label; final String label;
@override @override
final bool isSystem; final bool isSystem;
@override
final int firstInstallTime;
@override @override
String toString() { String toString() {
return 'Package(packageName: $packageName, label: $label, isSystem: $isSystem)'; return 'Package(packageName: $packageName, label: $label, isSystem: $isSystem, firstInstallTime: $firstInstallTime)';
} }
@override @override
@@ -143,12 +161,15 @@ class _$PackageImpl implements _Package {
other.packageName == packageName) && other.packageName == packageName) &&
(identical(other.label, label) || other.label == label) && (identical(other.label, label) || other.label == label) &&
(identical(other.isSystem, isSystem) || (identical(other.isSystem, isSystem) ||
other.isSystem == isSystem)); other.isSystem == isSystem) &&
(identical(other.firstInstallTime, firstInstallTime) ||
other.firstInstallTime == firstInstallTime));
} }
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash(runtimeType, packageName, label, isSystem); int get hashCode =>
Object.hash(runtimeType, packageName, label, isSystem, firstInstallTime);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -168,7 +189,8 @@ abstract class _Package implements Package {
const factory _Package( const factory _Package(
{required final String packageName, {required final String packageName,
required final String label, required final String label,
required final bool isSystem}) = _$PackageImpl; required final bool isSystem,
required final int firstInstallTime}) = _$PackageImpl;
factory _Package.fromJson(Map<String, dynamic> json) = _$PackageImpl.fromJson; factory _Package.fromJson(Map<String, dynamic> json) = _$PackageImpl.fromJson;
@@ -179,6 +201,8 @@ abstract class _Package implements Package {
@override @override
bool get isSystem; bool get isSystem;
@override @override
int get firstInstallTime;
@override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$PackageImplCopyWith<_$PackageImpl> get copyWith => _$$PackageImplCopyWith<_$PackageImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;

View File

@@ -11,6 +11,7 @@ _$PackageImpl _$$PackageImplFromJson(Map<String, dynamic> json) =>
packageName: json['packageName'] as String, packageName: json['packageName'] as String,
label: json['label'] as String, label: json['label'] as String,
isSystem: json['isSystem'] as bool, isSystem: json['isSystem'] as bool,
firstInstallTime: (json['firstInstallTime'] as num).toInt(),
); );
Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) => Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
@@ -18,4 +19,5 @@ Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
'packageName': instance.packageName, 'packageName': instance.packageName,
'label': instance.label, 'label': instance.label,
'isSystem': instance.isSystem, 'isSystem': instance.isSystem,
'firstInstallTime': instance.firstInstallTime,
}; };

View File

@@ -223,6 +223,8 @@ mixin _$Profile {
bool get autoUpdate => throw _privateConstructorUsedError; bool get autoUpdate => throw _privateConstructorUsedError;
Map<String, String> get selectedMap => throw _privateConstructorUsedError; Map<String, String> get selectedMap => throw _privateConstructorUsedError;
Set<String> get unfoldSet => throw _privateConstructorUsedError; Set<String> get unfoldSet => throw _privateConstructorUsedError;
@JsonKey(includeToJson: false, includeFromJson: false)
bool get isUpdating => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
@@ -244,7 +246,8 @@ abstract class $ProfileCopyWith<$Res> {
UserInfo? userInfo, UserInfo? userInfo,
bool autoUpdate, bool autoUpdate,
Map<String, String> selectedMap, Map<String, String> selectedMap,
Set<String> unfoldSet}); Set<String> unfoldSet,
@JsonKey(includeToJson: false, includeFromJson: false) bool isUpdating});
$UserInfoCopyWith<$Res>? get userInfo; $UserInfoCopyWith<$Res>? get userInfo;
} }
@@ -272,6 +275,7 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
Object? autoUpdate = null, Object? autoUpdate = null,
Object? selectedMap = null, Object? selectedMap = null,
Object? unfoldSet = null, Object? unfoldSet = null,
Object? isUpdating = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
id: null == id id: null == id
@@ -314,6 +318,10 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
? _value.unfoldSet ? _value.unfoldSet
: unfoldSet // ignore: cast_nullable_to_non_nullable : unfoldSet // ignore: cast_nullable_to_non_nullable
as Set<String>, as Set<String>,
isUpdating: null == isUpdating
? _value.isUpdating
: isUpdating // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val); ) as $Val);
} }
@@ -347,7 +355,8 @@ abstract class _$$ProfileImplCopyWith<$Res> implements $ProfileCopyWith<$Res> {
UserInfo? userInfo, UserInfo? userInfo,
bool autoUpdate, bool autoUpdate,
Map<String, String> selectedMap, Map<String, String> selectedMap,
Set<String> unfoldSet}); Set<String> unfoldSet,
@JsonKey(includeToJson: false, includeFromJson: false) bool isUpdating});
@override @override
$UserInfoCopyWith<$Res>? get userInfo; $UserInfoCopyWith<$Res>? get userInfo;
@@ -374,6 +383,7 @@ class __$$ProfileImplCopyWithImpl<$Res>
Object? autoUpdate = null, Object? autoUpdate = null,
Object? selectedMap = null, Object? selectedMap = null,
Object? unfoldSet = null, Object? unfoldSet = null,
Object? isUpdating = null,
}) { }) {
return _then(_$ProfileImpl( return _then(_$ProfileImpl(
id: null == id id: null == id
@@ -416,6 +426,10 @@ class __$$ProfileImplCopyWithImpl<$Res>
? _value._unfoldSet ? _value._unfoldSet
: unfoldSet // ignore: cast_nullable_to_non_nullable : unfoldSet // ignore: cast_nullable_to_non_nullable
as Set<String>, as Set<String>,
isUpdating: null == isUpdating
? _value.isUpdating
: isUpdating // ignore: cast_nullable_to_non_nullable
as bool,
)); ));
} }
} }
@@ -433,7 +447,9 @@ class _$ProfileImpl implements _Profile {
this.userInfo, this.userInfo,
this.autoUpdate = true, this.autoUpdate = true,
final Map<String, String> selectedMap = const {}, final Map<String, String> selectedMap = const {},
final Set<String> unfoldSet = const {}}) final Set<String> unfoldSet = const {},
@JsonKey(includeToJson: false, includeFromJson: false)
this.isUpdating = false})
: _selectedMap = selectedMap, : _selectedMap = selectedMap,
_unfoldSet = unfoldSet; _unfoldSet = unfoldSet;
@@ -476,9 +492,13 @@ class _$ProfileImpl implements _Profile {
return EqualUnmodifiableSetView(_unfoldSet); return EqualUnmodifiableSetView(_unfoldSet);
} }
@override
@JsonKey(includeToJson: false, includeFromJson: false)
final bool isUpdating;
@override @override
String toString() { String toString() {
return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap, unfoldSet: $unfoldSet)'; return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap, unfoldSet: $unfoldSet, isUpdating: $isUpdating)';
} }
@override @override
@@ -502,7 +522,9 @@ class _$ProfileImpl implements _Profile {
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other._selectedMap, _selectedMap) && .equals(other._selectedMap, _selectedMap) &&
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other._unfoldSet, _unfoldSet)); .equals(other._unfoldSet, _unfoldSet) &&
(identical(other.isUpdating, isUpdating) ||
other.isUpdating == isUpdating));
} }
@JsonKey(ignore: true) @JsonKey(ignore: true)
@@ -518,7 +540,8 @@ class _$ProfileImpl implements _Profile {
userInfo, userInfo,
autoUpdate, autoUpdate,
const DeepCollectionEquality().hash(_selectedMap), const DeepCollectionEquality().hash(_selectedMap),
const DeepCollectionEquality().hash(_unfoldSet)); const DeepCollectionEquality().hash(_unfoldSet),
isUpdating);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -545,7 +568,9 @@ abstract class _Profile implements Profile {
final UserInfo? userInfo, final UserInfo? userInfo,
final bool autoUpdate, final bool autoUpdate,
final Map<String, String> selectedMap, final Map<String, String> selectedMap,
final Set<String> unfoldSet}) = _$ProfileImpl; final Set<String> unfoldSet,
@JsonKey(includeToJson: false, includeFromJson: false)
final bool isUpdating}) = _$ProfileImpl;
factory _Profile.fromJson(Map<String, dynamic> json) = _$ProfileImpl.fromJson; factory _Profile.fromJson(Map<String, dynamic> json) = _$ProfileImpl.fromJson;
@@ -570,6 +595,9 @@ abstract class _Profile implements Profile {
@override @override
Set<String> get unfoldSet; Set<String> get unfoldSet;
@override @override
@JsonKey(includeToJson: false, includeFromJson: false)
bool get isUpdating;
@override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$ProfileImplCopyWith<_$ProfileImpl> get copyWith => _$$ProfileImplCopyWith<_$ProfileImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;

View File

@@ -23,6 +23,7 @@ mixin _$Group {
GroupType get type => throw _privateConstructorUsedError; GroupType get type => throw _privateConstructorUsedError;
List<Proxy> get all => throw _privateConstructorUsedError; List<Proxy> get all => throw _privateConstructorUsedError;
String? get now => throw _privateConstructorUsedError; String? get now => throw _privateConstructorUsedError;
bool? get hidden => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -35,7 +36,12 @@ abstract class $GroupCopyWith<$Res> {
factory $GroupCopyWith(Group value, $Res Function(Group) then) = factory $GroupCopyWith(Group value, $Res Function(Group) then) =
_$GroupCopyWithImpl<$Res, Group>; _$GroupCopyWithImpl<$Res, Group>;
@useResult @useResult
$Res call({GroupType type, List<Proxy> all, String? now, String name}); $Res call(
{GroupType type,
List<Proxy> all,
String? now,
bool? hidden,
String name});
} }
/// @nodoc /// @nodoc
@@ -54,6 +60,7 @@ class _$GroupCopyWithImpl<$Res, $Val extends Group>
Object? type = null, Object? type = null,
Object? all = null, Object? all = null,
Object? now = freezed, Object? now = freezed,
Object? hidden = freezed,
Object? name = null, Object? name = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
@@ -69,6 +76,10 @@ class _$GroupCopyWithImpl<$Res, $Val extends Group>
? _value.now ? _value.now
: now // ignore: cast_nullable_to_non_nullable : now // ignore: cast_nullable_to_non_nullable
as String?, as String?,
hidden: freezed == hidden
? _value.hidden
: hidden // ignore: cast_nullable_to_non_nullable
as bool?,
name: null == name name: null == name
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
@@ -84,7 +95,12 @@ abstract class _$$GroupImplCopyWith<$Res> implements $GroupCopyWith<$Res> {
__$$GroupImplCopyWithImpl<$Res>; __$$GroupImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({GroupType type, List<Proxy> all, String? now, String name}); $Res call(
{GroupType type,
List<Proxy> all,
String? now,
bool? hidden,
String name});
} }
/// @nodoc /// @nodoc
@@ -101,6 +117,7 @@ class __$$GroupImplCopyWithImpl<$Res>
Object? type = null, Object? type = null,
Object? all = null, Object? all = null,
Object? now = freezed, Object? now = freezed,
Object? hidden = freezed,
Object? name = null, Object? name = null,
}) { }) {
return _then(_$GroupImpl( return _then(_$GroupImpl(
@@ -116,6 +133,10 @@ class __$$GroupImplCopyWithImpl<$Res>
? _value.now ? _value.now
: now // ignore: cast_nullable_to_non_nullable : now // ignore: cast_nullable_to_non_nullable
as String?, as String?,
hidden: freezed == hidden
? _value.hidden
: hidden // ignore: cast_nullable_to_non_nullable
as bool?,
name: null == name name: null == name
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
@@ -131,6 +152,7 @@ class _$GroupImpl implements _Group {
{required this.type, {required this.type,
final List<Proxy> all = const [], final List<Proxy> all = const [],
this.now, this.now,
this.hidden,
required this.name}) required this.name})
: _all = all; : _all = all;
@@ -151,11 +173,13 @@ class _$GroupImpl implements _Group {
@override @override
final String? now; final String? now;
@override @override
final bool? hidden;
@override
final String name; final String name;
@override @override
String toString() { String toString() {
return 'Group(type: $type, all: $all, now: $now, name: $name)'; return 'Group(type: $type, all: $all, now: $now, hidden: $hidden, name: $name)';
} }
@override @override
@@ -166,13 +190,14 @@ class _$GroupImpl implements _Group {
(identical(other.type, type) || other.type == type) && (identical(other.type, type) || other.type == type) &&
const DeepCollectionEquality().equals(other._all, _all) && const DeepCollectionEquality().equals(other._all, _all) &&
(identical(other.now, now) || other.now == now) && (identical(other.now, now) || other.now == now) &&
(identical(other.hidden, hidden) || other.hidden == hidden) &&
(identical(other.name, name) || other.name == name)); (identical(other.name, name) || other.name == name));
} }
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(runtimeType, type,
runtimeType, type, const DeepCollectionEquality().hash(_all), now, name); const DeepCollectionEquality().hash(_all), now, hidden, name);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -193,6 +218,7 @@ abstract class _Group implements Group {
{required final GroupType type, {required final GroupType type,
final List<Proxy> all, final List<Proxy> all,
final String? now, final String? now,
final bool? hidden,
required final String name}) = _$GroupImpl; required final String name}) = _$GroupImpl;
factory _Group.fromJson(Map<String, dynamic> json) = _$GroupImpl.fromJson; factory _Group.fromJson(Map<String, dynamic> json) = _$GroupImpl.fromJson;
@@ -204,6 +230,8 @@ abstract class _Group implements Group {
@override @override
String? get now; String? get now;
@override @override
bool? get hidden;
@override
String get name; String get name;
@override @override
@JsonKey(ignore: true) @JsonKey(ignore: true)

View File

@@ -13,6 +13,7 @@ _$GroupImpl _$$GroupImplFromJson(Map<String, dynamic> json) => _$GroupImpl(
.toList() ?? .toList() ??
const [], const [],
now: json['now'] as String?, now: json['now'] as String?,
hidden: json['hidden'] as bool?,
name: json['name'] as String, name: json['name'] as String,
); );
@@ -21,6 +22,7 @@ Map<String, dynamic> _$$GroupImplToJson(_$GroupImpl instance) =>
'type': _$GroupTypeEnumMap[instance.type]!, 'type': _$GroupTypeEnumMap[instance.type]!,
'all': instance.all, 'all': instance.all,
'now': instance.now, 'now': instance.now,
'hidden': instance.hidden,
'name': instance.name, 'name': instance.name,
}; };

View File

@@ -158,10 +158,8 @@ abstract class _StartButtonSelectorState implements StartButtonSelectorState {
/// @nodoc /// @nodoc
mixin _$CheckIpSelectorState { mixin _$CheckIpSelectorState {
bool get isInit => throw _privateConstructorUsedError; String? get currentProfileId => throw _privateConstructorUsedError;
bool get isStart => throw _privateConstructorUsedError;
Map<String, String> get selectedMap => throw _privateConstructorUsedError; Map<String, String> get selectedMap => throw _privateConstructorUsedError;
num get checkIpNum => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
$CheckIpSelectorStateCopyWith<CheckIpSelectorState> get copyWith => $CheckIpSelectorStateCopyWith<CheckIpSelectorState> get copyWith =>
@@ -174,11 +172,7 @@ abstract class $CheckIpSelectorStateCopyWith<$Res> {
$Res Function(CheckIpSelectorState) then) = $Res Function(CheckIpSelectorState) then) =
_$CheckIpSelectorStateCopyWithImpl<$Res, CheckIpSelectorState>; _$CheckIpSelectorStateCopyWithImpl<$Res, CheckIpSelectorState>;
@useResult @useResult
$Res call( $Res call({String? currentProfileId, Map<String, String> selectedMap});
{bool isInit,
bool isStart,
Map<String, String> selectedMap,
num checkIpNum});
} }
/// @nodoc /// @nodoc
@@ -195,28 +189,18 @@ class _$CheckIpSelectorStateCopyWithImpl<$Res,
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? isInit = null, Object? currentProfileId = freezed,
Object? isStart = null,
Object? selectedMap = null, Object? selectedMap = null,
Object? checkIpNum = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
isInit: null == isInit currentProfileId: freezed == currentProfileId
? _value.isInit ? _value.currentProfileId
: isInit // ignore: cast_nullable_to_non_nullable : currentProfileId // ignore: cast_nullable_to_non_nullable
as bool, as String?,
isStart: null == isStart
? _value.isStart
: isStart // ignore: cast_nullable_to_non_nullable
as bool,
selectedMap: null == selectedMap selectedMap: null == selectedMap
? _value.selectedMap ? _value.selectedMap
: selectedMap // ignore: cast_nullable_to_non_nullable : selectedMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>, as Map<String, String>,
checkIpNum: null == checkIpNum
? _value.checkIpNum
: checkIpNum // ignore: cast_nullable_to_non_nullable
as num,
) as $Val); ) as $Val);
} }
} }
@@ -229,11 +213,7 @@ abstract class _$$CheckIpSelectorStateImplCopyWith<$Res>
__$$CheckIpSelectorStateImplCopyWithImpl<$Res>; __$$CheckIpSelectorStateImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call( $Res call({String? currentProfileId, Map<String, String> selectedMap});
{bool isInit,
bool isStart,
Map<String, String> selectedMap,
num checkIpNum});
} }
/// @nodoc /// @nodoc
@@ -247,28 +227,18 @@ class __$$CheckIpSelectorStateImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? isInit = null, Object? currentProfileId = freezed,
Object? isStart = null,
Object? selectedMap = null, Object? selectedMap = null,
Object? checkIpNum = null,
}) { }) {
return _then(_$CheckIpSelectorStateImpl( return _then(_$CheckIpSelectorStateImpl(
isInit: null == isInit currentProfileId: freezed == currentProfileId
? _value.isInit ? _value.currentProfileId
: isInit // ignore: cast_nullable_to_non_nullable : currentProfileId // ignore: cast_nullable_to_non_nullable
as bool, as String?,
isStart: null == isStart
? _value.isStart
: isStart // ignore: cast_nullable_to_non_nullable
as bool,
selectedMap: null == selectedMap selectedMap: null == selectedMap
? _value._selectedMap ? _value._selectedMap
: selectedMap // ignore: cast_nullable_to_non_nullable : selectedMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>, as Map<String, String>,
checkIpNum: null == checkIpNum
? _value.checkIpNum
: checkIpNum // ignore: cast_nullable_to_non_nullable
as num,
)); ));
} }
} }
@@ -277,16 +247,12 @@ class __$$CheckIpSelectorStateImplCopyWithImpl<$Res>
class _$CheckIpSelectorStateImpl implements _CheckIpSelectorState { class _$CheckIpSelectorStateImpl implements _CheckIpSelectorState {
const _$CheckIpSelectorStateImpl( const _$CheckIpSelectorStateImpl(
{required this.isInit, {required this.currentProfileId,
required this.isStart, required final Map<String, String> selectedMap})
required final Map<String, String> selectedMap,
required this.checkIpNum})
: _selectedMap = selectedMap; : _selectedMap = selectedMap;
@override @override
final bool isInit; final String? currentProfileId;
@override
final bool isStart;
final Map<String, String> _selectedMap; final Map<String, String> _selectedMap;
@override @override
Map<String, String> get selectedMap { Map<String, String> get selectedMap {
@@ -295,12 +261,9 @@ class _$CheckIpSelectorStateImpl implements _CheckIpSelectorState {
return EqualUnmodifiableMapView(_selectedMap); return EqualUnmodifiableMapView(_selectedMap);
} }
@override
final num checkIpNum;
@override @override
String toString() { String toString() {
return 'CheckIpSelectorState(isInit: $isInit, isStart: $isStart, selectedMap: $selectedMap, checkIpNum: $checkIpNum)'; return 'CheckIpSelectorState(currentProfileId: $currentProfileId, selectedMap: $selectedMap)';
} }
@override @override
@@ -308,17 +271,15 @@ class _$CheckIpSelectorStateImpl implements _CheckIpSelectorState {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$CheckIpSelectorStateImpl && other is _$CheckIpSelectorStateImpl &&
(identical(other.isInit, isInit) || other.isInit == isInit) && (identical(other.currentProfileId, currentProfileId) ||
(identical(other.isStart, isStart) || other.isStart == isStart) && other.currentProfileId == currentProfileId) &&
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other._selectedMap, _selectedMap) && .equals(other._selectedMap, _selectedMap));
(identical(other.checkIpNum, checkIpNum) ||
other.checkIpNum == checkIpNum));
} }
@override @override
int get hashCode => Object.hash(runtimeType, isInit, isStart, int get hashCode => Object.hash(runtimeType, currentProfileId,
const DeepCollectionEquality().hash(_selectedMap), checkIpNum); const DeepCollectionEquality().hash(_selectedMap));
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -331,20 +292,15 @@ class _$CheckIpSelectorStateImpl implements _CheckIpSelectorState {
abstract class _CheckIpSelectorState implements CheckIpSelectorState { abstract class _CheckIpSelectorState implements CheckIpSelectorState {
const factory _CheckIpSelectorState( const factory _CheckIpSelectorState(
{required final bool isInit, {required final String? currentProfileId,
required final bool isStart, required final Map<String, String> selectedMap}) =
required final Map<String, String> selectedMap, _$CheckIpSelectorStateImpl;
required final num checkIpNum}) = _$CheckIpSelectorStateImpl;
@override @override
bool get isInit; String? get currentProfileId;
@override
bool get isStart;
@override @override
Map<String, String> get selectedMap; Map<String, String> get selectedMap;
@override @override
num get checkIpNum;
@override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$CheckIpSelectorStateImplCopyWith<_$CheckIpSelectorStateImpl> _$$CheckIpSelectorStateImplCopyWith<_$CheckIpSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
@@ -502,7 +458,7 @@ abstract class _NetworkDetectionSelectorState
mixin _$ProfilesSelectorState { mixin _$ProfilesSelectorState {
List<Profile> get profiles => throw _privateConstructorUsedError; List<Profile> get profiles => throw _privateConstructorUsedError;
String? get currentProfileId => throw _privateConstructorUsedError; String? get currentProfileId => throw _privateConstructorUsedError;
ViewMode get viewMode => throw _privateConstructorUsedError; int get columns => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
$ProfilesSelectorStateCopyWith<ProfilesSelectorState> get copyWith => $ProfilesSelectorStateCopyWith<ProfilesSelectorState> get copyWith =>
@@ -515,8 +471,7 @@ abstract class $ProfilesSelectorStateCopyWith<$Res> {
$Res Function(ProfilesSelectorState) then) = $Res Function(ProfilesSelectorState) then) =
_$ProfilesSelectorStateCopyWithImpl<$Res, ProfilesSelectorState>; _$ProfilesSelectorStateCopyWithImpl<$Res, ProfilesSelectorState>;
@useResult @useResult
$Res call( $Res call({List<Profile> profiles, String? currentProfileId, int columns});
{List<Profile> profiles, String? currentProfileId, ViewMode viewMode});
} }
/// @nodoc /// @nodoc
@@ -535,7 +490,7 @@ class _$ProfilesSelectorStateCopyWithImpl<$Res,
$Res call({ $Res call({
Object? profiles = null, Object? profiles = null,
Object? currentProfileId = freezed, Object? currentProfileId = freezed,
Object? viewMode = null, Object? columns = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
profiles: null == profiles profiles: null == profiles
@@ -546,10 +501,10 @@ class _$ProfilesSelectorStateCopyWithImpl<$Res,
? _value.currentProfileId ? _value.currentProfileId
: currentProfileId // ignore: cast_nullable_to_non_nullable : currentProfileId // ignore: cast_nullable_to_non_nullable
as String?, as String?,
viewMode: null == viewMode columns: null == columns
? _value.viewMode ? _value.columns
: viewMode // ignore: cast_nullable_to_non_nullable : columns // ignore: cast_nullable_to_non_nullable
as ViewMode, as int,
) as $Val); ) as $Val);
} }
} }
@@ -563,8 +518,7 @@ abstract class _$$ProfilesSelectorStateImplCopyWith<$Res>
__$$ProfilesSelectorStateImplCopyWithImpl<$Res>; __$$ProfilesSelectorStateImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call( $Res call({List<Profile> profiles, String? currentProfileId, int columns});
{List<Profile> profiles, String? currentProfileId, ViewMode viewMode});
} }
/// @nodoc /// @nodoc
@@ -581,7 +535,7 @@ class __$$ProfilesSelectorStateImplCopyWithImpl<$Res>
$Res call({ $Res call({
Object? profiles = null, Object? profiles = null,
Object? currentProfileId = freezed, Object? currentProfileId = freezed,
Object? viewMode = null, Object? columns = null,
}) { }) {
return _then(_$ProfilesSelectorStateImpl( return _then(_$ProfilesSelectorStateImpl(
profiles: null == profiles profiles: null == profiles
@@ -592,10 +546,10 @@ class __$$ProfilesSelectorStateImplCopyWithImpl<$Res>
? _value.currentProfileId ? _value.currentProfileId
: currentProfileId // ignore: cast_nullable_to_non_nullable : currentProfileId // ignore: cast_nullable_to_non_nullable
as String?, as String?,
viewMode: null == viewMode columns: null == columns
? _value.viewMode ? _value.columns
: viewMode // ignore: cast_nullable_to_non_nullable : columns // ignore: cast_nullable_to_non_nullable
as ViewMode, as int,
)); ));
} }
} }
@@ -606,7 +560,7 @@ class _$ProfilesSelectorStateImpl implements _ProfilesSelectorState {
const _$ProfilesSelectorStateImpl( const _$ProfilesSelectorStateImpl(
{required final List<Profile> profiles, {required final List<Profile> profiles,
required this.currentProfileId, required this.currentProfileId,
required this.viewMode}) required this.columns})
: _profiles = profiles; : _profiles = profiles;
final List<Profile> _profiles; final List<Profile> _profiles;
@@ -620,11 +574,11 @@ class _$ProfilesSelectorStateImpl implements _ProfilesSelectorState {
@override @override
final String? currentProfileId; final String? currentProfileId;
@override @override
final ViewMode viewMode; final int columns;
@override @override
String toString() { String toString() {
return 'ProfilesSelectorState(profiles: $profiles, currentProfileId: $currentProfileId, viewMode: $viewMode)'; return 'ProfilesSelectorState(profiles: $profiles, currentProfileId: $currentProfileId, columns: $columns)';
} }
@override @override
@@ -635,8 +589,7 @@ class _$ProfilesSelectorStateImpl implements _ProfilesSelectorState {
const DeepCollectionEquality().equals(other._profiles, _profiles) && const DeepCollectionEquality().equals(other._profiles, _profiles) &&
(identical(other.currentProfileId, currentProfileId) || (identical(other.currentProfileId, currentProfileId) ||
other.currentProfileId == currentProfileId) && other.currentProfileId == currentProfileId) &&
(identical(other.viewMode, viewMode) || (identical(other.columns, columns) || other.columns == columns));
other.viewMode == viewMode));
} }
@override @override
@@ -644,7 +597,7 @@ class _$ProfilesSelectorStateImpl implements _ProfilesSelectorState {
runtimeType, runtimeType,
const DeepCollectionEquality().hash(_profiles), const DeepCollectionEquality().hash(_profiles),
currentProfileId, currentProfileId,
viewMode); columns);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -658,14 +611,14 @@ abstract class _ProfilesSelectorState implements ProfilesSelectorState {
const factory _ProfilesSelectorState( const factory _ProfilesSelectorState(
{required final List<Profile> profiles, {required final List<Profile> profiles,
required final String? currentProfileId, required final String? currentProfileId,
required final ViewMode viewMode}) = _$ProfilesSelectorStateImpl; required final int columns}) = _$ProfilesSelectorStateImpl;
@override @override
List<Profile> get profiles; List<Profile> get profiles;
@override @override
String? get currentProfileId; String? get currentProfileId;
@override @override
ViewMode get viewMode; int get columns;
@override @override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$ProfilesSelectorStateImplCopyWith<_$ProfilesSelectorStateImpl> _$$ProfilesSelectorStateImplCopyWith<_$ProfilesSelectorStateImpl>
@@ -677,6 +630,7 @@ mixin _$ApplicationSelectorState {
String? get locale => throw _privateConstructorUsedError; String? get locale => throw _privateConstructorUsedError;
ThemeMode? get themeMode => throw _privateConstructorUsedError; ThemeMode? get themeMode => throw _privateConstructorUsedError;
int? get primaryColor => throw _privateConstructorUsedError; int? get primaryColor => throw _privateConstructorUsedError;
bool get prueBlack => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
$ApplicationSelectorStateCopyWith<ApplicationSelectorState> get copyWith => $ApplicationSelectorStateCopyWith<ApplicationSelectorState> get copyWith =>
@@ -689,7 +643,11 @@ abstract class $ApplicationSelectorStateCopyWith<$Res> {
$Res Function(ApplicationSelectorState) then) = $Res Function(ApplicationSelectorState) then) =
_$ApplicationSelectorStateCopyWithImpl<$Res, ApplicationSelectorState>; _$ApplicationSelectorStateCopyWithImpl<$Res, ApplicationSelectorState>;
@useResult @useResult
$Res call({String? locale, ThemeMode? themeMode, int? primaryColor}); $Res call(
{String? locale,
ThemeMode? themeMode,
int? primaryColor,
bool prueBlack});
} }
/// @nodoc /// @nodoc
@@ -709,6 +667,7 @@ class _$ApplicationSelectorStateCopyWithImpl<$Res,
Object? locale = freezed, Object? locale = freezed,
Object? themeMode = freezed, Object? themeMode = freezed,
Object? primaryColor = freezed, Object? primaryColor = freezed,
Object? prueBlack = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
locale: freezed == locale locale: freezed == locale
@@ -723,6 +682,10 @@ class _$ApplicationSelectorStateCopyWithImpl<$Res,
? _value.primaryColor ? _value.primaryColor
: primaryColor // ignore: cast_nullable_to_non_nullable : primaryColor // ignore: cast_nullable_to_non_nullable
as int?, as int?,
prueBlack: null == prueBlack
? _value.prueBlack
: prueBlack // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val); ) as $Val);
} }
} }
@@ -736,7 +699,11 @@ abstract class _$$ApplicationSelectorStateImplCopyWith<$Res>
__$$ApplicationSelectorStateImplCopyWithImpl<$Res>; __$$ApplicationSelectorStateImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({String? locale, ThemeMode? themeMode, int? primaryColor}); $Res call(
{String? locale,
ThemeMode? themeMode,
int? primaryColor,
bool prueBlack});
} }
/// @nodoc /// @nodoc
@@ -755,6 +722,7 @@ class __$$ApplicationSelectorStateImplCopyWithImpl<$Res>
Object? locale = freezed, Object? locale = freezed,
Object? themeMode = freezed, Object? themeMode = freezed,
Object? primaryColor = freezed, Object? primaryColor = freezed,
Object? prueBlack = null,
}) { }) {
return _then(_$ApplicationSelectorStateImpl( return _then(_$ApplicationSelectorStateImpl(
locale: freezed == locale locale: freezed == locale
@@ -769,6 +737,10 @@ class __$$ApplicationSelectorStateImplCopyWithImpl<$Res>
? _value.primaryColor ? _value.primaryColor
: primaryColor // ignore: cast_nullable_to_non_nullable : primaryColor // ignore: cast_nullable_to_non_nullable
as int?, as int?,
prueBlack: null == prueBlack
? _value.prueBlack
: prueBlack // ignore: cast_nullable_to_non_nullable
as bool,
)); ));
} }
} }
@@ -777,7 +749,10 @@ class __$$ApplicationSelectorStateImplCopyWithImpl<$Res>
class _$ApplicationSelectorStateImpl implements _ApplicationSelectorState { class _$ApplicationSelectorStateImpl implements _ApplicationSelectorState {
const _$ApplicationSelectorStateImpl( const _$ApplicationSelectorStateImpl(
{this.locale, this.themeMode, this.primaryColor}); {required this.locale,
required this.themeMode,
required this.primaryColor,
required this.prueBlack});
@override @override
final String? locale; final String? locale;
@@ -785,10 +760,12 @@ class _$ApplicationSelectorStateImpl implements _ApplicationSelectorState {
final ThemeMode? themeMode; final ThemeMode? themeMode;
@override @override
final int? primaryColor; final int? primaryColor;
@override
final bool prueBlack;
@override @override
String toString() { String toString() {
return 'ApplicationSelectorState(locale: $locale, themeMode: $themeMode, primaryColor: $primaryColor)'; return 'ApplicationSelectorState(locale: $locale, themeMode: $themeMode, primaryColor: $primaryColor, prueBlack: $prueBlack)';
} }
@override @override
@@ -800,11 +777,14 @@ class _$ApplicationSelectorStateImpl implements _ApplicationSelectorState {
(identical(other.themeMode, themeMode) || (identical(other.themeMode, themeMode) ||
other.themeMode == themeMode) && other.themeMode == themeMode) &&
(identical(other.primaryColor, primaryColor) || (identical(other.primaryColor, primaryColor) ||
other.primaryColor == primaryColor)); other.primaryColor == primaryColor) &&
(identical(other.prueBlack, prueBlack) ||
other.prueBlack == prueBlack));
} }
@override @override
int get hashCode => Object.hash(runtimeType, locale, themeMode, primaryColor); int get hashCode =>
Object.hash(runtimeType, locale, themeMode, primaryColor, prueBlack);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -816,9 +796,10 @@ class _$ApplicationSelectorStateImpl implements _ApplicationSelectorState {
abstract class _ApplicationSelectorState implements ApplicationSelectorState { abstract class _ApplicationSelectorState implements ApplicationSelectorState {
const factory _ApplicationSelectorState( const factory _ApplicationSelectorState(
{final String? locale, {required final String? locale,
final ThemeMode? themeMode, required final ThemeMode? themeMode,
final int? primaryColor}) = _$ApplicationSelectorStateImpl; required final int? primaryColor,
required final bool prueBlack}) = _$ApplicationSelectorStateImpl;
@override @override
String? get locale; String? get locale;
@@ -827,6 +808,8 @@ abstract class _ApplicationSelectorState implements ApplicationSelectorState {
@override @override
int? get primaryColor; int? get primaryColor;
@override @override
bool get prueBlack;
@override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$ApplicationSelectorStateImplCopyWith<_$ApplicationSelectorStateImpl> _$$ApplicationSelectorStateImplCopyWith<_$ApplicationSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
@@ -2012,6 +1995,7 @@ mixin _$ProxyGroupSelectorState {
ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError; ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError;
ProxyCardType get proxyCardType => throw _privateConstructorUsedError; ProxyCardType get proxyCardType => throw _privateConstructorUsedError;
num get sortNum => throw _privateConstructorUsedError; num get sortNum => throw _privateConstructorUsedError;
GroupType get groupType => throw _privateConstructorUsedError;
List<Proxy> get proxies => throw _privateConstructorUsedError; List<Proxy> get proxies => throw _privateConstructorUsedError;
int get columns => throw _privateConstructorUsedError; int get columns => throw _privateConstructorUsedError;
@@ -2030,6 +2014,7 @@ abstract class $ProxyGroupSelectorStateCopyWith<$Res> {
{ProxiesSortType proxiesSortType, {ProxiesSortType proxiesSortType,
ProxyCardType proxyCardType, ProxyCardType proxyCardType,
num sortNum, num sortNum,
GroupType groupType,
List<Proxy> proxies, List<Proxy> proxies,
int columns}); int columns});
} }
@@ -2051,6 +2036,7 @@ class _$ProxyGroupSelectorStateCopyWithImpl<$Res,
Object? proxiesSortType = null, Object? proxiesSortType = null,
Object? proxyCardType = null, Object? proxyCardType = null,
Object? sortNum = null, Object? sortNum = null,
Object? groupType = null,
Object? proxies = null, Object? proxies = null,
Object? columns = null, Object? columns = null,
}) { }) {
@@ -2067,6 +2053,10 @@ class _$ProxyGroupSelectorStateCopyWithImpl<$Res,
? _value.sortNum ? _value.sortNum
: sortNum // ignore: cast_nullable_to_non_nullable : sortNum // ignore: cast_nullable_to_non_nullable
as num, as num,
groupType: null == groupType
? _value.groupType
: groupType // ignore: cast_nullable_to_non_nullable
as GroupType,
proxies: null == proxies proxies: null == proxies
? _value.proxies ? _value.proxies
: proxies // ignore: cast_nullable_to_non_nullable : proxies // ignore: cast_nullable_to_non_nullable
@@ -2092,6 +2082,7 @@ abstract class _$$ProxyGroupSelectorStateImplCopyWith<$Res>
{ProxiesSortType proxiesSortType, {ProxiesSortType proxiesSortType,
ProxyCardType proxyCardType, ProxyCardType proxyCardType,
num sortNum, num sortNum,
GroupType groupType,
List<Proxy> proxies, List<Proxy> proxies,
int columns}); int columns});
} }
@@ -2112,6 +2103,7 @@ class __$$ProxyGroupSelectorStateImplCopyWithImpl<$Res>
Object? proxiesSortType = null, Object? proxiesSortType = null,
Object? proxyCardType = null, Object? proxyCardType = null,
Object? sortNum = null, Object? sortNum = null,
Object? groupType = null,
Object? proxies = null, Object? proxies = null,
Object? columns = null, Object? columns = null,
}) { }) {
@@ -2128,6 +2120,10 @@ class __$$ProxyGroupSelectorStateImplCopyWithImpl<$Res>
? _value.sortNum ? _value.sortNum
: sortNum // ignore: cast_nullable_to_non_nullable : sortNum // ignore: cast_nullable_to_non_nullable
as num, as num,
groupType: null == groupType
? _value.groupType
: groupType // ignore: cast_nullable_to_non_nullable
as GroupType,
proxies: null == proxies proxies: null == proxies
? _value._proxies ? _value._proxies
: proxies // ignore: cast_nullable_to_non_nullable : proxies // ignore: cast_nullable_to_non_nullable
@@ -2147,6 +2143,7 @@ class _$ProxyGroupSelectorStateImpl implements _ProxyGroupSelectorState {
{required this.proxiesSortType, {required this.proxiesSortType,
required this.proxyCardType, required this.proxyCardType,
required this.sortNum, required this.sortNum,
required this.groupType,
required final List<Proxy> proxies, required final List<Proxy> proxies,
required this.columns}) required this.columns})
: _proxies = proxies; : _proxies = proxies;
@@ -2157,6 +2154,8 @@ class _$ProxyGroupSelectorStateImpl implements _ProxyGroupSelectorState {
final ProxyCardType proxyCardType; final ProxyCardType proxyCardType;
@override @override
final num sortNum; final num sortNum;
@override
final GroupType groupType;
final List<Proxy> _proxies; final List<Proxy> _proxies;
@override @override
List<Proxy> get proxies { List<Proxy> get proxies {
@@ -2170,7 +2169,7 @@ class _$ProxyGroupSelectorStateImpl implements _ProxyGroupSelectorState {
@override @override
String toString() { String toString() {
return 'ProxyGroupSelectorState(proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, proxies: $proxies, columns: $columns)'; return 'ProxyGroupSelectorState(proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, groupType: $groupType, proxies: $proxies, columns: $columns)';
} }
@override @override
@@ -2183,13 +2182,21 @@ class _$ProxyGroupSelectorStateImpl implements _ProxyGroupSelectorState {
(identical(other.proxyCardType, proxyCardType) || (identical(other.proxyCardType, proxyCardType) ||
other.proxyCardType == proxyCardType) && other.proxyCardType == proxyCardType) &&
(identical(other.sortNum, sortNum) || other.sortNum == sortNum) && (identical(other.sortNum, sortNum) || other.sortNum == sortNum) &&
(identical(other.groupType, groupType) ||
other.groupType == groupType) &&
const DeepCollectionEquality().equals(other._proxies, _proxies) && const DeepCollectionEquality().equals(other._proxies, _proxies) &&
(identical(other.columns, columns) || other.columns == columns)); (identical(other.columns, columns) || other.columns == columns));
} }
@override @override
int get hashCode => Object.hash(runtimeType, proxiesSortType, proxyCardType, int get hashCode => Object.hash(
sortNum, const DeepCollectionEquality().hash(_proxies), columns); runtimeType,
proxiesSortType,
proxyCardType,
sortNum,
groupType,
const DeepCollectionEquality().hash(_proxies),
columns);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -2204,6 +2211,7 @@ abstract class _ProxyGroupSelectorState implements ProxyGroupSelectorState {
{required final ProxiesSortType proxiesSortType, {required final ProxiesSortType proxiesSortType,
required final ProxyCardType proxyCardType, required final ProxyCardType proxyCardType,
required final num sortNum, required final num sortNum,
required final GroupType groupType,
required final List<Proxy> proxies, required final List<Proxy> proxies,
required final int columns}) = _$ProxyGroupSelectorStateImpl; required final int columns}) = _$ProxyGroupSelectorStateImpl;
@@ -2214,6 +2222,8 @@ abstract class _ProxyGroupSelectorState implements ProxyGroupSelectorState {
@override @override
num get sortNum; num get sortNum;
@override @override
GroupType get groupType;
@override
List<Proxy> get proxies; List<Proxy> get proxies;
@override @override
int get columns; int get columns;
@@ -2359,6 +2369,7 @@ abstract class _MoreToolsSelectorState implements MoreToolsSelectorState {
/// @nodoc /// @nodoc
mixin _$PackageListSelectorState { mixin _$PackageListSelectorState {
List<Package> get packages => throw _privateConstructorUsedError;
AccessControl get accessControl => throw _privateConstructorUsedError; AccessControl get accessControl => throw _privateConstructorUsedError;
bool get isAccessControl => throw _privateConstructorUsedError; bool get isAccessControl => throw _privateConstructorUsedError;
@@ -2373,7 +2384,10 @@ abstract class $PackageListSelectorStateCopyWith<$Res> {
$Res Function(PackageListSelectorState) then) = $Res Function(PackageListSelectorState) then) =
_$PackageListSelectorStateCopyWithImpl<$Res, PackageListSelectorState>; _$PackageListSelectorStateCopyWithImpl<$Res, PackageListSelectorState>;
@useResult @useResult
$Res call({AccessControl accessControl, bool isAccessControl}); $Res call(
{List<Package> packages,
AccessControl accessControl,
bool isAccessControl});
$AccessControlCopyWith<$Res> get accessControl; $AccessControlCopyWith<$Res> get accessControl;
} }
@@ -2392,10 +2406,15 @@ class _$PackageListSelectorStateCopyWithImpl<$Res,
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? packages = null,
Object? accessControl = null, Object? accessControl = null,
Object? isAccessControl = null, Object? isAccessControl = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
packages: null == packages
? _value.packages
: packages // ignore: cast_nullable_to_non_nullable
as List<Package>,
accessControl: null == accessControl accessControl: null == accessControl
? _value.accessControl ? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable : accessControl // ignore: cast_nullable_to_non_nullable
@@ -2425,7 +2444,10 @@ abstract class _$$PackageListSelectorStateImplCopyWith<$Res>
__$$PackageListSelectorStateImplCopyWithImpl<$Res>; __$$PackageListSelectorStateImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({AccessControl accessControl, bool isAccessControl}); $Res call(
{List<Package> packages,
AccessControl accessControl,
bool isAccessControl});
@override @override
$AccessControlCopyWith<$Res> get accessControl; $AccessControlCopyWith<$Res> get accessControl;
@@ -2444,10 +2466,15 @@ class __$$PackageListSelectorStateImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? packages = null,
Object? accessControl = null, Object? accessControl = null,
Object? isAccessControl = null, Object? isAccessControl = null,
}) { }) {
return _then(_$PackageListSelectorStateImpl( return _then(_$PackageListSelectorStateImpl(
packages: null == packages
? _value._packages
: packages // ignore: cast_nullable_to_non_nullable
as List<Package>,
accessControl: null == accessControl accessControl: null == accessControl
? _value.accessControl ? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable : accessControl // ignore: cast_nullable_to_non_nullable
@@ -2464,7 +2491,18 @@ class __$$PackageListSelectorStateImplCopyWithImpl<$Res>
class _$PackageListSelectorStateImpl implements _PackageListSelectorState { class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
const _$PackageListSelectorStateImpl( const _$PackageListSelectorStateImpl(
{required this.accessControl, required this.isAccessControl}); {required final List<Package> packages,
required this.accessControl,
required this.isAccessControl})
: _packages = packages;
final List<Package> _packages;
@override
List<Package> get packages {
if (_packages is EqualUnmodifiableListView) return _packages;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_packages);
}
@override @override
final AccessControl accessControl; final AccessControl accessControl;
@@ -2473,7 +2511,7 @@ class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
@override @override
String toString() { String toString() {
return 'PackageListSelectorState(accessControl: $accessControl, isAccessControl: $isAccessControl)'; return 'PackageListSelectorState(packages: $packages, accessControl: $accessControl, isAccessControl: $isAccessControl)';
} }
@override @override
@@ -2481,6 +2519,7 @@ class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$PackageListSelectorStateImpl && other is _$PackageListSelectorStateImpl &&
const DeepCollectionEquality().equals(other._packages, _packages) &&
(identical(other.accessControl, accessControl) || (identical(other.accessControl, accessControl) ||
other.accessControl == accessControl) && other.accessControl == accessControl) &&
(identical(other.isAccessControl, isAccessControl) || (identical(other.isAccessControl, isAccessControl) ||
@@ -2488,7 +2527,11 @@ class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
} }
@override @override
int get hashCode => Object.hash(runtimeType, accessControl, isAccessControl); int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_packages),
accessControl,
isAccessControl);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -2500,9 +2543,12 @@ class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
abstract class _PackageListSelectorState implements PackageListSelectorState { abstract class _PackageListSelectorState implements PackageListSelectorState {
const factory _PackageListSelectorState( const factory _PackageListSelectorState(
{required final AccessControl accessControl, {required final List<Package> packages,
required final AccessControl accessControl,
required final bool isAccessControl}) = _$PackageListSelectorStateImpl; required final bool isAccessControl}) = _$PackageListSelectorStateImpl;
@override
List<Package> get packages;
@override @override
AccessControl get accessControl; AccessControl get accessControl;
@override @override
@@ -2513,146 +2559,6 @@ abstract class _PackageListSelectorState implements PackageListSelectorState {
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
} }
/// @nodoc
mixin _$ColumnsSelectorState {
int get columns => throw _privateConstructorUsedError;
ViewMode get viewMode => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ColumnsSelectorStateCopyWith<ColumnsSelectorState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ColumnsSelectorStateCopyWith<$Res> {
factory $ColumnsSelectorStateCopyWith(ColumnsSelectorState value,
$Res Function(ColumnsSelectorState) then) =
_$ColumnsSelectorStateCopyWithImpl<$Res, ColumnsSelectorState>;
@useResult
$Res call({int columns, ViewMode viewMode});
}
/// @nodoc
class _$ColumnsSelectorStateCopyWithImpl<$Res,
$Val extends ColumnsSelectorState>
implements $ColumnsSelectorStateCopyWith<$Res> {
_$ColumnsSelectorStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? columns = null,
Object? viewMode = null,
}) {
return _then(_value.copyWith(
columns: null == columns
? _value.columns
: columns // ignore: cast_nullable_to_non_nullable
as int,
viewMode: null == viewMode
? _value.viewMode
: viewMode // ignore: cast_nullable_to_non_nullable
as ViewMode,
) as $Val);
}
}
/// @nodoc
abstract class _$$ColumnsSelectorStateImplCopyWith<$Res>
implements $ColumnsSelectorStateCopyWith<$Res> {
factory _$$ColumnsSelectorStateImplCopyWith(_$ColumnsSelectorStateImpl value,
$Res Function(_$ColumnsSelectorStateImpl) then) =
__$$ColumnsSelectorStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({int columns, ViewMode viewMode});
}
/// @nodoc
class __$$ColumnsSelectorStateImplCopyWithImpl<$Res>
extends _$ColumnsSelectorStateCopyWithImpl<$Res, _$ColumnsSelectorStateImpl>
implements _$$ColumnsSelectorStateImplCopyWith<$Res> {
__$$ColumnsSelectorStateImplCopyWithImpl(_$ColumnsSelectorStateImpl _value,
$Res Function(_$ColumnsSelectorStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? columns = null,
Object? viewMode = null,
}) {
return _then(_$ColumnsSelectorStateImpl(
columns: null == columns
? _value.columns
: columns // ignore: cast_nullable_to_non_nullable
as int,
viewMode: null == viewMode
? _value.viewMode
: viewMode // ignore: cast_nullable_to_non_nullable
as ViewMode,
));
}
}
/// @nodoc
class _$ColumnsSelectorStateImpl implements _ColumnsSelectorState {
const _$ColumnsSelectorStateImpl(
{required this.columns, required this.viewMode});
@override
final int columns;
@override
final ViewMode viewMode;
@override
String toString() {
return 'ColumnsSelectorState(columns: $columns, viewMode: $viewMode)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ColumnsSelectorStateImpl &&
(identical(other.columns, columns) || other.columns == columns) &&
(identical(other.viewMode, viewMode) ||
other.viewMode == viewMode));
}
@override
int get hashCode => Object.hash(runtimeType, columns, viewMode);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ColumnsSelectorStateImplCopyWith<_$ColumnsSelectorStateImpl>
get copyWith =>
__$$ColumnsSelectorStateImplCopyWithImpl<_$ColumnsSelectorStateImpl>(
this, _$identity);
}
abstract class _ColumnsSelectorState implements ColumnsSelectorState {
const factory _ColumnsSelectorState(
{required final int columns,
required final ViewMode viewMode}) = _$ColumnsSelectorStateImpl;
@override
int get columns;
@override
ViewMode get viewMode;
@override
@JsonKey(ignore: true)
_$$ColumnsSelectorStateImplCopyWith<_$ColumnsSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc /// @nodoc
mixin _$ProxiesListHeaderSelectorState { mixin _$ProxiesListHeaderSelectorState {
double get offset => throw _privateConstructorUsedError; double get offset => throw _privateConstructorUsedError;
@@ -2800,3 +2706,142 @@ abstract class _ProxiesListHeaderSelectorState
_$ProxiesListHeaderSelectorStateImpl> _$ProxiesListHeaderSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
} }
/// @nodoc
mixin _$ProxiesActionsState {
bool get isCurrent => throw _privateConstructorUsedError;
bool get hasProvider => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ProxiesActionsStateCopyWith<ProxiesActionsState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProxiesActionsStateCopyWith<$Res> {
factory $ProxiesActionsStateCopyWith(
ProxiesActionsState value, $Res Function(ProxiesActionsState) then) =
_$ProxiesActionsStateCopyWithImpl<$Res, ProxiesActionsState>;
@useResult
$Res call({bool isCurrent, bool hasProvider});
}
/// @nodoc
class _$ProxiesActionsStateCopyWithImpl<$Res, $Val extends ProxiesActionsState>
implements $ProxiesActionsStateCopyWith<$Res> {
_$ProxiesActionsStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isCurrent = null,
Object? hasProvider = null,
}) {
return _then(_value.copyWith(
isCurrent: null == isCurrent
? _value.isCurrent
: isCurrent // ignore: cast_nullable_to_non_nullable
as bool,
hasProvider: null == hasProvider
? _value.hasProvider
: hasProvider // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
/// @nodoc
abstract class _$$ProxiesActionsStateImplCopyWith<$Res>
implements $ProxiesActionsStateCopyWith<$Res> {
factory _$$ProxiesActionsStateImplCopyWith(_$ProxiesActionsStateImpl value,
$Res Function(_$ProxiesActionsStateImpl) then) =
__$$ProxiesActionsStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool isCurrent, bool hasProvider});
}
/// @nodoc
class __$$ProxiesActionsStateImplCopyWithImpl<$Res>
extends _$ProxiesActionsStateCopyWithImpl<$Res, _$ProxiesActionsStateImpl>
implements _$$ProxiesActionsStateImplCopyWith<$Res> {
__$$ProxiesActionsStateImplCopyWithImpl(_$ProxiesActionsStateImpl _value,
$Res Function(_$ProxiesActionsStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isCurrent = null,
Object? hasProvider = null,
}) {
return _then(_$ProxiesActionsStateImpl(
isCurrent: null == isCurrent
? _value.isCurrent
: isCurrent // ignore: cast_nullable_to_non_nullable
as bool,
hasProvider: null == hasProvider
? _value.hasProvider
: hasProvider // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
class _$ProxiesActionsStateImpl implements _ProxiesActionsState {
const _$ProxiesActionsStateImpl(
{required this.isCurrent, required this.hasProvider});
@override
final bool isCurrent;
@override
final bool hasProvider;
@override
String toString() {
return 'ProxiesActionsState(isCurrent: $isCurrent, hasProvider: $hasProvider)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProxiesActionsStateImpl &&
(identical(other.isCurrent, isCurrent) ||
other.isCurrent == isCurrent) &&
(identical(other.hasProvider, hasProvider) ||
other.hasProvider == hasProvider));
}
@override
int get hashCode => Object.hash(runtimeType, isCurrent, hasProvider);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ProxiesActionsStateImplCopyWith<_$ProxiesActionsStateImpl> get copyWith =>
__$$ProxiesActionsStateImplCopyWithImpl<_$ProxiesActionsStateImpl>(
this, _$identity);
}
abstract class _ProxiesActionsState implements ProxiesActionsState {
const factory _ProxiesActionsState(
{required final bool isCurrent,
required final bool hasProvider}) = _$ProxiesActionsStateImpl;
@override
bool get isCurrent;
@override
bool get hasProvider;
@override
@JsonKey(ignore: true)
_$$ProxiesActionsStateImplCopyWith<_$ProxiesActionsStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -13,4 +13,5 @@ export 'ffi.dart';
export 'selector.dart'; export 'selector.dart';
export 'navigation.dart'; export 'navigation.dart';
export 'dav.dart'; export 'dav.dart';
export 'ip.dart'; export 'ip.dart';
export 'file.dart';

View File

@@ -9,6 +9,7 @@ class Package with _$Package {
required String packageName, required String packageName,
required String label, required String label,
required bool isSystem, required bool isSystem,
required int firstInstallTime,
}) = _Package; }) = _Package;
factory Package.fromJson(Map<String, Object?> json) => factory Package.fromJson(Map<String, Object?> json) =>

View File

@@ -1,3 +1,4 @@
// ignore_for_file: invalid_annotation_target
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
@@ -55,6 +56,9 @@ class Profile with _$Profile {
@Default(true) bool autoUpdate, @Default(true) bool autoUpdate,
@Default({}) SelectedMap selectedMap, @Default({}) SelectedMap selectedMap,
@Default({}) Set<String> unfoldSet, @Default({}) Set<String> unfoldSet,
@JsonKey(includeToJson: false, includeFromJson: false)
@Default(false)
bool isUpdating,
}) = _Profile; }) = _Profile;
factory Profile.fromJson(Map<String, Object?> json) => factory Profile.fromJson(Map<String, Object?> json) =>
@@ -63,7 +67,7 @@ class Profile with _$Profile {
factory Profile.normal({ factory Profile.normal({
String? label, String? label,
String url = '', String url = '',
}) { }) {
return Profile( return Profile(
label: label, label: label,
url: url, url: url,
@@ -77,8 +81,7 @@ extension ProfileExtension on Profile {
ProfileType get type => ProfileType get type =>
url.isEmpty == true ? ProfileType.file : ProfileType.url; url.isEmpty == true ? ProfileType.file : ProfileType.url;
bool get realAutoUpdate => bool get realAutoUpdate => url.isEmpty == true ? false : autoUpdate;
url.isEmpty == true ? false : autoUpdate;
Future<void> checkAndUpdate() async { Future<void> checkAndUpdate() async {
final isExists = await check(); final isExists = await check();

View File

@@ -14,12 +14,24 @@ class Group with _$Group {
required GroupType type, required GroupType type,
@Default([]) List<Proxy> all, @Default([]) List<Proxy> all,
String? now, String? now,
bool? hidden,
required String name, required String name,
}) = _Group; }) = _Group;
factory Group.fromJson(Map<String, Object?> json) => _$GroupFromJson(json); factory Group.fromJson(Map<String, Object?> json) => _$GroupFromJson(json);
} }
extension GroupExt on Group {
String get realNow => now ?? "";
String getCurrentSelectedName(String proxyName) {
if (type == GroupType.URLTest) {
return realNow.isNotEmpty ? realNow : proxyName;
}
return proxyName.isNotEmpty ? proxyName : realNow;
}
}
@freezed @freezed
class Proxy with _$Proxy { class Proxy with _$Proxy {
const factory Proxy({ const factory Proxy({
@@ -29,4 +41,4 @@ class Proxy with _$Proxy {
}) = _Proxy; }) = _Proxy;
factory Proxy.fromJson(Map<String, Object?> json) => _$ProxyFromJson(json); factory Proxy.fromJson(Map<String, Object?> json) => _$ProxyFromJson(json);
} }

View File

@@ -1,7 +1,10 @@
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:lpinyin/lpinyin.dart';
part 'generated/selector.freezed.dart'; part 'generated/selector.freezed.dart';
@@ -16,10 +19,8 @@ class StartButtonSelectorState with _$StartButtonSelectorState {
@freezed @freezed
class CheckIpSelectorState with _$CheckIpSelectorState { class CheckIpSelectorState with _$CheckIpSelectorState {
const factory CheckIpSelectorState({ const factory CheckIpSelectorState({
required bool isInit, required String? currentProfileId,
required bool isStart,
required SelectedMap selectedMap, required SelectedMap selectedMap,
required num checkIpNum
}) = _CheckIpSelectorState; }) = _CheckIpSelectorState;
} }
@@ -36,16 +37,17 @@ class ProfilesSelectorState with _$ProfilesSelectorState {
const factory ProfilesSelectorState({ const factory ProfilesSelectorState({
required List<Profile> profiles, required List<Profile> profiles,
required String? currentProfileId, required String? currentProfileId,
required ViewMode viewMode, required int columns,
}) = _ProfilesSelectorState; }) = _ProfilesSelectorState;
} }
@freezed @freezed
class ApplicationSelectorState with _$ApplicationSelectorState { class ApplicationSelectorState with _$ApplicationSelectorState {
const factory ApplicationSelectorState({ const factory ApplicationSelectorState({
String? locale, required String? locale,
ThemeMode? themeMode, required ThemeMode? themeMode,
int? primaryColor, required int? primaryColor,
required bool prueBlack,
}) = _ApplicationSelectorState; }) = _ApplicationSelectorState;
} }
@@ -117,6 +119,7 @@ class ProxyGroupSelectorState with _$ProxyGroupSelectorState {
required ProxiesSortType proxiesSortType, required ProxiesSortType proxiesSortType,
required ProxyCardType proxyCardType, required ProxyCardType proxyCardType,
required num sortNum, required num sortNum,
required GroupType groupType,
required List<Proxy> proxies, required List<Proxy> proxies,
required int columns, required int columns,
}) = _ProxyGroupSelectorState; }) = _ProxyGroupSelectorState;
@@ -132,18 +135,41 @@ class MoreToolsSelectorState with _$MoreToolsSelectorState {
@freezed @freezed
class PackageListSelectorState with _$PackageListSelectorState { class PackageListSelectorState with _$PackageListSelectorState {
const factory PackageListSelectorState({ const factory PackageListSelectorState({
required List<Package> packages,
required AccessControl accessControl, required AccessControl accessControl,
required bool isAccessControl, required bool isAccessControl,
}) = _PackageListSelectorState; }) = _PackageListSelectorState;
} }
extension PackageListSelectorStateExt on PackageListSelectorState {
@freezed List<Package> getList(List<String> selectedList) {
class ColumnsSelectorState with _$ColumnsSelectorState { final isFilterSystemApp = accessControl.isFilterSystemApp;
const factory ColumnsSelectorState({ final sort = accessControl.sort;
required int columns, return packages
required ViewMode viewMode, .where((item) => isFilterSystemApp ? item.isSystem == false : true)
}) = _ColumnsSelectorState; .sorted(
(a, b) {
return switch (sort) {
AccessSortType.none => 0,
AccessSortType.name =>
other.sortByChar(
PinyinHelper.getPinyin(a.label),
PinyinHelper.getPinyin(b.label),
),
AccessSortType.time => a.firstInstallTime.compareTo(b.firstInstallTime),
};
},
).sorted(
(a, b) {
final isSelectA = selectedList.contains(a.packageName);
final isSelectB = selectedList.contains(b.packageName);
if (isSelectA && isSelectB) return 0;
if (isSelectA) return -1;
if (isSelectB) return 1;
return 0;
},
);
}
} }
@freezed @freezed
@@ -152,4 +178,12 @@ class ProxiesListHeaderSelectorState with _$ProxiesListHeaderSelectorState {
required double offset, required double offset,
required int currentIndex, required int currentIndex,
}) = _ProxiesListHeaderSelectorState; }) = _ProxiesListHeaderSelectorState;
} }
@freezed
class ProxiesActionsState with _$ProxiesActionsState {
const factory ProxiesActionsState({
required bool isCurrent,
required bool hasProvider,
}) = _ProxiesActionsState;
}

View File

@@ -12,15 +12,15 @@ class SystemColorSchemes {
}); });
getSystemColorSchemeForBrightness(Brightness? brightness) { getSystemColorSchemeForBrightness(Brightness? brightness) {
if (brightness != null && brightness == Brightness.dark) { if (brightness == Brightness.dark) {
return darkColorScheme != null return darkColorScheme != null
? ColorScheme.fromSeed( ? ColorScheme.fromSeed(
seedColor: darkColorScheme!.primary, seedColor: darkColorScheme!.primary,
brightness: brightness, brightness: Brightness.dark,
) )
: ColorScheme.fromSeed( : ColorScheme.fromSeed(
seedColor: defaultPrimaryColor, seedColor: defaultPrimaryColor,
brightness: brightness, brightness: Brightness.dark,
); );
} }
return lightColorScheme != null return lightColorScheme != null

View File

@@ -52,6 +52,18 @@ class HomePage extends StatelessWidget {
context: context, context: context,
child: NavigationRail( child: NavigationRail(
groupAlignment: -0.8, groupAlignment: -0.8,
selectedIconTheme: IconThemeData(
color: context.colorScheme.onSurfaceVariant,
),
unselectedIconTheme: IconThemeData(
color: context.colorScheme.onSurfaceVariant,
),
selectedLabelTextStyle: context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
unselectedLabelTextStyle: context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
destinations: navigationItems destinations: navigationItems
.map( .map(
(e) => NavigationRailDestination( (e) => NavigationRailDestination(
@@ -64,7 +76,7 @@ class HomePage extends StatelessWidget {
.toList(), .toList(),
onDestinationSelected: globalState.appController.toPage, onDestinationSelected: globalState.appController.toPage,
extended: extended, extended: extended,
minExtendedWidth: 172, minExtendedWidth: 200,
selectedIndex: currentIndex, selectedIndex: currentIndex,
labelType: extended labelType: extended
? NavigationRailLabelType.none ? NavigationRailLabelType.none

View File

@@ -5,10 +5,8 @@ import 'dart:isolate';
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
class App { class App {
static App? _instance; static App? _instance;
@@ -50,6 +48,23 @@ class App {
}); });
} }
Future<List<String>> getChinaPackageNames() async {
final packageNamesString =
await methodChannel.invokeMethod<String>("getChinaPackageNames");
return Isolate.run<List<String>>(() {
final List<dynamic> packageNamesRaw =
packageNamesString != null ? json.decode(packageNamesString) : [];
return packageNamesRaw.map((e) => e.toString()).toList();
});
}
Future<bool> openFile(String path) async {
return await methodChannel.invokeMethod<bool>("openFile", {
"path": path,
}) ??
false;
}
Future<ImageProvider?> getPackageIcon(String packageName) async { Future<ImageProvider?> getPackageIcon(String packageName) async {
final base64 = await methodChannel.invokeMethod<String>("getPackageIcon", { final base64 = await methodChannel.invokeMethod<String>("getPackageIcon", {
"packageName": packageName, "packageName": packageName,

View File

@@ -47,9 +47,10 @@ class Proxy extends ProxyPlatform {
@override @override
Future<bool?> startProxy(port) async { Future<bool?> startProxy(port) async {
final state = clashCore.getState();
return await methodChannel.invokeMethod<bool>("startProxy", { return await methodChannel.invokeMethod<bool>("startProxy", {
'port': port, 'port': state.mixedPort,
'args': json.encode(clashCore.getProps()), 'args': json.encode(state),
}); });
} }
@@ -80,7 +81,6 @@ class Proxy extends ProxyPlatform {
bool get isStart => startTime != null && startTime!.isBeforeNow; bool get isStart => startTime != null && startTime!.isBeforeNow;
onStarted(int? fd) { onStarted(int? fd) {
debugPrint("onStarted ==> $fd");
if (fd == null) return; if (fd == null) return;
if (receiver != null) { if (receiver != null) {
receiver!.close(); receiver!.close();

View File

@@ -45,11 +45,10 @@ class GlobalState {
required Config config, required Config config,
bool isPatch = true, bool isPatch = true,
}) async { }) async {
final profilePath = await appPath.getProfilePath(config.currentProfileId);
await config.currentProfile?.checkAndUpdate(); await config.currentProfile?.checkAndUpdate();
final res = await clashCore.updateConfig( final res = await clashCore.updateConfig(
UpdateConfigParams( UpdateConfigParams(
profilePath: profilePath, profileId: config.currentProfileId ?? "",
config: clashConfig, config: clashConfig,
params: ConfigExtendedParams( params: ConfigExtendedParams(
isPatch: isPatch, isPatch: isPatch,
@@ -72,13 +71,6 @@ class GlobalState {
required ClashConfig clashConfig, required ClashConfig clashConfig,
}) async { }) async {
if (!globalState.isVpnService && Platform.isAndroid) { if (!globalState.isVpnService && Platform.isAndroid) {
clashCore.setProps(
Props(
accessControl: config.isAccessControl ? config.accessControl : null,
allowBypass: config.allowBypass,
systemProxy: config.systemProxy,
),
);
await proxy?.initService(); await proxy?.initService();
} else { } else {
await proxyManager.startProxy( await proxyManager.startProxy(
@@ -86,16 +78,6 @@ class GlobalState {
); );
} }
startListenUpdate(); startListenUpdate();
if (Platform.isAndroid) {
return;
}
applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
).then((_) {
globalState.appController.addCheckIpNumDebounce();
});
} }
Future<void> stopSystemProxy() async { Future<void> stopSystemProxy() async {
@@ -113,8 +95,12 @@ class GlobalState {
config: config, config: config,
isPatch: false, isPatch: false,
); );
clashCore.setProfileName(config.currentProfile?.label ?? '');
await updateGroups(appState); await updateGroups(appState);
await updateProviders(appState);
}
updateProviders(AppState appState) async {
appState.providers = await clashCore.getExternalProviders();
} }
init({ init({
@@ -124,19 +110,20 @@ class GlobalState {
}) async { }) async {
appState.isInit = clashCore.isInit; appState.isInit = clashCore.isInit;
if (!appState.isInit) { if (!appState.isInit) {
if (Platform.isAndroid) {
clashCore.setProps(
Props(
accessControl: config.isAccessControl ? config.accessControl : null,
allowBypass: config.allowBypass,
systemProxy: config.systemProxy,
),
);
}
appState.isInit = await clashService.init( appState.isInit = await clashService.init(
config: config, config: config,
clashConfig: clashConfig, clashConfig: clashConfig,
); );
clashCore.setState(
CoreState(
accessControl: config.isAccessControl ? config.accessControl : null,
allowBypass: config.allowBypass,
systemProxy: config.systemProxy,
mixedPort: clashConfig.mixedPort,
onlyProxy: config.onlyProxy,
currentProfileName: config.currentProfile?.label ?? config.currentProfileId ?? "",
),
);
} }
updateCoreVersionInfo(appState); updateCoreVersionInfo(appState);
} }
@@ -160,12 +147,14 @@ class GlobalState {
width: 300, width: 300,
constraints: const BoxConstraints(maxHeight: 200), constraints: const BoxConstraints(maxHeight: 200),
child: SingleChildScrollView( child: SingleChildScrollView(
child: RichText( child: SelectableText.rich(
overflow: TextOverflow.visible, TextSpan(
text: TextSpan(
style: Theme.of(context).textTheme.labelLarge, style: Theme.of(context).textTheme.labelLarge,
children: [message], children: [message],
), ),
style: const TextStyle(
overflow: TextOverflow.visible,
),
), ),
), ),
), ),
@@ -195,7 +184,7 @@ class GlobalState {
proxyName: proxyName, proxyName: proxyName,
), ),
); );
if(config.isCloseConnections){ if (config.isCloseConnections) {
clashCore.closeConnections(); clashCore.closeConnections();
} }
} }
@@ -219,7 +208,7 @@ class GlobalState {
final traffic = clashCore.getTraffic(); final traffic = clashCore.getTraffic();
if (Platform.isAndroid && isVpnService == true) { if (Platform.isAndroid && isVpnService == true) {
proxy?.startForeground( proxy?.startForeground(
title: clashCore.getProfileName(), title: clashCore.getState().currentProfileName,
content: "$traffic", content: "$traffic",
); );
} else { } else {

View File

@@ -19,7 +19,6 @@ class AndroidContainer extends StatefulWidget {
class _AndroidContainerState extends State<AndroidContainer> class _AndroidContainerState extends State<AndroidContainer>
with WidgetsBindingObserver { with WidgetsBindingObserver {
Widget _excludeContainer(Widget child) { Widget _excludeContainer(Widget child) {
return Selector<Config, bool>( return Selector<Config, bool>(
selector: (_, config) => config.isExclude, selector: (_, config) => config.isExclude,

67
lib/widgets/builder.dart Normal file
View File

@@ -0,0 +1,67 @@
import 'package:fl_clash/models/models.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ScrollOverBuilder extends StatefulWidget {
final Widget Function(bool isOver) builder;
const ScrollOverBuilder({
super.key,
required this.builder,
});
@override
State<ScrollOverBuilder> createState() => _ScrollOverBuilderState();
}
class _ScrollOverBuilderState extends State<ScrollOverBuilder> {
final isOverNotifier = ValueNotifier<bool>(false);
@override
void dispose() {
super.dispose();
isOverNotifier.dispose();
}
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollMetricsNotification>(
onNotification: (scrollNotification) {
isOverNotifier.value = scrollNotification.metrics.maxScrollExtent > 0;
return true;
},
child: ValueListenableBuilder<bool>(
valueListenable: isOverNotifier,
builder: (_, isOver, __) {
return widget.builder(isOver);
},
),
);
}
}
class ProxiesActionsBuilder extends StatelessWidget {
final Widget? child;
final Widget Function(
ProxiesActionsState state,
Widget? child,
) builder;
const ProxiesActionsBuilder({
super.key,
required this.child,
required this.builder,
});
@override
Widget build(BuildContext context) {
return Selector<AppState, ProxiesActionsState>(
selector: (_, appState) => ProxiesActionsState(
isCurrent: appState.currentLabel == "proxies",
hasProvider: appState.providers.isNotEmpty,
),
builder: (_, state, child) => builder(state, child),
child: child,
);
}
}

View File

@@ -32,46 +32,39 @@ class InfoHeader extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (info.iconData != null) ...[
Icon(
info.iconData,
color: Theme
.of(context)
.colorScheme
.primary,
),
const SizedBox(
width: 8,
),
],
Flexible(
child: TooltipText(
text: Text(
info.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme
.of(context)
.textTheme
.titleMedium,
),
),
),
],
),
Expanded( Expanded(
flex: 1,
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
...actions, if (info.iconData != null) ...[
Icon(
info.iconData,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(
width: 8,
),
],
Flexible(
child: TooltipText(
text: Text(
info.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleMedium,
),
),
),
], ],
), ),
), ),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
...actions,
],
),
], ],
), ),
); );
@@ -86,6 +79,7 @@ class CommonCard extends StatelessWidget {
this.onPressed, this.onPressed,
this.info, this.info,
this.selectWidget, this.selectWidget,
this.radius = 12,
required this.child, required this.child,
}) : isSelected = isSelected ?? false; }) : isSelected = isSelected ?? false;
@@ -95,14 +89,13 @@ class CommonCard extends StatelessWidget {
final Widget child; final Widget child;
final Info? info; final Info? info;
final CommonCardType type; final CommonCardType type;
final double radius;
BorderSide getBorderSide(BuildContext context, Set<WidgetState> states) { BorderSide getBorderSide(BuildContext context, Set<WidgetState> states) {
if (type == CommonCardType.filled) { if (type == CommonCardType.filled) {
return BorderSide.none; return BorderSide.none;
} }
final colorScheme = Theme final colorScheme = Theme.of(context).colorScheme;
.of(context)
.colorScheme;
final hoverColor = isSelected final hoverColor = isSelected
? colorScheme.primary.toLight() ? colorScheme.primary.toLight()
: colorScheme.primary.toLighter(); : colorScheme.primary.toLighter();
@@ -119,9 +112,7 @@ class CommonCard extends StatelessWidget {
} }
Color? getBackgroundColor(BuildContext context, Set<WidgetState> states) { Color? getBackgroundColor(BuildContext context, Set<WidgetState> states) {
final colorScheme = Theme final colorScheme = Theme.of(context).colorScheme;
.of(context)
.colorScheme;
switch (type) { switch (type) {
case CommonCardType.plain: case CommonCardType.plain:
if (isSelected) { if (isSelected) {
@@ -130,8 +121,7 @@ class CommonCard extends StatelessWidget {
if (states.isEmpty) { if (states.isEmpty) {
return colorScheme.secondaryContainer.toLittle(); return colorScheme.secondaryContainer.toLittle();
} }
return Theme return Theme.of(context)
.of(context)
.outlinedButtonTheme .outlinedButtonTheme
.style .style
?.backgroundColor ?.backgroundColor
@@ -155,41 +145,41 @@ class CommonCard extends StatelessWidget {
childWidget = Column( childWidget = Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Flexible( InfoHeader(
flex: 0, info: info!,
child: InfoHeader(
info: info!,
),
), ),
Flexible( Flexible(
flex: 1,
child: child, child: child,
), ),
], ],
); );
} }
return OutlinedButton( return OutlinedButton(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
style: ButtonStyle( style: ButtonStyle(
padding: const WidgetStatePropertyAll(EdgeInsets.zero), padding: const WidgetStatePropertyAll(EdgeInsets.zero),
shape: WidgetStatePropertyAll( shape: WidgetStatePropertyAll(
RoundedRectangleBorder( RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(radius),
), ),
), ),
backgroundColor: WidgetStateProperty.resolveWith( backgroundColor: WidgetStateProperty.resolveWith(
(states) => getBackgroundColor(context, states), (states) => getBackgroundColor(context, states),
), ),
side: WidgetStateProperty.resolveWith( side: WidgetStateProperty.resolveWith(
(states) => getBorderSide(context, states), (states) => getBorderSide(context, states),
), ),
), ),
onPressed: onPressed, onPressed: onPressed,
child: Builder( child: Builder(
builder: (_) { builder: (_) {
if (selectWidget == null) {
return childWidget;
}
List<Widget> children = []; List<Widget> children = [];
children.add(childWidget); children.add(childWidget);
if (selectWidget != null && isSelected) { if (isSelected) {
children.add( children.add(
Positioned.fill( Positioned.fill(
child: selectWidget!, child: selectWidget!,
@@ -211,10 +201,7 @@ class SelectIcon extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return Material(
color: Theme color: Theme.of(context).colorScheme.inversePrimary,
.of(context)
.colorScheme
.inversePrimary,
shape: const CircleBorder(), shape: const CircleBorder(),
child: Container( child: Container(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),

View File

@@ -0,0 +1,121 @@
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/proxy.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ClashContainer extends StatefulWidget {
final Widget child;
const ClashContainer({
super.key,
required this.child,
});
@override
State<ClashContainer> createState() => _ClashContainerState();
}
class _ClashContainerState extends State<ClashContainer>
with AppMessageListener {
Widget _updateCoreState(Widget child) {
return Selector2<Config, ClashConfig, CoreState>(
selector: (_, config, clashConfig) => CoreState(
accessControl: config.isAccessControl ? config.accessControl : null,
allowBypass: config.allowBypass,
systemProxy: config.systemProxy,
mixedPort: clashConfig.mixedPort,
onlyProxy: config.onlyProxy,
currentProfileName:
config.currentProfile?.label ?? config.currentProfileId ?? "",
),
builder: (__, state, child) {
clashCore.setState(state);
return child!;
},
child: child,
);
}
_changeProfileHandle() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final appController = globalState.appController;
appController.appState.delayMap = {};
await appController.applyProfile();
});
}
Widget _changeProfileContainer(Widget child) {
return Selector<Config, String?>(
selector: (_, config) => config.currentProfileId,
builder: (__, state, child) {
_changeProfileHandle();
return child!;
},
child: child,
);
}
@override
Widget build(BuildContext context) {
return _changeProfileContainer(
_updateCoreState(
widget.child,
),
);
}
@override
void initState() {
super.initState();
clashMessage.addListener(this);
}
@override
Future<void> dispose() async {
clashMessage.removeListener(this);
super.dispose();
}
@override
Future<void> onDelay(Delay delay) async {
final appController = globalState.appController;
appController.setDelay(delay);
super.onDelay(delay);
await globalState.appController.updateGroupDebounce();
}
@override
void onLog(Log log) {
globalState.appController.appState.addLog(log);
super.onLog(log);
}
@override
void onRequest(Connection connection) async {
globalState.appController.appState.addRequest(connection);
super.onRequest(connection);
}
@override
void onLoaded(String providerName) {
final appController = globalState.appController;
appController.appState.setProvider(
clashCore.getExternalProvider(
providerName,
),
);
appController.addCheckIpNumDebounce();
super.onLoaded(providerName);
}
@override
Future<void> onStarted(String runTime) async {
super.onStarted(runTime);
proxy?.updateStartTime();
final appController = globalState.appController;
await appController.applyProfile(isPrue: true);
appController.addCheckIpNumDebounce();
}
}

View File

@@ -1,81 +0,0 @@
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/proxy.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
class ClashMessageContainer extends StatefulWidget {
final Widget child;
const ClashMessageContainer({
super.key,
required this.child,
});
@override
State<ClashMessageContainer> createState() => _ClashMessageContainerState();
}
class _ClashMessageContainerState extends State<ClashMessageContainer>
with AppMessageListener {
@override
Widget build(BuildContext context) {
return widget.child;
}
@override
void initState() {
super.initState();
clashMessage.addListener(this);
}
@override
Future<void> dispose() async {
clashMessage.removeListener(this);
super.dispose();
}
@override
void onDelay(Delay delay) {
final appController = globalState.appController;
appController.setDelay(delay);
super.onDelay(delay);
}
@override
void onLog(Log log) {
globalState.appController.appState.addLog(log);
super.onLog(log);
}
@override
void onRequest(Connection connection) async {
globalState.appController.appState.addRequest(connection);
super.onRequest(connection);
}
@override
void onLoaded(String groupName) {
final appController = globalState.appController;
final currentSelectedMap = appController.config.currentSelectedMap;
final proxyName = currentSelectedMap[groupName];
if (proxyName == null) return;
globalState.changeProxy(
config: appController.config,
groupName: groupName,
proxyName: proxyName,
);
appController.addCheckIpNumDebounce();
super.onLoaded(proxyName);
}
@override
void onStarted(String runTime) {
super.onStarted(runTime);
proxy?.updateStartTime();
final appController = globalState.appController;
appController.rawApplyProfile().then((_) {
appController.addCheckIpNumDebounce();
});
}
}

View File

@@ -213,6 +213,9 @@ class ListItem<T> extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (delegate is OpenDelegate) { if (delegate is OpenDelegate) {
final openDelegate = delegate as OpenDelegate; final openDelegate = delegate as OpenDelegate;
final child = SafeArea(
child: openDelegate.widget,
);
return OpenContainer( return OpenContainer(
closedBuilder: (_, action) { closedBuilder: (_, action) {
openAction() { openAction() {
@@ -221,7 +224,7 @@ class ListItem<T> extends StatelessWidget {
if (!isMobile) { if (!isMobile) {
showExtendPage( showExtendPage(
context, context,
body: openDelegate.widget, body: child,
title: openDelegate.title, title: openDelegate.title,
extendPageWidth: openDelegate.extendPageWidth, extendPageWidth: openDelegate.extendPageWidth,
); );
@@ -230,14 +233,16 @@ class ListItem<T> extends StatelessWidget {
action(); action();
} }
return _buildListTile(onTap: openAction); return _buildListTile(
onTap: openAction,
);
}, },
openBuilder: (_, action) { openBuilder: (_, action) {
return CommonScaffold.open( return CommonScaffold.open(
key: Key(openDelegate.title), key: Key(openDelegate.title),
onBack: action, onBack: action,
title: openDelegate.title, title: openDelegate.title,
body: openDelegate.widget, body: child,
); );
}, },
); );
@@ -399,10 +404,10 @@ List<Widget> generateInfoSection({
}) { }) {
final genItems = separated final genItems = separated
? items.separated( ? items.separated(
const Divider( const Divider(
height: 0, height: 0,
), ),
) )
: items; : items;
return [ return [
if (items.isNotEmpty) if (items.isNotEmpty)
@@ -414,7 +419,6 @@ List<Widget> generateInfoSection({
]; ];
} }
Widget generateListView(List<Widget> items) { Widget generateListView(List<Widget> items) {
return ListView.builder( return ListView.builder(
itemCount: items.length, itemCount: items.length,

View File

@@ -85,7 +85,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
} }
@override @override
void didUpdateWidget(covariant CommonScaffold oldWidget) { void didUpdateWidget(CommonScaffold oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (oldWidget.title != widget.title) { if (oldWidget.title != widget.title) {
_actions.value = []; _actions.value = [];
@@ -94,6 +94,8 @@ class CommonScaffoldState extends State<CommonScaffold> {
Widget? get _sideNavigationBar => widget.sideNavigationBar; Widget? get _sideNavigationBar => widget.sideNavigationBar;
Widget get body => SafeArea(child: widget.body);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final scaffold = Scaffold( final scaffold = Scaffold(
@@ -107,7 +109,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
valueListenable: _actions, valueListenable: _actions,
builder: (_, actions, __) { builder: (_, actions, __) {
final realActions = final realActions =
actions.isNotEmpty ? actions : widget.actions; actions.isNotEmpty ? actions : widget.actions;
return AppBar( return AppBar(
centerTitle: false, centerTitle: false,
automaticallyImplyLeading: widget.automaticallyImplyLeading, automaticallyImplyLeading: widget.automaticallyImplyLeading,
@@ -133,7 +135,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
], ],
), ),
), ),
body: widget.body, body: body,
bottomNavigationBar: widget.bottomNavigationBar, bottomNavigationBar: widget.bottomNavigationBar,
); );
return _sideNavigationBar != null return _sideNavigationBar != null

74
lib/widgets/setting.dart Normal file
View File

@@ -0,0 +1,74 @@
import 'package:fl_clash/common/common.dart';
import 'package:flutter/material.dart';
import 'card.dart';
class SettingInfoCard extends StatelessWidget {
final Info info;
final bool? isSelected;
final VoidCallback onPressed;
const SettingInfoCard(
this.info, {
super.key,
this.isSelected,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return CommonCard(
isSelected: isSelected,
onPressed: onPressed,
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(
child: Icon(info.iconData),
),
const SizedBox(
width: 8,
),
Flexible(
child: Text(
info.label,
style: context.textTheme.bodyMedium,
),
),
],
),
),
);
}
}
class SettingTextCard extends StatelessWidget {
final String text;
final bool? isSelected;
final VoidCallback onPressed;
const SettingTextCard(
this.text, {
super.key,
this.isSelected,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return CommonCard(
onPressed: onPressed,
isSelected: isSelected,
child: Padding(
padding: const EdgeInsets.all(12),
child: Text(
text,
style: context.textTheme.bodyMedium,
),
),
);
}
}

View File

@@ -1,10 +1,8 @@
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/scaffold.dart'; import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'side_sheet.dart'; import 'side_sheet.dart';
showExtendPage( showExtendPage(
@@ -12,6 +10,7 @@ showExtendPage(
required Widget body, required Widget body,
required String title, required String title,
double? extendPageWidth, double? extendPageWidth,
bool forceNotSide = false,
Widget? action, Widget? action,
}) { }) {
final NavigatorState navigator = Navigator.of(context); final NavigatorState navigator = Navigator.of(context);
@@ -20,35 +19,32 @@ showExtendPage(
key: globalKey, key: globalKey,
child: body, child: body,
); );
final isMobile =
globalState.appController.appState.viewMode == ViewMode.mobile;
final isNotSide = isMobile || forceNotSide;
navigator.push( navigator.push(
ModalSideSheetRoute( ModalSideSheetRoute(
modalBarrierColor: Colors.black38, modalBarrierColor: Colors.black38,
builder: (context) => Selector<AppState, double>( builder: (context) {
selector: (_, appState) => appState.viewWidth, final commonScaffold = CommonScaffold(
builder: (_, viewWidth, __) { automaticallyImplyLeading: isNotSide,
final isMobile = actions: isNotSide
globalState.appController.appState.viewMode == ViewMode.mobile; ? null
final commonScaffold = CommonScaffold( : [
automaticallyImplyLeading: isMobile ? true : false, const SizedBox(
actions: isMobile height: kToolbarHeight,
? null width: kToolbarHeight,
: [ child: CloseButton(),
const SizedBox( ),
height: kToolbarHeight, ],
width: kToolbarHeight, title: title,
child: CloseButton(), body: uniqueBody,
), );
], return SizedBox(
title: title, width: isMobile ? context.width : extendPageWidth ?? 300,
body: uniqueBody, child: commonScaffold,
); );
return AnimatedContainer( },
duration: kThemeAnimationDuration,
width: isMobile ? viewWidth : extendPageWidth ?? 300,
child: commonScaffold,
);
},
),
constraints: const BoxConstraints(), constraints: const BoxConstraints(),
filter: filter, filter: filter,
), ),
@@ -68,7 +64,13 @@ showSheet({
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: isScrollControlled, isScrollControlled: isScrollControlled,
builder: builder, builder: (context) {
return SafeArea(
child: builder(
context,
),
);
},
showDragHandle: true, showDragHandle: true,
useSafeArea: true, useSafeArea: true,
); );
@@ -80,7 +82,9 @@ showSheet({
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: width, maxWidth: width,
), ),
body: builder(context), body: SafeArea(
child: builder(context),
),
title: title, title: title,
); );
} }

View File

@@ -589,25 +589,27 @@ Future<T?> showModalSideSheet<T>({
final MaterialLocalizations localizations = MaterialLocalizations.of(context); final MaterialLocalizations localizations = MaterialLocalizations.of(context);
return navigator.push(ModalSideSheetRoute<T>( return navigator.push(ModalSideSheetRoute<T>(
builder: (context) { builder: (context) {
return Column( return SafeArea(
children: [ child: Column(
AppBar( children: [
automaticallyImplyLeading: false, AppBar(
title: Text(title), automaticallyImplyLeading: false,
centerTitle: false, title: Text(title),
actions: const [ centerTitle: false,
SizedBox( actions: const [
height: kToolbarHeight, SizedBox(
width: kToolbarHeight, height: kToolbarHeight,
child: CloseButton(), width: kToolbarHeight,
) child: CloseButton(),
], )
), ],
Expanded( ),
flex: 1, Expanded(
child: body, flex: 1,
), child: body,
], ),
],
),
); );
}, },
capturedThemes: capturedThemes:

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:emoji_regex/emoji_regex.dart';
import '../state.dart'; import '../state.dart';
@@ -30,3 +31,63 @@ class TooltipText extends StatelessWidget {
); );
} }
} }
class EmojiText extends StatelessWidget {
final String text;
final TextStyle? style;
final int? maxLines;
final TextOverflow? overflow;
const EmojiText(
this.text, {
super.key,
this.maxLines,
this.overflow,
this.style,
});
List<TextSpan> _buildTextSpans(String emojis) {
final List<TextSpan> spans = [];
final matches = emojiRegex().allMatches(text);
int lastMatchEnd = 0;
for (final match in matches) {
if (match.start > lastMatchEnd) {
spans.add(
TextSpan(
text: text.substring(lastMatchEnd, match.start), style: style),
);
}
spans.add(
TextSpan(
text:match.group(0),
style: style?.copyWith(
fontFamily: "Twemoji",
),
),
);
lastMatchEnd = match.end;
}
if (lastMatchEnd < text.length) {
spans.add(
TextSpan(
text: text.substring(lastMatchEnd),
style: style,
),
);
}
return spans;
}
@override
Widget build(BuildContext context) {
return RichText(
maxLines: maxLines,
overflow: overflow ?? TextOverflow.clip,
text: TextSpan(
children: _buildTextSpans(text),
),
);
}
}

View File

@@ -17,10 +17,12 @@ export 'animate_grid.dart';
export 'tray_container.dart'; export 'tray_container.dart';
export 'window_container.dart'; export 'window_container.dart';
export 'android_container.dart'; export 'android_container.dart';
export 'clash_message_container.dart'; export 'clash_container.dart';
export 'tile_container.dart'; export 'tile_container.dart';
export 'chip.dart'; export 'chip.dart';
export 'fade_box.dart'; export 'fade_box.dart';
export 'app_state_container.dart'; export 'app_state_container.dart';
export 'text.dart'; export 'text.dart';
export 'connection_item.dart'; export 'connection_item.dart';
export 'builder.dart';
export 'setting.dart';

View File

@@ -220,16 +220,14 @@ class _WindowHeaderState extends State<WindowHeader> {
), ),
), ),
), ),
if (!Platform.isMacOS) ...[ const Positioned(
const Positioned( left: 0,
left: 0, child: AppIcon(),
child: AppIcon(), ),
), Positioned(
Positioned( right: 0,
right: 0, child: _buildActions(),
child: _buildActions(), ),
),
]
], ],
), ),
); );

View File

@@ -34,7 +34,7 @@ packages:
source: hosted source: hosted
version: "3.5.1" version: "3.5.1"
archive: archive:
dependency: transitive dependency: "direct main"
description: description:
name: archive name: archive
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
@@ -193,14 +193,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.1.1"
country_flags:
dependency: "direct main"
description:
name: country_flags
sha256: dbc4f76e7c801619b2d841023e0327191ba00663f1f1b4770394e9bc6632c444
url: "https://pub.dev"
source: hosted
version: "2.2.0"
cross_file: cross_file:
dependency: transitive dependency: transitive
description: description:
@@ -249,6 +241,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.7.0" version: "1.7.0"
emoji_regex:
dependency: "direct main"
description:
name: emoji_regex
sha256: "3a25dd4d16f98b6f76dc37cc9ae49b8511891ac4b87beac9443a1e9f4634b6c7"
url: "https://pub.dev"
source: hosted
version: "0.0.5"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@@ -541,22 +541,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.3.1" version: "4.3.1"
jovial_misc:
dependency: transitive
description:
name: jovial_misc
sha256: f6e64f789ee311025bb367be9c9afe9759f76dd8209070b7f38e735b5f529eb1
url: "https://pub.dev"
source: hosted
version: "0.8.5"
jovial_svg:
dependency: transitive
description:
name: jovial_svg
sha256: "000cafe183bdeba28f76d65bf93fc9b636d6f7eaa7e2a33e80c4345a28866ea8"
url: "https://pub.dev"
source: hosted
version: "1.1.21"
js: js:
dependency: transitive dependency: transitive
description: description:
@@ -629,6 +613,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
lpinyin:
dependency: "direct main"
description:
name: lpinyin
sha256: "0bb843363f1f65170efd09fbdfc760c7ec34fc6354f9fcb2f89e74866a0d814a"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@@ -638,7 +630,7 @@ packages:
source: hosted source: hosted
version: "0.12.16+1" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: "direct main" dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
@@ -789,14 +781,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev"
source: hosted
version: "3.9.1"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@@ -848,10 +832,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: re_editor name: re_editor
sha256: db7a82e95f0f74301e85d4d5c805a8b8a5ba43d6c0d26673b7e35dc011f06635 sha256: abae2b015799c936b9f9b68888e2c55007dd159b4654a85da22ce1af84efbd17
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.0" version: "0.3.1"
re_highlight: re_highlight:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1243,4 +1227,4 @@ packages:
version: "0.2.3" version: "0.2.3"
sdks: sdks:
dart: ">=3.4.0 <4.0.0" dart: ">=3.4.0 <4.0.0"
flutter: ">=3.22.0" flutter: ">=3.22.3"

View File

@@ -1,7 +1,7 @@
name: fl_clash name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free. description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none' publish_to: 'none'
version: 0.8.48+202407251 version: 0.8.53+202408151
environment: environment:
sdk: '>=3.1.0 <4.0.0' sdk: '>=3.1.0 <4.0.0'
@@ -37,12 +37,13 @@ dependencies:
image: ^4.1.7 image: ^4.1.7
webdav_client: ^1.2.2 webdav_client: ^1.2.2
dio: ^5.4.3+1 dio: ^5.4.3+1
country_flags: ^2.2.0
re_editor: ^0.3.0
re_highlight: ^0.0.3
win32: ^5.5.1 win32: ^5.5.1
ffi: ^2.1.2 ffi: ^2.1.2
material_color_utilities: ^0.8.0 re_editor: ^0.3.1
re_highlight: ^0.0.3
archive: ^3.6.1
lpinyin: ^2.0.3
emoji_regex: ^0.0.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
@@ -57,8 +58,13 @@ flutter:
uses-material-design: true uses-material-design: true
assets: assets:
- assets/data/ - assets/data/
- assets/fonts/
- assets/images/ - assets/images/
- assets/images/avatars/ - assets/images/avatars/
fonts:
- family: Twemoji
fonts:
- asset: assets/fonts/Twemoji.Mozilla.ttf
ffigen: ffigen:
name: "ClashFFI" name: "ClashFFI"
output: 'lib/clash/generated/clash_ffi.dart' output: 'lib/clash/generated/clash_ffi.dart'

View File

@@ -0,0 +1,347 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="646"
height="250"
id="svg8502"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="get-it-on.svg">
<defs
id="defs8504">
<linearGradient
inkscape:collect="always"
id="linearGradient4392">
<stop
style="stop-color:#ffffff;stop-opacity:0.09803922"
offset="0"
id="stop4394" />
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="1"
id="stop4396" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4392"
id="radialGradient4398"
cx="113"
cy="-12.889574"
fx="113"
fy="-12.889574"
r="59.661892"
gradientTransform="matrix(-4.3416142e-8,1.9610509,-1.9778119,2.8493899e-8,254.50686,78.76343)"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.4142136"
inkscape:cx="322.4318"
inkscape:cy="148.85197"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="0"
inkscape:window-y="34"
inkscape:window-maximized="1" />
<metadata
id="metadata8507">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Andrew Nayenko</dc:title>
</cc:Agent>
</dc:creator>
<dc:publisher>
<cc:Agent>
<dc:title>https://f-droid.org</dc:title>
</cc:Agent>
</dc:publisher>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
<dc:contributor>
<cc:Agent>
<dc:title />
</cc:Agent>
</dc:contributor>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/3.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-289,-312.36218)">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#a6a6a6;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
id="rect3007"
width="560"
height="164"
x="332"
y="355.36218"
rx="20"
ry="20" />
<text
sodipodi:linespacing="100%"
id="text3013"
y="402.36697"
x="508.95129"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.39519119px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate"
xml:space="preserve"><tspan
y="402.36697"
x="508.95129"
id="tspan3015"
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:34.125px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans'">GET IT ON</tspan></text>
<text
xml:space="preserve"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.70882034px;line-height:100%;font-family:Rokkitt;-inkscape-font-specification:'Rokkitt Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate"
x="508.21259"
y="489.36108"
id="text5902"
sodipodi:linespacing="100%"><tspan
sodipodi:role="line"
id="tspan5904"
x="508.21259"
y="489.36108"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:95px;line-height:100%;font-family:'Roboto Slab';-inkscape-font-specification:'Roboto Slab Bold';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1">F-Droid</tspan></text>
<g
id="g4400"
transform="translate(80.999992,75.999997)">
<g
id="g5012"
transform="matrix(2.6315876,0,0,2.6315749,275.84226,-2346.4746)">
<g
transform="matrix(-1,0,0,1,47.999779,0)"
id="g4179">
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path4181"
d="m 2.5889342,1006.8622 4.25,5.5"
style="fill:#8ab000;fill-opacity:1;fill-rule:evenodd;stroke:#769616;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:0.29803922;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 2.6113281,1005.6094 c -0.4534623,0.012 -0.7616975,0.189 -0.9807462,0.4486 2.0269314,2.4089 2.368401,2.7916 5.1354735,6.2214 1.0195329,1.319 2.0816026,0.6373 1.0620696,-0.6817 l -4.25,-5.5 c -0.2289894,-0.3056 -0.5850813,-0.478 -0.9667969,-0.4883 z"
id="path4183"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#263238;fill-opacity:0.2;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 1.6220992,1006.0705 c -0.1238933,0.1479 -0.561176,0.8046 -0.02249,1.5562 l 4.25,5.5 c 1.0195329,1.319 1.1498748,-0.6123 1.1498748,-0.6123 0,0 -3.7344514,-4.51 -5.3773848,-6.4439 z"
id="path4185"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#8ab000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 2.3378905,1005.8443 c -0.438175,0 -0.959862,0.1416 -0.8242183,0.7986 0.103561,0.5016 4.6608262,6.0744 4.6608262,6.0744 1.0195329,1.319 2.4934721,0.6763 1.4739391,-0.6425 l -4.234375,-5.4727 c -0.2602394,-0.29 -0.6085188,-0.7436 -1.076172,-0.7578 z"
id="path4187"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cscccc" />
</g>
<g
id="g4955">
<path
style="fill:#8ab000;fill-opacity:1;fill-rule:evenodd;stroke:#769616;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 2.5889342,1006.8622 4.25,5.5"
id="path4945"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cccccc"
inkscape:connector-curvature="0"
id="path4947"
d="m 2.6113281,1005.6094 c -0.4534623,0.012 -0.7616975,0.189 -0.9807462,0.4486 2.0269314,2.4089 2.368401,2.7916 5.1354735,6.2214 1.0195329,1.319 2.0816026,0.6373 1.0620696,-0.6817 l -4.25,-5.5 c -0.2289894,-0.3056 -0.5850813,-0.478 -0.9667969,-0.4883 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:0.29803922;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path4951"
d="m 1.6220992,1006.0705 c -0.1238933,0.1479 -0.561176,0.8046 -0.02249,1.5562 l 4.25,5.5 c 1.0195329,1.319 1.1498748,-0.6123 1.1498748,-0.6123 0,0 -3.7344514,-4.51 -5.3773848,-6.4439 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#263238;fill-opacity:0.2;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path
sodipodi:nodetypes="cscccc"
inkscape:connector-curvature="0"
id="path4925"
d="m 2.3378905,1005.8443 c -0.438175,0 -0.959862,0.1416 -0.8242183,0.7986 0.103561,0.5016 4.6608262,6.0744 4.6608262,6.0744 1.0195329,1.319 2.4934721,0.6763 1.4739391,-0.6425 l -4.234375,-5.4727 c -0.2602394,-0.29 -0.6085188,-0.7436 -1.076172,-0.7578 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#8ab000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
</g>
<g
id="g4967"
transform="translate(42,0)">
<rect
ry="3"
rx="3"
y="1010.3596"
x="-37"
height="12.92002"
width="38"
id="rect4144"
style="opacity:1;fill:#aeea00;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
style="opacity:1;fill:#263238;fill-opacity:0.2;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4961"
width="38"
height="10"
x="-37"
y="1013.2795"
rx="3"
ry="3" />
<rect
style="opacity:1;fill:#ffffff;fill-opacity:0.29803922;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4963"
width="38"
height="10"
x="-37"
y="1010.3622"
rx="3"
ry="3" />
<rect
style="opacity:1;fill:#aeea00;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4965"
width="38"
height="10.640781"
x="-37"
y="1011.5002"
rx="3"
ry="2.455565" />
</g>
<g
transform="translate(0,-0.10259092)"
id="g4979">
<rect
ry="3"
rx="3"
y="1024.5221"
x="5"
height="25.84004"
width="38"
id="rect4146"
style="opacity:1;fill:#1976d2;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
style="opacity:1;fill:#263238;fill-opacity:0.2;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4973"
width="38"
height="13"
x="5"
y="1037.3622"
rx="3"
ry="3" />
<rect
style="opacity:1;fill:#ffffff;fill-opacity:0.2;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4975"
width="38"
height="13"
x="5"
y="1024.4421"
rx="3"
ry="3" />
<rect
style="opacity:1;fill:#1976d2;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4977"
width="38"
height="23.560036"
x="5"
y="1025.6621"
rx="3"
ry="2.7184658" />
</g>
<g
id="g4211"
transform="translate(0,1013.3622)">
<path
inkscape:connector-curvature="0"
id="path4161"
d="m 24,17.75 c -2.880662,0 -5.319789,1.984685 -6.033203,4.650391 l 3.212891,0 C 21.734004,21.415044 22.774798,20.75 24,20.75 c 1.812692,0 3.25,1.437308 3.25,3.25 0,1.812693 -1.437308,3.25 -3.25,3.25 -1.307381,0 -2.411251,-0.75269 -2.929688,-1.849609 l -3.154296,0 C 18.558263,28.166146 21.04791,30.25 24,30.25 c 3.434013,0 6.25,-2.815987 6.25,-6.25 0,-3.434012 -2.815987,-6.25 -6.25,-6.25 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0d47a1;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<circle
r="9.5500002"
cy="24"
cx="24"
id="path4209"
style="opacity:1;fill:none;fill-opacity:0.40392157;stroke:#0d47a1;stroke-width:1.89999998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
<g
transform="translate(0,0.50001738)"
id="g4989">
<ellipse
ry="3.875"
rx="3.375"
style="opacity:1;fill:#263238;fill-opacity:0.2;stroke:none;stroke-width:1.89999998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.69721117"
id="circle4985"
cx="14.375"
cy="1016.4872" />
<circle
r="3.375"
cy="1016.9872"
cx="14.375"
id="path4859"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.89999998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.69721117" />
</g>
<g
id="g4201"
transform="translate(19.5,0.50001738)">
<ellipse
cy="1016.4872"
cx="14.375"
id="ellipse4175"
style="opacity:1;fill:#263238;fill-opacity:0.2;stroke:none;stroke-width:1.89999998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.69721117"
rx="3.375"
ry="3.875" />
<circle
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.89999998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.69721117"
id="circle4177"
cx="14.375"
cy="1016.9872"
r="3.375" />
</g>
</g>
<path
inkscape:connector-curvature="0"
id="path4286"
d="m 282.71484,299.83484 a 3.2898056,3.2898056 0 0 0 -2.66211,5.33593 l 9.47461,12.26172 C 289.19237,318.31001 289,319.25869 289,320.25671 l 0,18.21094 c 0,4.37367 3.52083,7.89453 7.89453,7.89453 l 84.21094,0 c 4.3737,0 7.89453,-3.52086 7.89453,-7.89453 l 0,-18.21094 c 0,-0.99858 -0.19005,-1.94824 -0.52539,-2.82617 l 9.47266,-12.25977 a 3.2898056,3.2898056 0 0 0 -2.4336,-5.33398 3.2898056,3.2898056 0 0 0 -2.77148,1.31055 l -9.01367,11.66601 c -0.82093,-0.28771 -1.70144,-0.45117 -2.62305,-0.45117 l -84.21094,0 c -0.92137,0 -1.80035,0.1636 -2.62109,0.45117 l -9.01563,-11.66601 a 3.2898056,3.2898056 0 0 0 -2.54297,-1.3125 z m 14.17969,49.52734 c -4.3737,0 -7.89453,3.52085 -7.89453,7.89453 l 0,52.21094 c 0,4.37368 3.52083,7.89453 7.89453,7.89453 l 84.21094,0 c 4.3737,0 7.89453,-3.52085 7.89453,-7.89453 l 0,-52.21094 c 0,-4.37368 -3.52083,-7.89453 -7.89453,-7.89453 l -84.21094,0 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#radialGradient4398);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:6.57895327;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="646" height="250" id="svg8502" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="get-it-on-github.svg" inkscape:export-filename="/home/nixdorf/Lokaler Speicher/Build/andOTP/assets/badges/get-it-on-github.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90">
<defs id="defs8504" />
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.50000001" inkscape:cx="-223.13537" inkscape:cy="546.37524" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" inkscape:window-width="1920" inkscape:window-height="1021" inkscape:window-x="0" inkscape:window-y="31" inkscape:window-maximized="1" />
<metadata id="metadata8507">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<dc:creator>
<cc:Agent>
<dc:title>Andrew Nayenko</dc:title>
</cc:Agent>
</dc:creator>
<dc:publisher>
<cc:Agent>
<dc:title>https://f-droid.org</dc:title>
</cc:Agent>
</dc:publisher>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
<dc:contributor>
<cc:Agent>
<dc:title />
</cc:Agent>
</dc:contributor>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/3.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(-289,-312.36218)">
<rect style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#a6a6a6;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" id="rect3007" width="560" height="164" x="332" y="355.36218" rx="20" ry="20" />
<text sodipodi:linespacing="100%" id="text3013" y="402.36697" x="508.95129" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.39519119px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate" xml:space="preserve"><tspan y="402.36697" x="508.95129" id="tspan3015" sodipodi:role="line" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:34.125px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans'">GET IT ON</tspan></text>
<text xml:space="preserve" style="color:black;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.70882034px;line-height:100%;font-family:Rokkitt;-inkscape-font-specification:'Rokkitt Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate" x="508.21259" y="489.36108" id="text5902" sodipodi:linespacing="100%"><tspan sodipodi:role="line" id="tspan5904" x="508.21259" y="489.36108" style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:87.5px;line-height:100%;font-family:'Roboto Slab';-inkscape-font-specification:'Roboto Slab Bold';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:white;fill-opacity:1">GitHub</tspan></text>
<g transform="matrix(1.0017681,0,0,1.0017681,68.770756,92.660249)" id="g5136">
<path style="fill:white;fill-opacity:1;fill-rule:evenodd;stroke:none" d="m 350.60937,282.4375 c -33.34765,0 -60.38671,27.03516 -60.38671,60.38672 0,26.67969 17.30078,49.3164 41.29687,57.30078 3.01563,0.55859 4.125,-1.30859 4.125,-2.90625 0,-1.4375 -0.0547,-6.19531 -0.082,-11.24219 -16.80078,3.65235 -20.34375,-7.125 -20.34375,-7.125 -2.75,-6.98047 -6.70703,-8.83594 -6.70703,-8.83594 -5.48047,-3.74609 0.41406,-3.67187 0.41406,-3.67187 6.0625,0.42578 9.25781,6.22266 9.25781,6.22266 5.38282,9.23437 14.125,6.5664 17.57032,5.02343 0.54296,-3.90234 2.10937,-6.57031 3.83593,-8.07812 -13.41406,-1.52735 -27.51562,-6.70313 -27.51562,-29.84375 0,-6.59375 2.35937,-11.98047 6.22265,-16.20703 -0.625,-1.52344 -2.6914,-7.66407 0.58594,-15.98047 0,0 5.07031,-1.625 16.60938,6.1875 4.82031,-1.33594 9.98437,-2.00781 15.11718,-2.03125 5.13282,0.0234 10.30079,0.69531 15.12891,2.03125 11.52344,-7.8125 16.58984,-6.1875 16.58984,-6.1875 3.28516,8.3164 1.21875,14.45703 0.58985,15.98047 3.87109,4.22656 6.21484,9.61328 6.21484,16.20703 0,23.19531 -14.125,28.30078 -27.57422,29.79687 2.16797,1.875 4.09766,5.55078 4.09766,11.1836 0,8.07812 -0.0703,14.58203 -0.0703,16.57031 0,1.60937 1.08593,3.49219 4.14843,2.89844 23.98047,-7.9961 41.26172,-30.6211 41.26172,-57.29297 0,-33.35156 -27.03515,-60.38672 -60.38672,-60.38672" id="path4937" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 313.09375,369.14062 c -0.13281,0.30079 -0.60547,0.38672 -1.03516,0.1836 -0.4375,-0.19922 -0.68359,-0.60547 -0.54297,-0.91016 0.12891,-0.30859 0.60157,-0.39062 1.03907,-0.1875 0.4414,0.19922 0.6914,0.61328 0.53906,0.91406" id="path4939" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 315.53906,371.86719 c -0.28906,0.26562 -0.85156,0.14453 -1.23437,-0.27735 -0.39453,-0.42187 -0.46875,-0.98437 -0.17578,-1.25781 0.29687,-0.26562 0.84375,-0.14062 1.23828,0.28125 0.39453,0.42578 0.47265,0.98438 0.17187,1.25391" id="path4941" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 317.92187,375.34375 c -0.37109,0.25781 -0.97656,0.0156 -1.35156,-0.51953 -0.37109,-0.53906 -0.37109,-1.17969 0.008,-1.4375 0.375,-0.25781 0.97266,-0.0274 1.35157,0.5039 0.36718,0.54688 0.36718,1.19141 -0.008,1.45313" id="path4943" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 321.18359,378.70312 c -0.33203,0.36719 -1.03906,0.26954 -1.55468,-0.23046 -0.52735,-0.48438 -0.67579,-1.17579 -0.34375,-1.54297 0.33593,-0.36328 1.04687,-0.26172 1.5664,0.23047 0.52344,0.48828 0.6836,1.18359 0.33203,1.54296" id="path4945-3" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 325.68359,380.65625 c -0.14843,0.47266 -0.82812,0.6875 -1.51172,0.48828 -0.68359,-0.20703 -1.1289,-0.76172 -0.99218,-1.24219 0.14453,-0.47265 0.82422,-0.69922 1.51562,-0.48437 0.67969,0.20703 1.12891,0.75781 0.98828,1.23828" id="path4947-6" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 330.625,381.01953 c 0.0156,0.49609 -0.5625,0.91016 -1.28125,0.91797 -0.72266,0.0156 -1.30859,-0.38672 -1.31641,-0.875 0,-0.50391 0.57032,-0.91406 1.28907,-0.92578 0.71875,-0.0156 1.30859,0.38672 1.30859,0.88281" id="path4949" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 335.22266,380.23437 c 0.0859,0.48829 -0.41407,0.98438 -1.125,1.11719 -0.70313,0.12891 -1.35157,-0.17187 -1.44141,-0.65234 -0.0859,-0.5 0.42187,-0.9961 1.12109,-1.125 0.71485,-0.125 1.35547,0.16797 1.44532,0.66015" id="path4951-7" inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.5 KiB

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