Compare commits

...

7 Commits

Author SHA1 Message Date
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
66 changed files with 2190 additions and 1203 deletions

View File

@@ -141,3 +141,17 @@ jobs:
with: with:
files: ./dist/* files: ./dist/*
body_path: './release.md' body_path: './release.md'
- 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: ./dist/
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

@@ -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

@@ -4,12 +4,14 @@ 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.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 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,6 +30,7 @@ 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
@@ -61,7 +64,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()
} }
@@ -164,12 +167,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)

View File

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

View File

@@ -410,7 +410,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 +428,5 @@ func applyConfig() {
hub.UltraApplyConfig(cfg, true) hub.UltraApplyConfig(cfg, true)
patchSelectGroup() patchSelectGroup()
} }
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

@@ -13,8 +13,7 @@ import (
"github.com/metacubex/mihomo/adapter/provider" "github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/common/structure" "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"
@@ -114,7 +113,11 @@ func updateConfig(s *C.char, port C.longlong) {
configParams = params.Params configParams = params.Params
prof := decorationConfig(params.ProfilePath, params.Config) prof := decorationConfig(params.ProfilePath, params.Config)
currentConfig = prof currentConfig = prof
applyConfig() err = applyConfig()
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
bridge.SendToPort(i, "") bridge.SendToPort(i, "")
}() }()
} }
@@ -164,35 +167,33 @@ 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 }
}
err = selector.Set(proxyName) err = selector.Set(proxyName)
if err == nil { if err == nil {
log.Infoln("[Selector] %s selected %s", groupName, proxyName) log.Infoln("[Selector] %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 +208,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,
@@ -397,25 +398,25 @@ func updateExternalProvider(providerName *C.char, providerType *C.char, port C.l
return return
} }
case "MMDB": case "MMDB":
err := mmdb.DownloadMMDB(constant.Path.Resolve(providerNameString)) err := updater.UpdateMMDB(constant.Path.Resolve(providerNameString))
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(providerNameString))
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(providerNameString))
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(providerNameString))
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return

View File

@@ -1,5 +1,3 @@
//go:build android
package main package main
import "C" import "C"
@@ -21,11 +19,17 @@ type AndroidProps struct {
SystemProxy bool `json:"systemProxy"` SystemProxy bool `json:"systemProxy"`
} }
var androidProps AndroidProps type State struct {
AndroidProps
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 +37,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

@@ -128,7 +128,7 @@ class ApplicationState extends State<Application> {
globalState.groupsUpdateTimer ??= Timer.periodic( globalState.groupsUpdateTimer ??= Timer.periodic(
httpTimeoutDuration, httpTimeoutDuration,
(timer) async { (timer) async {
await globalState.appController.updateGroups(); await globalState.appController.updateGroupDebounce();
}, },
); );
} }
@@ -136,7 +136,7 @@ class ApplicationState extends State<Application> {
@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,

View File

@@ -128,8 +128,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 +141,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();
}); });
} }
@@ -237,19 +240,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

@@ -5499,29 +5499,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,

View File

@@ -8,7 +8,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);

View File

@@ -85,7 +85,7 @@ 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) {
try { try {
final response = await _dio final response = await _dio

View File

@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
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';
@@ -17,6 +18,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 +31,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 +50,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();
} }
} }
@@ -108,24 +116,25 @@ 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 {
@@ -192,8 +201,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();
@@ -384,6 +404,10 @@ class AppController {
addProfileFormFile() async { addProfileFormFile() async {
final platformFile = await globalState.safeRun(picker.pickerConfigFile); final platformFile = await globalState.safeRun(picker.pickerConfigFile);
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 +416,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}",
@@ -456,6 +476,8 @@ 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] ?? '') ??
'';
} }
} }

View File

@@ -111,49 +111,49 @@ class _AccessFragmentState extends State<AccessFragment> {
); );
} }
Widget _buildSelectedAllButton({ // Widget _buildSelectedAllButton({
required bool isAccessControl, // 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 AbsorbPointer(
absorbing: !isAccessControl, // absorbing: !isAccessControl,
child: FloatingActionButton( // child: FloatingActionButton(
tooltip: tooltip, // tooltip: tooltip,
onPressed: () { // onPressed: () {
final config = globalState.appController.config; // final config = globalState.appController.config;
final isAccept = // final isAccept =
config.accessControl.mode == AccessControlMode.acceptSelected; // config.accessControl.mode == AccessControlMode.acceptSelected;
//
if (isSelectedAll) { // if (isSelectedAll) {
config.accessControl = switch (isAccept) { // config.accessControl = switch (isAccept) {
true => config.accessControl.copyWith( // true => config.accessControl.copyWith(
acceptList: [], // acceptList: [],
), // ),
false => config.accessControl.copyWith( // false => config.accessControl.copyWith(
rejectList: [], // rejectList: [],
), // ),
}; // };
} else { // } else {
config.accessControl = switch (isAccept) { // config.accessControl = switch (isAccept) {
true => config.accessControl.copyWith( // true => config.accessControl.copyWith(
acceptList: allValueList, // acceptList: allValueList,
), // ),
false => config.accessControl.copyWith( // false => config.accessControl.copyWith(
rejectList: allValueList, // rejectList: allValueList,
), // ),
}; // };
} // }
}, // },
child: isSelectedAll // child: isSelectedAll
? const Icon(Icons.deselect) // ? const Icon(Icons.deselect)
: const Icon(Icons.select_all), // : const Icon(Icons.select_all),
), // ),
); // );
} // }
Widget _buildPackageList() { Widget _buildPackageList() {
return ValueListenableBuilder( return ValueListenableBuilder(
@@ -207,139 +207,130 @@ class _AccessFragmentState extends State<AccessFragment> {
: appLocalizations.accessControlNotAllowDesc; : appLocalizations.accessControlNotAllowDesc;
return DisabledMask( return DisabledMask(
status: !isAccessControl, status: !isAccessControl,
child: FloatLayout( child: Column(
floatingWidget: FloatWrapper( children: [
child: _buildSelectedAllButton( AbsorbPointer(
isAccessControl: isAccessControl, absorbing: !isAccessControl,
isSelectedAll: valueList.length == packageNameList.length, child: Padding(
allValueList: packageNameList, padding: const EdgeInsets.only(
), top: 4,
), bottom: 4,
child: Column( left: 16,
children: [ right: 8,
AbsorbPointer( ),
absorbing: !isAccessControl, child: Row(
child: Padding( mainAxisAlignment: MainAxisAlignment.spaceBetween,
padding: const EdgeInsets.only( mainAxisSize: MainAxisSize.max,
top: 4, children: [
bottom: 4, Expanded(
left: 16, child: IntrinsicHeight(
right: 8, child: Column(
), mainAxisSize: MainAxisSize.max,
child: Row( crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
mainAxisSize: MainAxisSize.max, Expanded(
children: [ child: Row(
Expanded( children: [
child: IntrinsicHeight( Flexible(
child: Column( child: Text(
mainAxisSize: MainAxisSize.max, appLocalizations.selected,
crossAxisAlignment: CrossAxisAlignment.start, style: Theme.of(context)
children: [ .textTheme
Expanded( .labelLarge
child: Row( ?.copyWith(
children: [ color: Theme.of(context)
Flexible( .colorScheme
child: Text( .primary,
appLocalizations.selected,
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: Theme.of(context)
.colorScheme
.primary,
),
), ),
), ),
const Flexible( ),
child: SizedBox( const Flexible(
width: 8, child: SizedBox(
width: 8,
),
),
Flexible(
child: Text(
"${valueList.length}",
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: Theme.of(context)
.colorScheme
.primary,
), ),
), ),
Flexible( ),
child: Text( ],
"${valueList.length}",
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: Theme.of(context)
.colorScheme
.primary,
),
),
),
],
),
), ),
Flexible( ),
child: Text(describe), Flexible(
) child: Text(describe),
], )
), ],
), ),
), ),
Row( ),
mainAxisSize: MainAxisSize.min, Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisSize: MainAxisSize.min,
children: [ mainAxisAlignment: MainAxisAlignment.end,
Flexible( children: [
child: _buildSearchButton(currentPackages)), Flexible(
Flexible(child: _buildFilterSystemAppButton()), child: _buildSearchButton(currentPackages)),
Flexible(child: _buildAppProxyModePopup()), Flexible(child: _buildFilterSystemAppButton()),
], Flexible(child: _buildAppProxyModePopup()),
), ],
], ),
), ],
), ),
), ),
Expanded( ),
flex: 1, Expanded(
child: FadeBox( flex: 1,
key: const Key("fade_box"), child: FadeBox(
child: currentPackages.isEmpty key: const Key("fade_box"),
? const Center( child: currentPackages.isEmpty
child: CircularProgressIndicator(), ? const Center(
) child: CircularProgressIndicator(),
: ListView.builder( )
itemCount: currentPackages.length, : ListView.builder(
itemBuilder: (_, index) { itemCount: currentPackages.length,
final package = currentPackages[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,
} );
}, }
); },
}, );
), },
), ),
), ),
], ),
), ],
), ),
); );
}, },

View File

@@ -195,23 +195,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();
// },
// ),
// );
// },
// ),
], ],
); );
} }

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

@@ -18,48 +18,37 @@ 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;
_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;
if (cancelToken != null) { _preIsStart = isStart;
cancelToken!.cancel();
cancelToken = null;
}
ipInfoNotifier.value = null; ipInfoNotifier.value = null;
final ipInfo = await request.checkIp(cancelToken); final ipInfo = await request.checkIp();
if (ipInfo == null) { if (ipInfo == null) {
timeoutNotifier.value = true; timeoutNotifier.value = true;
return; return;
} else { } else {
timeoutNotifier.value = false; timeoutNotifier.value = false;
} }
_preIsStart = isStart;
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,
); );
@@ -74,6 +63,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
@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,

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,97 @@ class _EditProfileState extends State<EditProfile> {
}); });
} }
Future<FileInfo> _getFileInfo(path) async {
final file = File(path);
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.pickerConfigFile);
if (platformFile?.bytes == null) return;
fileData = platformFile?.bytes;
fileInfoNotifier.value = fileInfoNotifier.value?.copyWith(
size: fileData?.length ?? 0,
lastModified: DateTime.now(),
);
}
Widget _buildSubtitle() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 4,
),
ValueListenableBuilder<FileInfo?>(
valueListenable: fileInfoNotifier,
builder: (_, fileInfo, __) {
final height =
globalState.appController.measure.bodyMediumHeight + 4;
return SizedBox(
height: height,
child: FadeBox(
child: fileInfo == null
? SizedBox(
width: height,
height: height,
child: const CircularProgressIndicator(
strokeWidth: 2,
),
)
: 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,
),
],
),
],
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final items = [ final items = [
@@ -141,7 +249,11 @@ class _EditProfileState extends State<EditProfile> {
}, },
), ),
), ),
] ],
ListItem(
title: Text(appLocalizations.profile),
subtitle: _buildSubtitle(),
),
]; ];
return FloatLayout( return FloatLayout(
floatingWidget: FloatWrapper( floatingWidget: FloatWrapper(
@@ -158,17 +270,23 @@ class _EditProfileState extends State<EditProfile> {
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 16, vertical: 16,
), ),
child: ListView.separated( child: ScrollOverBuilder(
primary: true, builder: (isOver) {
itemBuilder: (_, index) { return ListView.separated(
return items[index]; padding: kMaterialListPadding.copyWith(
}, bottom: isOver ? 72 : 36,
separatorBuilder: (_, __) { ),
return const SizedBox( itemBuilder: (_, index) {
height: 24, return items[index];
},
separatorBuilder: (_, __) {
return const SizedBox(
height: 24,
);
},
itemCount: items.length,
); );
}, },
itemCount: items.length,
), ),
), ),
), ),

View File

@@ -1,6 +1,5 @@
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 +15,6 @@ enum ProfileActions {
edit, edit,
update, update,
delete, delete,
view,
} }
class ProfilesFragment extends StatefulWidget { class ProfilesFragment extends StatefulWidget {
@@ -27,7 +25,6 @@ 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 = []; List<GlobalObjectKey<_ProfileItemState>> profileItemKeys = [];
@@ -77,17 +74,6 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
); );
} }
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
hasPadding.dispose();
}
_changeProfile(String? id) async { _changeProfile(String? id) async {
final appController = globalState.appController; final appController = globalState.appController;
final config = appController.config; final config = appController.config;
@@ -140,41 +126,33 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
final columns = _getColumns(state.viewMode); final columns = _getColumns(state.viewMode);
return Align( return Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: NotificationListener<ScrollNotification>( child: ScrollOverBuilder(
onNotification: (scrollNotification) { builder: (isOver) {
hasPadding.value = return SingleChildScrollView(
scrollNotification.metrics.maxScrollExtent > 0; padding: EdgeInsets.only(
return true; left: 16,
}, right: 16,
child: ValueListenableBuilder( top: 16,
valueListenable: hasPadding, bottom: 16 + (isOver ? 72 : 0),
builder: (_, hasPadding, __) { ),
return SingleChildScrollView( child: Grid(
padding: EdgeInsets.only( mainAxisSpacing: 16,
left: 16, crossAxisSpacing: 16,
right: 16, crossAxisCount: columns,
top: 16, children: [
bottom: 16 + (hasPadding ? 72 : 0), for (int i = 0; i < state.profiles.length; i++)
), GridItem(
child: Grid( child: ProfileItem(
mainAxisSpacing: 16, key: profileItemKeys[i],
crossAxisSpacing: 16, profile: state.profiles[i],
crossAxisCount: columns, groupValue: state.currentProfileId,
children: [ onChanged: _changeProfile,
for (int i = 0; i < state.profiles.length; i++)
GridItem(
child: ProfileItem(
key: profileItemKeys[i],
profile: state.profiles[i],
groupValue: state.currentProfileId,
onChanged: _changeProfile,
),
), ),
], ),
), ],
); ),
}, );
), },
), ),
); );
}, },
@@ -204,7 +182,18 @@ class _ProfileItemState extends State<ProfileItem> {
final isUpdating = ValueNotifier<bool>(false); final isUpdating = ValueNotifier<bool>(false);
_handleDeleteProfile() async { _handleDeleteProfile() async {
globalState.appController.deleteProfile(widget.profile.id); globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(
text: appLocalizations.deleteProfileTip,
),
onTab: () async {
await globalState.appController.deleteProfile(widget.profile.id);
if(mounted){
Navigator.of(context).pop();
}
},
);
} }
_handleUpdateProfile() async { _handleUpdateProfile() async {
@@ -216,9 +205,8 @@ class _ProfileItemState extends State<ProfileItem> {
try { try {
final appController = globalState.appController; final appController = globalState.appController;
await appController.updateProfile(widget.profile); await appController.updateProfile(widget.profile);
if (widget.profile.id == appController.config.currentProfile?.id && if (widget.profile.id == appController.config.currentProfile?.id) {
!appController.appState.isStart) { globalState.appController.applyProfile(isPrue: true);
globalState.appController.rawApplyProfile();
} }
} catch (e) { } catch (e) {
isUpdating.value = false; isUpdating.value = false;
@@ -243,16 +231,6 @@ class _ProfileItemState extends State<ProfileItem> {
); );
} }
_handleViewProfile() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ViewProfile(
profile: widget.profile,
),
),
);
}
_buildTitle(Profile profile) { _buildTitle(Profile profile) {
final textTheme = context.textTheme; final textTheme = context.textTheme;
return Container( return Container(
@@ -315,82 +293,12 @@ class _ProfileItemState extends State<ProfileItem> {
children: [ children: [
Text( Text(
expireShow, expireShow,
style: textTheme.labelMedium?.toLighter, style: textTheme.labelMedium?.toLight,
), ),
], ],
) )
], ],
); );
// 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,
// );
}), }),
], ],
), ),
@@ -409,70 +317,62 @@ class _ProfileItemState extends State<ProfileItem> {
final groupValue = widget.groupValue; final groupValue = widget.groupValue;
final onChanged = widget.onChanged; 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: ValueListenableBuilder(
valueListenable: isUpdating, valueListenable: isUpdating,
builder: (_, isUpdating, ___) { builder: (_, isUpdating, ___) {
return FadeBox( return FadeBox(
child: isUpdating child: isUpdating
? const Padding( ? const Padding(
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
) )
: CommonPopupMenu<ProfileActions>( : CommonPopupMenu<ProfileActions>(
items: [ items: [
CommonPopupMenuItem(
action: ProfileActions.edit,
label: appLocalizations.edit,
iconData: Icons.edit,
),
if (profile.type == ProfileType.url)
CommonPopupMenuItem( CommonPopupMenuItem(
action: ProfileActions.edit, action: ProfileActions.update,
label: appLocalizations.edit, label: appLocalizations.update,
iconData: Icons.edit, iconData: Icons.sync,
), ),
if (profile.type == ProfileType.url) CommonPopupMenuItem(
CommonPopupMenuItem( action: ProfileActions.delete,
action: ProfileActions.update, label: appLocalizations.delete,
label: appLocalizations.update, iconData: Icons.delete,
iconData: Icons.sync, ),
), ],
CommonPopupMenuItem( onSelected: (ProfileActions? action) async {
action: ProfileActions.view, switch (action) {
label: appLocalizations.view, case ProfileActions.edit:
iconData: Icons.visibility, _handleShowEditExtendPage();
), break;
CommonPopupMenuItem( case ProfileActions.delete:
action: ProfileActions.delete, _handleDeleteProfile();
label: appLocalizations.delete, break;
iconData: Icons.delete, case ProfileActions.update:
), _handleUpdateProfile();
], break;
onSelected: (ProfileActions? action) async { case null:
switch (action) { break;
case ProfileActions.edit: }
_handleShowEditExtendPage(); },
break; ),
case ProfileActions.delete: );
_handleDeleteProfile();
break;
case ProfileActions.update:
_handleUpdateProfile();
break;
case ProfileActions.view:
_handleViewProfile();
break;
case null:
break;
}
},
));
}, },
), ),
), ),

View File

@@ -1,207 +0,0 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart';
import 'package:re_editor/re_editor.dart';
import 'package:re_highlight/languages/yaml.dart';
import 'package:re_highlight/styles/intellij-light.dart';
class ViewProfile extends StatefulWidget {
final Profile profile;
const ViewProfile({
super.key,
required this.profile,
});
@override
State<ViewProfile> createState() => _ViewProfileState();
}
class _ViewProfileState extends State<ViewProfile> {
bool readOnly = true;
CodeLineEditingController? controller;
final contentNotifier = ValueNotifier<String>("");
final key = GlobalKey<CommonScaffoldState>();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final profilePath = await appPath.getProfilePath(widget.profile.id);
if (profilePath == null) {
return;
}
final file = File(profilePath);
final text = await file.readAsString();
contentNotifier.value = text;
});
}
@override
void dispose() {
super.dispose();
contentNotifier.dispose();
controller?.dispose();
}
Profile get profile => widget.profile;
_handleChangeReadOnly() async {
if (readOnly == true) {
setState(() {
readOnly = false;
});
} else {
final text = controller?.text;
if (text == null || text == contentNotifier.value) {
setState(() {
readOnly = true;
});
return;
}
contentNotifier.value = text;
final newProfile = await key.currentState?.loadingRun<Profile>(() async {
return await profile.saveFileWithString(text);
});
if (newProfile == null) return;
globalState.appController.config.setProfile(newProfile);
setState(() {
readOnly = true;
});
}
}
@override
Widget build(BuildContext context) {
return CommonScaffold(
key: key,
actions: [
IconButton(
onPressed: controller?.undo,
icon: const Icon(Icons.undo),
),
IconButton(
onPressed: controller?.redo,
icon: const Icon(Icons.redo),
),
if (!widget.profile.realAutoUpdate)
IconButton(
onPressed: _handleChangeReadOnly,
icon: readOnly ? const Icon(Icons.edit) : const Icon(Icons.save),
),
const SizedBox(
width: 8,
)
],
body: ValueListenableBuilder(
valueListenable: contentNotifier,
builder: (_, value, __) {
if (value.isEmpty) return Container();
controller = CodeLineEditingController.fromText(value);
return CodeEditor(
autofocus: false,
readOnly: readOnly,
scrollbarBuilder: (context, child, details) {
return Scrollbar(
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,
),
),
);
},
),
title: widget.profile.label ?? widget.profile.id,
);
}
}
class ContextMenuItemWidget extends PopupMenuItem<void> {
ContextMenuItemWidget({
super.key,
required String text,
required VoidCallback super.onTap,
}) : super(child: Text(text));
}
class ContextMenuControllerImpl implements SelectionToolbarController {
const ContextMenuControllerImpl();
@override
void hide(BuildContext context) {}
@override
void show({
required BuildContext context,
required CodeLineEditingController controller,
required TextSelectionToolbarAnchors anchors,
Rect? renderRect,
required LayerLink layerLink,
required ValueNotifier<bool> visibility,
}) {
if (controller.selectedText.isEmpty) {
return;
}
showMenu(
context: context,
position: RelativeRect.fromSize(
(anchors.secondaryAnchor ?? anchors.primaryAnchor) &
const Size(150, double.infinity),
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,
),
],
);
}
}

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,
}); });
@@ -91,24 +91,31 @@ class ProxyCard extends StatelessWidget {
} }
} }
_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 +124,125 @@ 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 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: [
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

@@ -8,17 +8,18 @@ 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);
}, },
); );
} }
@@ -42,8 +43,7 @@ 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) { for (final proxy in proxies) {
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,
@@ -70,6 +70,6 @@ double getScrollToSelectedOffset({
(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

@@ -140,17 +140,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 +205,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();
@@ -393,6 +392,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 +421,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 +439,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: Text(
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
" · $value", " · $currentGroupName",
style: context style: context
.textTheme.labelMedium?.toLight, .textTheme.labelMedium?.toLight,
), ),

View File

@@ -262,54 +262,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(
@@ -332,16 +288,47 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
columns: globalState.appController.columns, columns: globalState.appController.columns,
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,17 +22,6 @@ class GeoItem {
}); });
} }
@immutable
class FileInfo {
final String size;
final DateTime lastModified;
const FileInfo({
required this.size,
required this.lastModified,
});
}
class Resources extends StatefulWidget { class Resources extends StatefulWidget {
const Resources({super.key}); const Resources({super.key});
@@ -196,27 +185,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 +213,7 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
), ),
) )
: Text( : Text(
_buildFileInfoDesc(snapshot.data!), snapshot.data!.desc,
), ),
), ),
); );

View File

@@ -216,5 +216,8 @@
"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?"
} }

View File

@@ -216,5 +216,8 @@
"externalLink": "外部链接", "externalLink": "外部链接",
"otherContributors": "其他贡献者", "otherContributors": "其他贡献者",
"autoCloseConnections": "自动关闭连接", "autoCloseConnections": "自动关闭连接",
"autoCloseConnectionsDesc": "切换节点后自动关闭连接" "autoCloseConnectionsDesc": "切换节点后自动关闭连接",
"onlyStatisticsProxy": "仅统计代理",
"onlyStatisticsProxyDesc": "开启后,将只统计代理流量",
"deleteProfileTip": "确定要删除当前配置吗?"
} }

View File

@@ -115,6 +115,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"),
@@ -208,6 +210,10 @@ 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"),
"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"),

View File

@@ -96,6 +96,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("直连"),
@@ -170,6 +171,9 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"), MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"), "nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"), "oneColumn": 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("出站模式"),

View File

@@ -2229,6 +2229,36 @@ 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: [],
);
}
} }
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> { class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -55,7 +55,7 @@ class AppState with ChangeNotifier {
_delayMap = {}, _delayMap = {},
_groups = [], _groups = [],
_isCompatible = isCompatible, _isCompatible = isCompatible,
_systemColorSchemes = SystemColorSchemes(); _systemColorSchemes = const SystemColorSchemes();
String get currentLabel => _currentLabel; String get currentLabel => _currentLabel;
@@ -109,7 +109,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 +120,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 +142,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 +295,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();
} }

View File

@@ -26,14 +26,16 @@ class AccessControl with _$AccessControl {
} }
@freezed @freezed
class Props with _$Props { class CoreState with _$CoreState {
const factory Props({ const factory CoreState({
AccessControl? accessControl, AccessControl? accessControl,
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
@@ -79,6 +81,7 @@ class Config extends ChangeNotifier {
int _proxiesColumns; int _proxiesColumns;
String _testUrl; String _testUrl;
WindowProps _windowProps; WindowProps _windowProps;
bool _onlyProxy;
Config() Config()
: _profiles = [], : _profiles = [],
@@ -103,7 +106,8 @@ class Config extends ChangeNotifier {
_proxyCardType = ProxyCardType.expand, _proxyCardType = ProxyCardType.expand,
_windowProps = defaultWindowProps, _windowProps = defaultWindowProps,
_proxiesType = ProxiesType.tab, _proxiesType = ProxiesType.tab,
_proxiesColumns = 2; _proxiesColumns = 2,
_onlyProxy = false;
deleteProfileById(String id) { deleteProfileById(String id) {
_profiles = profiles.where((element) => element.id != id).toList(); _profiles = profiles.where((element) => element.id != id).toList();
@@ -407,6 +411,19 @@ 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) @JsonKey(defaultValue: false)
bool get isCloseConnections { bool get isCloseConnections {
return _isCloseConnections; return _isCloseConnections;

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

@@ -240,35 +240,43 @@ 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;
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,
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;
@@ -281,6 +289,8 @@ class _$PropsCopyWithImpl<$Res, $Val extends Props>
Object? accessControl = freezed, Object? accessControl = freezed,
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
@@ -295,6 +305,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,24 +330,30 @@ 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,
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')
@@ -338,8 +362,10 @@ class __$$PropsImplCopyWithImpl<$Res>
Object? accessControl = freezed, Object? accessControl = freezed,
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
@@ -352,20 +378,30 @@ 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.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;
@@ -373,51 +409,62 @@ class _$PropsImpl implements _Props {
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, 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.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, allowBypass,
Object.hash(runtimeType, accessControl, allowBypass, systemProxy); 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 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;
@@ -426,8 +473,12 @@ abstract class _Props implements Props {
@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

@@ -35,6 +35,7 @@ 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
..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) ??
@@ -69,6 +70,7 @@ 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,
'isCloseConnections': instance.isCloseConnections, 'isCloseConnections': instance.isCloseConnections,
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!, 'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!, 'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
@@ -129,20 +131,25 @@ const _$AccessControlModeEnumMap = {
AccessControlMode.rejectSelected: 'rejectSelected', AccessControlMode.rejectSelected: 'rejectSelected',
}; };
_$PropsImpl _$$PropsImplFromJson(Map<String, dynamic> json) => _$PropsImpl( _$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>),
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,
'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

@@ -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 _$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;
@@ -2012,6 +1968,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 +1987,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 +2009,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 +2026,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 +2055,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 +2076,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 +2093,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 +2116,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 +2127,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 +2142,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 +2155,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 +2184,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 +2195,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;
@@ -2800,3 +2783,154 @@ abstract class _ProxiesListHeaderSelectorState
_$ProxiesListHeaderSelectorStateImpl> _$ProxiesListHeaderSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
} }
/// @nodoc
mixin _$CurrentGroupProxyNameSelectorState {
String? get proxyName => throw _privateConstructorUsedError;
String? get proxyName2 => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$CurrentGroupProxyNameSelectorStateCopyWith<
CurrentGroupProxyNameSelectorState>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $CurrentGroupProxyNameSelectorStateCopyWith<$Res> {
factory $CurrentGroupProxyNameSelectorStateCopyWith(
CurrentGroupProxyNameSelectorState value,
$Res Function(CurrentGroupProxyNameSelectorState) then) =
_$CurrentGroupProxyNameSelectorStateCopyWithImpl<$Res,
CurrentGroupProxyNameSelectorState>;
@useResult
$Res call({String? proxyName, String? proxyName2});
}
/// @nodoc
class _$CurrentGroupProxyNameSelectorStateCopyWithImpl<$Res,
$Val extends CurrentGroupProxyNameSelectorState>
implements $CurrentGroupProxyNameSelectorStateCopyWith<$Res> {
_$CurrentGroupProxyNameSelectorStateCopyWithImpl(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? proxyName = freezed,
Object? proxyName2 = freezed,
}) {
return _then(_value.copyWith(
proxyName: freezed == proxyName
? _value.proxyName
: proxyName // ignore: cast_nullable_to_non_nullable
as String?,
proxyName2: freezed == proxyName2
? _value.proxyName2
: proxyName2 // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$CurrentGroupProxyNameSelectorStateImplCopyWith<$Res>
implements $CurrentGroupProxyNameSelectorStateCopyWith<$Res> {
factory _$$CurrentGroupProxyNameSelectorStateImplCopyWith(
_$CurrentGroupProxyNameSelectorStateImpl value,
$Res Function(_$CurrentGroupProxyNameSelectorStateImpl) then) =
__$$CurrentGroupProxyNameSelectorStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String? proxyName, String? proxyName2});
}
/// @nodoc
class __$$CurrentGroupProxyNameSelectorStateImplCopyWithImpl<$Res>
extends _$CurrentGroupProxyNameSelectorStateCopyWithImpl<$Res,
_$CurrentGroupProxyNameSelectorStateImpl>
implements _$$CurrentGroupProxyNameSelectorStateImplCopyWith<$Res> {
__$$CurrentGroupProxyNameSelectorStateImplCopyWithImpl(
_$CurrentGroupProxyNameSelectorStateImpl _value,
$Res Function(_$CurrentGroupProxyNameSelectorStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? proxyName = freezed,
Object? proxyName2 = freezed,
}) {
return _then(_$CurrentGroupProxyNameSelectorStateImpl(
proxyName: freezed == proxyName
? _value.proxyName
: proxyName // ignore: cast_nullable_to_non_nullable
as String?,
proxyName2: freezed == proxyName2
? _value.proxyName2
: proxyName2 // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
class _$CurrentGroupProxyNameSelectorStateImpl
implements _CurrentGroupProxyNameSelectorState {
const _$CurrentGroupProxyNameSelectorStateImpl(
{required this.proxyName, required this.proxyName2});
@override
final String? proxyName;
@override
final String? proxyName2;
@override
String toString() {
return 'CurrentGroupProxyNameSelectorState(proxyName: $proxyName, proxyName2: $proxyName2)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$CurrentGroupProxyNameSelectorStateImpl &&
(identical(other.proxyName, proxyName) ||
other.proxyName == proxyName) &&
(identical(other.proxyName2, proxyName2) ||
other.proxyName2 == proxyName2));
}
@override
int get hashCode => Object.hash(runtimeType, proxyName, proxyName2);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$CurrentGroupProxyNameSelectorStateImplCopyWith<
_$CurrentGroupProxyNameSelectorStateImpl>
get copyWith => __$$CurrentGroupProxyNameSelectorStateImplCopyWithImpl<
_$CurrentGroupProxyNameSelectorStateImpl>(this, _$identity);
}
abstract class _CurrentGroupProxyNameSelectorState
implements CurrentGroupProxyNameSelectorState {
const factory _CurrentGroupProxyNameSelectorState(
{required final String? proxyName,
required final String? proxyName2}) =
_$CurrentGroupProxyNameSelectorStateImpl;
@override
String? get proxyName;
@override
String? get proxyName2;
@override
@JsonKey(ignore: true)
_$$CurrentGroupProxyNameSelectorStateImplCopyWith<
_$CurrentGroupProxyNameSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@@ -14,3 +14,4 @@ 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

@@ -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({

View File

@@ -16,10 +16,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;
} }
@@ -117,6 +115,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;
@@ -153,3 +152,11 @@ class ProxiesListHeaderSelectorState with _$ProxiesListHeaderSelectorState {
required int currentIndex, required int currentIndex,
}) = _ProxiesListHeaderSelectorState; }) = _ProxiesListHeaderSelectorState;
} }
@freezed
class CurrentGroupProxyNameSelectorState with _$CurrentGroupProxyNameSelectorState {
const factory CurrentGroupProxyNameSelectorState({
required String? proxyName,
required String? proxyName2,
}) = _CurrentGroupProxyNameSelectorState;
}

View File

@@ -52,6 +52,19 @@ 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,
fontWeight: FontWeight.w600,
),
unselectedLabelTextStyle: context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
destinations: navigationItems destinations: navigationItems
.map( .map(
(e) => NavigationRailDestination( (e) => NavigationRailDestination(
@@ -64,7 +77,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,13 @@ class App {
}); });
} }
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),
}); });
} }

View File

@@ -72,13 +72,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 +79,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 {
@@ -124,15 +107,6 @@ 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,
@@ -160,12 +134,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 +171,7 @@ class GlobalState {
proxyName: proxyName, proxyName: proxyName,
), ),
); );
if(config.isCloseConnections){ if (config.isCloseConnections) {
clashCore.closeConnections(); clashCore.closeConnections();
} }
} }

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,

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

@@ -0,0 +1,40 @@
import 'package:flutter/material.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);
},
),
);
}
}

View File

@@ -38,10 +38,7 @@ class InfoHeader extends StatelessWidget {
if (info.iconData != null) ...[ if (info.iconData != null) ...[
Icon( Icon(
info.iconData, info.iconData,
color: Theme color: Theme.of(context).colorScheme.primary,
.of(context)
.colorScheme
.primary,
), ),
const SizedBox( const SizedBox(
width: 8, width: 8,
@@ -53,10 +50,7 @@ class InfoHeader extends StatelessWidget {
info.label, info.label,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme style: Theme.of(context).textTheme.titleMedium,
.of(context)
.textTheme
.titleMedium,
), ),
), ),
), ),
@@ -86,6 +80,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 +90,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 +113,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 +122,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
@@ -167,29 +158,31 @@ class CommonCard extends StatelessWidget {
], ],
); );
} }
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 +204,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

@@ -3,24 +3,42 @@ import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/proxy.dart'; import 'package:fl_clash/plugins/proxy.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';
class ClashMessageContainer extends StatefulWidget { class ClashContainer extends StatefulWidget {
final Widget child; final Widget child;
const ClashMessageContainer({ const ClashContainer({
super.key, super.key,
required this.child, required this.child,
}); });
@override @override
State<ClashMessageContainer> createState() => _ClashMessageContainerState(); State<ClashContainer> createState() => _ClashContainerState();
} }
class _ClashMessageContainerState extends State<ClashMessageContainer> class _ClashContainerState extends State<ClashContainer>
with AppMessageListener { 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,
),
builder: (__, state, child) {
clashCore.setState(state);
return child!;
},
child: child,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return widget.child; return _updateCoreState(widget.child);
} }
@override @override
@@ -60,22 +78,19 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
final currentSelectedMap = appController.config.currentSelectedMap; final currentSelectedMap = appController.config.currentSelectedMap;
final proxyName = currentSelectedMap[groupName]; final proxyName = currentSelectedMap[groupName];
if (proxyName == null) return; if (proxyName == null) return;
globalState.changeProxy( appController.changeProxy(
config: appController.config,
groupName: groupName, groupName: groupName,
proxyName: proxyName, proxyName: proxyName,
); );
appController.addCheckIpNumDebounce();
super.onLoaded(proxyName); super.onLoaded(proxyName);
} }
@override @override
void onStarted(String runTime) { Future<void> onStarted(String runTime) async {
super.onStarted(runTime); super.onStarted(runTime);
proxy?.updateStartTime(); proxy?.updateStartTime();
final appController = globalState.appController; final appController = globalState.appController;
appController.rawApplyProfile().then((_) { await appController.applyProfile(isPrue: true);
appController.addCheckIpNumDebounce(); 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

View File

@@ -68,7 +68,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 +86,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

@@ -17,10 +17,11 @@ 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';

View File

@@ -525,22 +525,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
isolate_contactor:
dependency: transitive
description:
name: isolate_contactor
sha256: f1be0a90f91e4309ef37cc45280b2a84e769e848aae378318dd3dd263cfc482a
url: "https://pub.dev"
source: hosted
version: "4.2.0"
isolate_manager:
dependency: transitive
description:
name: isolate_manager
sha256: "8fb916c4444fd408f089448f904f083ac3e169ea1789fd4d987b25809af92188"
url: "https://pub.dev"
source: hosted
version: "4.3.1"
jovial_misc: jovial_misc:
dependency: transitive dependency: transitive
description: description:
@@ -844,22 +828,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.1" version: "3.2.1"
re_editor:
dependency: "direct main"
description:
name: re_editor
sha256: db7a82e95f0f74301e85d4d5c805a8b8a5ba43d6c0d26673b7e35dc011f06635
url: "https://pub.dev"
source: hosted
version: "0.3.0"
re_highlight:
dependency: "direct main"
description:
name: re_highlight
sha256: "6c4ac3f76f939fb7ca9df013df98526634e17d8f7460e028bd23a035870024f2"
url: "https://pub.dev"
source: hosted
version: "0.0.3"
screen_retriever: screen_retriever:
dependency: transitive dependency: transitive
description: description:

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.49+202407311
environment: environment:
sdk: '>=3.1.0 <4.0.0' sdk: '>=3.1.0 <4.0.0'

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