Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75af47aead | ||
|
|
8dafe3b0ec | ||
|
|
813198a21d | ||
|
|
68dd262fef | ||
|
|
5ef020db73 | ||
|
|
e3c9035903 |
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@@ -87,7 +87,7 @@ jobs:
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.x'
|
||||
flutter-version: 3.22.x
|
||||
channel: 'stable'
|
||||
cache: true
|
||||
|
||||
@@ -136,18 +136,25 @@ jobs:
|
||||
gitchangelog "${pre}.." >> release.md 2>&1 || echo "Error in gitchangelog"
|
||||
echo -e "\n\n</details>" >> release.md
|
||||
fi
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ./dist/*
|
||||
body_path: './release.md'
|
||||
|
||||
- name: Create Fdroid Source Dir
|
||||
run: |
|
||||
mkdir -p ./tmp
|
||||
cp ./dist/*android-arm64-v8a* ./tmp/ || true
|
||||
echo "Files copied successfully"
|
||||
|
||||
- name: Push to fdroid repo
|
||||
uses: cpina/github-action-push-to-another-repository@v1.7.2
|
||||
env:
|
||||
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
|
||||
with:
|
||||
source-directory: ./dist/
|
||||
source-directory: ./tmp/
|
||||
destination-github-username: chen08209
|
||||
destination-repository-name: FlClash-fdroid-repo
|
||||
user-name: 'github-actions[bot]'
|
||||
|
||||
@@ -102,6 +102,9 @@ flutter {
|
||||
dependencies {
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation 'com.google.code.gson:gson:2.10'
|
||||
implementation("com.android.tools.smali:smali-dexlib2:3.0.7") {
|
||||
exclude group: "com.google.guava", module: "guava"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.follow.clash.models
|
||||
|
||||
import java.util.Date
|
||||
|
||||
data class Package(
|
||||
val packageName: String,
|
||||
val label: String,
|
||||
val isSystem:Boolean
|
||||
val isSystem: Boolean,
|
||||
val firstInstallTime: Long,
|
||||
)
|
||||
|
||||
@@ -6,11 +6,13 @@ import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.ComponentInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import com.android.tools.smali.dexlib2.dexbacked.DexBackedDexFile
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.content.getSystemService
|
||||
import com.follow.clash.GlobalState
|
||||
@@ -32,6 +34,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
|
||||
class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {
|
||||
@@ -49,6 +52,62 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
private var connectivity: ConnectivityManager? = null
|
||||
|
||||
private val iconMap = mutableMapOf<String, String?>()
|
||||
private val packages = mutableListOf<Package>()
|
||||
|
||||
private val skipPrefixList = listOf(
|
||||
"com.google",
|
||||
"com.android.chrome",
|
||||
"com.android.vending",
|
||||
"com.microsoft",
|
||||
"com.apple",
|
||||
"com.zhiliaoapp.musically", // Banned by China
|
||||
)
|
||||
|
||||
private val chinaAppPrefixList = listOf(
|
||||
"com.tencent",
|
||||
"com.alibaba",
|
||||
"com.umeng",
|
||||
"com.qihoo",
|
||||
"com.ali",
|
||||
"com.alipay",
|
||||
"com.amap",
|
||||
"com.sina",
|
||||
"com.weibo",
|
||||
"com.vivo",
|
||||
"com.xiaomi",
|
||||
"com.huawei",
|
||||
"com.taobao",
|
||||
"com.secneo",
|
||||
"s.h.e.l.l",
|
||||
"com.stub",
|
||||
"com.kiwisec",
|
||||
"com.secshell",
|
||||
"com.wrapper",
|
||||
"cn.securitystack",
|
||||
"com.mogosec",
|
||||
"com.secoen",
|
||||
"com.netease",
|
||||
"com.mx",
|
||||
"com.qq.e",
|
||||
"com.baidu",
|
||||
"com.bytedance",
|
||||
"com.bugly",
|
||||
"com.miui",
|
||||
"com.oppo",
|
||||
"com.coloros",
|
||||
"com.iqoo",
|
||||
"com.meizu",
|
||||
"com.gionee",
|
||||
"cn.nubia",
|
||||
"com.oplus",
|
||||
"andes.oplus",
|
||||
"com.unionpay",
|
||||
"cn.wps"
|
||||
)
|
||||
|
||||
private val chinaAppRegex by lazy {
|
||||
("(" + chinaAppPrefixList.joinToString("|").replace(".", "\\.") + ").*").toRegex()
|
||||
}
|
||||
|
||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
scope = CoroutineScope(Dispatchers.Default)
|
||||
@@ -88,7 +147,13 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
|
||||
"getPackages" -> {
|
||||
scope.launch {
|
||||
result.success(getPackages())
|
||||
result.success(getPackagesToJson())
|
||||
}
|
||||
}
|
||||
|
||||
"getChinaPackageNames" -> {
|
||||
scope.launch {
|
||||
result.success(getChinaPackageNames())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,26 +313,106 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
return iconMap[packageName]
|
||||
}
|
||||
|
||||
private suspend fun getPackages(): String {
|
||||
return withContext(Dispatchers.Default) {
|
||||
val packageManager = context?.packageManager
|
||||
val packages: List<Package>? =
|
||||
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter {
|
||||
it.packageName != context?.packageName
|
||||
|| it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
||||
|| it.packageName == "android"
|
||||
private fun getPackages(): List<Package> {
|
||||
val packageManager = context?.packageManager
|
||||
if (packages.isNotEmpty()) return packages;
|
||||
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter {
|
||||
it.packageName != context?.packageName
|
||||
|| it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
||||
|| it.packageName == "android"
|
||||
|
||||
}?.map {
|
||||
Package(
|
||||
packageName = it.packageName,
|
||||
label = it.applicationInfo.loadLabel(packageManager).toString(),
|
||||
isSystem = (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 1
|
||||
)
|
||||
}
|
||||
}?.map {
|
||||
Package(
|
||||
packageName = it.packageName,
|
||||
label = it.applicationInfo.loadLabel(packageManager).toString(),
|
||||
isSystem = (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 1,
|
||||
firstInstallTime = it.firstInstallTime
|
||||
)
|
||||
}?.let { packages.addAll(it) }
|
||||
return packages;
|
||||
}
|
||||
|
||||
private suspend fun getPackagesToJson(): String {
|
||||
return withContext(Dispatchers.Default) {
|
||||
Gson().toJson(getPackages())
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getChinaPackageNames(): String {
|
||||
return withContext(Dispatchers.Default) {
|
||||
val packages: List<String> =
|
||||
getPackages().map { it.packageName }.filter { isChinaPackage(it) }
|
||||
Gson().toJson(packages)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isChinaPackage(packageName: String): Boolean {
|
||||
val packageManager = context?.packageManager ?: return false
|
||||
skipPrefixList.forEach {
|
||||
if (packageName == it || packageName.startsWith("$it.")) return false
|
||||
}
|
||||
val packageManagerFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
PackageManager.MATCH_UNINSTALLED_PACKAGES or PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
PackageManager.GET_UNINSTALLED_PACKAGES or PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
|
||||
}
|
||||
if (packageName.matches(chinaAppRegex)) {
|
||||
return true
|
||||
}
|
||||
try {
|
||||
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.PackageInfoFlags.of(packageManagerFlags.toLong())
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION") packageManager.getPackageInfo(
|
||||
packageName, packageManagerFlags
|
||||
)
|
||||
}
|
||||
mutableListOf<ComponentInfo>().apply {
|
||||
packageInfo.services?.let { addAll(it) }
|
||||
packageInfo.activities?.let { addAll(it) }
|
||||
packageInfo.receivers?.let { addAll(it) }
|
||||
packageInfo.providers?.let { addAll(it) }
|
||||
}.forEach {
|
||||
if (it.name.matches(chinaAppRegex)) return true
|
||||
}
|
||||
ZipFile(File(packageInfo.applicationInfo.publicSourceDir)).use {
|
||||
for (packageEntry in it.entries()) {
|
||||
if (packageEntry.name.startsWith("firebase-")) return false
|
||||
}
|
||||
for (packageEntry in it.entries()) {
|
||||
if (!(packageEntry.name.startsWith("classes") && packageEntry.name.endsWith(
|
||||
".dex"
|
||||
))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if (packageEntry.size > 15000000) {
|
||||
return true
|
||||
}
|
||||
val input = it.getInputStream(packageEntry).buffered()
|
||||
val dexFile = try {
|
||||
DexBackedDexFile.fromInputStream(null, input)
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
for (clazz in dexFile.classes) {
|
||||
val clazzName =
|
||||
clazz.type.substring(1, clazz.type.length - 1).replace("/", ".")
|
||||
.replace("$", ".")
|
||||
if (clazzName.matches(chinaAppRegex)) return true
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun requestGc() {
|
||||
channel.invokeMethod("gc", null)
|
||||
}
|
||||
|
||||
BIN
assets/fonts/Twemoji.Mozilla.ttf
Normal file
BIN
assets/fonts/Twemoji.Mozilla.ttf
Normal file
Binary file not shown.
Submodule core/Clash.Meta updated: fffdf84493...44d4b6dab2
@@ -3,6 +3,7 @@ package main
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
@@ -102,6 +103,12 @@ type ExternalProvider struct {
|
||||
UpdateAt time.Time `json:"update-at"`
|
||||
}
|
||||
|
||||
type ExternalProviders []ExternalProvider
|
||||
|
||||
func (a ExternalProviders) Len() int { return len(a) }
|
||||
func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
||||
func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
var b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
||||
|
||||
func restartExecutable(execPath string) {
|
||||
@@ -190,35 +197,67 @@ func getRawConfigWithId(id string) *config.RawConfig {
|
||||
return prof
|
||||
}
|
||||
|
||||
func getExternalProvidersRaw() map[string]ExternalProvider {
|
||||
externalProviders := make(map[string]ExternalProvider)
|
||||
func getExternalProvidersRaw() map[string]cp.Provider {
|
||||
eps := make(map[string]cp.Provider)
|
||||
for n, p := range tunnel.Providers() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*provider.ProxySetProvider)
|
||||
externalProviders[n] = ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Count: p.Count(),
|
||||
Path: p.Vehicle().Path(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
}
|
||||
eps[n] = p
|
||||
}
|
||||
}
|
||||
for n, p := range tunnel.RuleProviders() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*rp.RuleSetProvider)
|
||||
externalProviders[n] = ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Count: p.Count(),
|
||||
Path: p.Vehicle().Path(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
}
|
||||
eps[n] = p
|
||||
}
|
||||
}
|
||||
return externalProviders
|
||||
return eps
|
||||
}
|
||||
|
||||
func toExternalProvider(p cp.Provider) (*ExternalProvider, error) {
|
||||
switch p.(type) {
|
||||
case *provider.ProxySetProvider:
|
||||
psp := p.(*provider.ProxySetProvider)
|
||||
return &ExternalProvider{
|
||||
Name: psp.Name(),
|
||||
Type: psp.Type().String(),
|
||||
VehicleType: psp.VehicleType().String(),
|
||||
Count: psp.Count(),
|
||||
Path: psp.Vehicle().Path(),
|
||||
UpdateAt: psp.UpdatedAt,
|
||||
}, nil
|
||||
case *rp.RuleSetProvider:
|
||||
rsp := p.(*rp.RuleSetProvider)
|
||||
return &ExternalProvider{
|
||||
Name: rsp.Name(),
|
||||
Type: rsp.Type().String(),
|
||||
VehicleType: rsp.VehicleType().String(),
|
||||
Count: rsp.Count(),
|
||||
Path: rsp.Vehicle().Path(),
|
||||
UpdateAt: rsp.UpdatedAt,
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.New("not external provider")
|
||||
}
|
||||
}
|
||||
|
||||
func sideUpdateExternalProvider(p cp.Provider, bytes []byte) error {
|
||||
switch p.(type) {
|
||||
case *provider.ProxySetProvider:
|
||||
psp := p.(*provider.ProxySetProvider)
|
||||
elm, same, err := psp.SideUpdate(bytes)
|
||||
if err == nil && !same {
|
||||
psp.OnUpdate(elm)
|
||||
}
|
||||
return nil
|
||||
case rp.RuleSetProvider:
|
||||
rsp := p.(*rp.RuleSetProvider)
|
||||
elm, same, err := rsp.SideUpdate(bytes)
|
||||
if err == nil && !same {
|
||||
rsp.OnUpdate(elm)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.New("not external provider")
|
||||
}
|
||||
}
|
||||
|
||||
func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig {
|
||||
@@ -487,5 +526,6 @@ func applyConfig() error {
|
||||
hub.UltraApplyConfig(cfg, true)
|
||||
patchSelectGroup()
|
||||
}
|
||||
externalProviders = getExternalProvidersRaw()
|
||||
return err
|
||||
}
|
||||
|
||||
184
core/hub.go
184
core/hub.go
@@ -18,12 +18,12 @@ import (
|
||||
cp "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/hub/executor"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
rp "github.com/metacubex/mihomo/rules/provider"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||
"golang.org/x/net/context"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -32,6 +32,8 @@ var currentConfig = config.DefaultRawConfig()
|
||||
|
||||
var configParams = ConfigExtendedParams{}
|
||||
|
||||
var externalProviders = map[string]cp.Provider{}
|
||||
|
||||
var isInit = false
|
||||
|
||||
//export initClash
|
||||
@@ -311,34 +313,16 @@ func getProvider(name *C.char) *C.char {
|
||||
|
||||
//export getExternalProviders
|
||||
func getExternalProviders() *C.char {
|
||||
externalProviders := make(map[string]ExternalProvider)
|
||||
for n, p := range tunnel.Providers() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*provider.ProxySetProvider)
|
||||
externalProviders[n] = ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Count: p.Count(),
|
||||
Path: p.Vehicle().Path(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
}
|
||||
eps := make([]ExternalProvider, 0)
|
||||
for _, p := range externalProviders {
|
||||
externalProvider, err := toExternalProvider(p)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
eps = append(eps, *externalProvider)
|
||||
}
|
||||
for n, p := range tunnel.RuleProviders() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*rp.RuleSetProvider)
|
||||
externalProviders[n] = ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Count: p.Count(),
|
||||
Path: p.Vehicle().Path(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
data, err := json.Marshal(externalProviders)
|
||||
sort.Sort(ExternalProviders(eps))
|
||||
data, err := json.Marshal(eps)
|
||||
if err != nil {
|
||||
return C.CString("")
|
||||
}
|
||||
@@ -348,69 +332,48 @@ func getExternalProviders() *C.char {
|
||||
//export getExternalProvider
|
||||
func getExternalProvider(name *C.char) *C.char {
|
||||
externalProviderName := C.GoString(name)
|
||||
externalProviders := getExternalProvidersRaw()
|
||||
externalProvider, exist := externalProviders[externalProviderName]
|
||||
if !exist {
|
||||
return C.CString("")
|
||||
}
|
||||
data, err := json.Marshal(externalProvider)
|
||||
e, err := toExternalProvider(externalProvider)
|
||||
if err != nil {
|
||||
return C.CString("")
|
||||
}
|
||||
data, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return C.CString("")
|
||||
}
|
||||
return C.CString(string(data))
|
||||
}
|
||||
|
||||
//export updateExternalProvider
|
||||
func updateExternalProvider(providerName *C.char, providerType *C.char, port C.longlong) {
|
||||
//export updateGeoData
|
||||
func updateGeoData(geoType *C.char, geoName *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
providerNameString := C.GoString(providerName)
|
||||
providerTypeString := C.GoString(providerType)
|
||||
geoTypeString := C.GoString(geoType)
|
||||
geoNameString := C.GoString(geoName)
|
||||
go func() {
|
||||
switch providerTypeString {
|
||||
case "Proxy":
|
||||
providers := tunnel.Providers()
|
||||
proxyProvider, exist := providers[providerNameString].(*provider.ProxySetProvider)
|
||||
if !exist {
|
||||
bridge.SendToPort(i, "proxy provider is not exist")
|
||||
return
|
||||
}
|
||||
err := proxyProvider.Update()
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "Rule":
|
||||
providers := tunnel.RuleProviders()
|
||||
ruleProvider, exist := providers[providerNameString].(*rp.RuleSetProvider)
|
||||
if !exist {
|
||||
bridge.SendToPort(i, "rule provider is not exist")
|
||||
return
|
||||
}
|
||||
err := ruleProvider.Update()
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
switch geoTypeString {
|
||||
case "MMDB":
|
||||
err := updater.UpdateMMDB(constant.Path.Resolve(providerNameString))
|
||||
err := updater.UpdateMMDB(constant.Path.Resolve(geoNameString))
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "ASN":
|
||||
err := updater.UpdateASN(constant.Path.Resolve(providerNameString))
|
||||
err := updater.UpdateASN(constant.Path.Resolve(geoNameString))
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "GeoIp":
|
||||
err := updater.UpdateGeoIp(constant.Path.Resolve(providerNameString))
|
||||
err := updater.UpdateGeoIp(constant.Path.Resolve(geoNameString))
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "GeoSite":
|
||||
err := updater.UpdateGeoSite(constant.Path.Resolve(providerNameString))
|
||||
err := updater.UpdateGeoSite(constant.Path.Resolve(geoNameString))
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
@@ -420,65 +383,44 @@ func updateExternalProvider(providerName *C.char, providerType *C.char, port C.l
|
||||
}()
|
||||
}
|
||||
|
||||
//func sideLoadExternalProvider(providerName *C.char, providerType *C.char, data *C.char, port C.longlong) {
|
||||
// i := int64(port)
|
||||
// bytes := []byte(C.GoString(data))
|
||||
// providerNameString := C.GoString(providerName)
|
||||
// providerTypeString := C.GoString(providerType)
|
||||
// go func() {
|
||||
// switch providerTypeString {
|
||||
// case "Proxy":
|
||||
// providers := tunnel.Providers()
|
||||
// proxyProvider, exist := providers[providerNameString].(*provider.ProxySetProvider)
|
||||
// if exist {
|
||||
// bridge.SendToPort(i, "proxy provider is not exist")
|
||||
// return
|
||||
// }
|
||||
// err := proxyProvider.Update()
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "Rule":
|
||||
// providers := tunnel.RuleProviders()
|
||||
// ruleProvider, exist := providers[providerNameString].(*rp.RuleSetProvider)
|
||||
// if exist {
|
||||
// bridge.SendToPort(i, "proxy provider is not exist")
|
||||
// return
|
||||
// }
|
||||
// err := ruleProvider.Update()
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "MMDB":
|
||||
// err := updater.UpdateMMDB(constant.Path.Resolve(providerNameString))
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "ASN":
|
||||
// err := updater.UpdateASN(constant.Path.Resolve(providerNameString))
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "GeoIp":
|
||||
// err := updater.UpdateGeoIp(constant.Path.Resolve(providerNameString))
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "GeoSite":
|
||||
// err := updater.UpdateGeoSite(constant.Path.Resolve(providerNameString))
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// bridge.SendToPort(i, "")
|
||||
// }()
|
||||
//}
|
||||
//export updateExternalProvider
|
||||
func updateExternalProvider(providerName *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
providerNameString := C.GoString(providerName)
|
||||
go func() {
|
||||
externalProvider, exist := externalProviders[providerNameString]
|
||||
if !exist {
|
||||
bridge.SendToPort(i, "external provider is not exist")
|
||||
return
|
||||
}
|
||||
err := externalProvider.Update()
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
bridge.SendToPort(i, "")
|
||||
}()
|
||||
}
|
||||
|
||||
//export sideLoadExternalProvider
|
||||
func sideLoadExternalProvider(providerName *C.char, data *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
bytes := []byte(C.GoString(data))
|
||||
providerNameString := C.GoString(providerName)
|
||||
go func() {
|
||||
externalProvider, exist := externalProviders[providerNameString]
|
||||
if !exist {
|
||||
bridge.SendToPort(i, "external provider is not exist")
|
||||
return
|
||||
}
|
||||
err := sideUpdateExternalProvider(externalProvider, bytes)
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
bridge.SendToPort(i, "")
|
||||
}()
|
||||
}
|
||||
|
||||
//export initNativeApiBridge
|
||||
func initNativeApiBridge(api unsafe.Pointer) {
|
||||
|
||||
@@ -88,7 +88,6 @@ class ApplicationState extends State<Application> {
|
||||
}
|
||||
await globalState.appController.init();
|
||||
globalState.appController.initLink();
|
||||
_updateGroups();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,19 +119,6 @@ class ApplicationState extends State<Application> {
|
||||
});
|
||||
}
|
||||
|
||||
_updateGroups() {
|
||||
if (globalState.groupsUpdateTimer != null) {
|
||||
globalState.groupsUpdateTimer?.cancel();
|
||||
globalState.groupsUpdateTimer = null;
|
||||
}
|
||||
globalState.groupsUpdateTimer ??= Timer.periodic(
|
||||
httpTimeoutDuration,
|
||||
(timer) async {
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(context) {
|
||||
return AppStateContainer(
|
||||
@@ -149,7 +135,6 @@ class ApplicationState extends State<Application> {
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
_updateSystemColorSchemes(lightDynamic, darkDynamic);
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
navigatorKey: globalState.navigatorKey,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
|
||||
@@ -140,8 +140,7 @@ class ClashCore {
|
||||
clashFFI.freeCString(externalProvidersRaw);
|
||||
return Isolate.run<List<ExternalProvider>>(() {
|
||||
final externalProviders =
|
||||
(json.decode(externalProvidersRawString) as Map<String, dynamic>)
|
||||
.values
|
||||
(json.decode(externalProvidersRawString) as List<dynamic>)
|
||||
.map(
|
||||
(item) => ExternalProvider.fromJson(item),
|
||||
)
|
||||
@@ -150,7 +149,7 @@ class ClashCore {
|
||||
});
|
||||
}
|
||||
|
||||
ExternalProvider getExternalProvider(String externalProviderName) {
|
||||
ExternalProvider? getExternalProvider(String externalProviderName) {
|
||||
final externalProviderNameChar =
|
||||
externalProviderName.toNativeUtf8().cast<Char>();
|
||||
final externalProviderRaw =
|
||||
@@ -159,12 +158,37 @@ class ClashCore {
|
||||
final externalProviderRawString =
|
||||
externalProviderRaw.cast<Utf8>().toDartString();
|
||||
clashFFI.freeCString(externalProviderRaw);
|
||||
if(externalProviderRawString.isEmpty) return null;
|
||||
return ExternalProvider.fromJson(json.decode(externalProviderRawString));
|
||||
}
|
||||
|
||||
Future<String> updateExternalProvider({
|
||||
Future<String> updateGeoData({
|
||||
required String geoType,
|
||||
required String geoName,
|
||||
}) {
|
||||
final completer = Completer<String>();
|
||||
final receiver = ReceivePort();
|
||||
receiver.listen((message) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(message);
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final geoTypeChar = geoType.toNativeUtf8().cast<Char>();
|
||||
final geoNameChar = geoName.toNativeUtf8().cast<Char>();
|
||||
clashFFI.updateGeoData(
|
||||
geoTypeChar,
|
||||
geoNameChar,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(geoTypeChar);
|
||||
malloc.free(geoNameChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<String> sideLoadExternalProvider({
|
||||
required String providerName,
|
||||
required String providerType,
|
||||
required String data,
|
||||
}) {
|
||||
final completer = Completer<String>();
|
||||
final receiver = ReceivePort();
|
||||
@@ -175,14 +199,34 @@ class ClashCore {
|
||||
}
|
||||
});
|
||||
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
|
||||
final providerTypeChar = providerType.toNativeUtf8().cast<Char>();
|
||||
clashFFI.updateExternalProvider(
|
||||
final dataChar = data.toNativeUtf8().cast<Char>();
|
||||
clashFFI.sideLoadExternalProvider(
|
||||
providerNameChar,
|
||||
dataChar,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(providerNameChar);
|
||||
malloc.free(dataChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<String> updateExternalProvider({
|
||||
required String providerName,
|
||||
}) {
|
||||
final completer = Completer<String>();
|
||||
final receiver = ReceivePort();
|
||||
receiver.listen((message) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(message);
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
|
||||
clashFFI.updateExternalProvider(
|
||||
providerNameChar,
|
||||
providerTypeChar,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(providerNameChar);
|
||||
malloc.free(providerTypeChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
|
||||
@@ -5400,24 +5400,61 @@ class ClashFFI {
|
||||
late final _getExternalProvider = _getExternalProviderPtr
|
||||
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
|
||||
|
||||
void updateGeoData(
|
||||
ffi.Pointer<ffi.Char> geoType,
|
||||
ffi.Pointer<ffi.Char> geoName,
|
||||
int port,
|
||||
) {
|
||||
return _updateGeoData(
|
||||
geoType,
|
||||
geoName,
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _updateGeoDataPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
|
||||
ffi.LongLong)>>('updateGeoData');
|
||||
late final _updateGeoData = _updateGeoDataPtr.asFunction<
|
||||
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
||||
|
||||
void updateExternalProvider(
|
||||
ffi.Pointer<ffi.Char> providerName,
|
||||
ffi.Pointer<ffi.Char> providerType,
|
||||
int port,
|
||||
) {
|
||||
return _updateExternalProvider(
|
||||
providerName,
|
||||
providerType,
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _updateExternalProviderPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(
|
||||
ffi.Pointer<ffi.Char>, ffi.LongLong)>>('updateExternalProvider');
|
||||
late final _updateExternalProvider = _updateExternalProviderPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
|
||||
|
||||
void sideLoadExternalProvider(
|
||||
ffi.Pointer<ffi.Char> providerName,
|
||||
ffi.Pointer<ffi.Char> data,
|
||||
int port,
|
||||
) {
|
||||
return _sideLoadExternalProvider(
|
||||
providerName,
|
||||
data,
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _sideLoadExternalProviderPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
|
||||
ffi.LongLong)>>('updateExternalProvider');
|
||||
late final _updateExternalProvider = _updateExternalProviderPtr.asFunction<
|
||||
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
||||
ffi.LongLong)>>('sideLoadExternalProvider');
|
||||
late final _sideLoadExternalProvider =
|
||||
_sideLoadExternalProviderPtr.asFunction<
|
||||
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
||||
|
||||
void initNativeApiBridge(
|
||||
ffi.Pointer<ffi.Void> api,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:webdav_client/webdav_client.dart';
|
||||
|
||||
class DAVClient {
|
||||
@@ -34,8 +30,6 @@ class DAVClient {
|
||||
Future<bool> _ping() async {
|
||||
try {
|
||||
await client.ping();
|
||||
await client.mkdir("/$appName");
|
||||
await client.mkdir("/$appName/$profilesDirectoryName");
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
@@ -53,6 +47,7 @@ class DAVClient {
|
||||
}
|
||||
|
||||
Future<List<int>> recovery() async {
|
||||
await client.mkdir("$root");
|
||||
final data = await client.read(backupFile);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fl_clash/common/app_localizations.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/common/constant.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
import 'package:zxing2/qrcode.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
|
||||
@@ -84,7 +84,7 @@ class Other {
|
||||
if (charA == charB) {
|
||||
return sortByChar(a.substring(1), b.substring(1));
|
||||
} else {
|
||||
return charA.compareTo(charB);
|
||||
return charA.compareToLower(charB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,25 +192,21 @@ class Other {
|
||||
return ViewMode.desktop;
|
||||
}
|
||||
|
||||
int getColumns(ViewMode viewMode, int currentColumns) {
|
||||
final targetColumnsArray = viewModeColumnsMap[viewMode]!;
|
||||
if (targetColumnsArray.contains(currentColumns)) {
|
||||
return currentColumns;
|
||||
}
|
||||
return targetColumnsArray.first;
|
||||
}
|
||||
|
||||
String getColumnsTextForInt(int number){
|
||||
return switch(number){
|
||||
1 => appLocalizations.oneColumn,
|
||||
2 => appLocalizations.twoColumns,
|
||||
3 => appLocalizations.threeColumns,
|
||||
4 => appLocalizations.fourColumns,
|
||||
int() => throw UnimplementedError(),
|
||||
int getProxiesColumns(double viewWidth, ProxiesLayout proxiesLayout) {
|
||||
final columns = max((viewWidth / 300).ceil(), 2);
|
||||
return switch (proxiesLayout) {
|
||||
ProxiesLayout.tight => columns - 1,
|
||||
ProxiesLayout.standard => columns,
|
||||
ProxiesLayout.loose => columns + 1,
|
||||
};
|
||||
}
|
||||
|
||||
String getBackupFileName(){
|
||||
int getProfilesColumns(double viewWidth) {
|
||||
return max((viewWidth / 400).floor(), 1);
|
||||
}
|
||||
|
||||
String getBackupFileName() {
|
||||
return "${appName}_backup_${DateTime.now().show}.zip";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
@@ -13,9 +14,6 @@ class Request {
|
||||
|
||||
Request() {
|
||||
_dio = Dio();
|
||||
_dio.options = BaseOptions(
|
||||
headers: {"User-Agent": globalState.appController.clashConfig.globalUa},
|
||||
);
|
||||
_dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
@@ -52,11 +50,14 @@ class Request {
|
||||
.get(
|
||||
url,
|
||||
options: Options(
|
||||
headers: {
|
||||
"User-Agent": globalState.appController.clashConfig.globalUa
|
||||
},
|
||||
responseType: ResponseType.bytes,
|
||||
),
|
||||
)
|
||||
.timeout(
|
||||
httpTimeoutDuration * 2,
|
||||
httpTimeoutDuration * 6,
|
||||
);
|
||||
return response;
|
||||
}
|
||||
@@ -86,10 +87,13 @@ class Request {
|
||||
};
|
||||
|
||||
Future<IpInfo?> checkIp({CancelToken? cancelToken}) async {
|
||||
for (final source in _ipInfoSources.entries) {
|
||||
for (final source in _ipInfoSources.entries.toList()..shuffle(Random())) {
|
||||
try {
|
||||
final response = await _dio
|
||||
.get<Map<String, dynamic>>(source.key, cancelToken: cancelToken)
|
||||
.get<Map<String, dynamic>>(
|
||||
source.key,
|
||||
cancelToken: cancelToken,
|
||||
)
|
||||
.timeout(
|
||||
httpTimeoutDuration,
|
||||
);
|
||||
|
||||
@@ -2,4 +2,10 @@ extension StringExtension on String {
|
||||
bool get isUrl {
|
||||
return RegExp(r'^(http|https|ftp)://').hasMatch(this);
|
||||
}
|
||||
|
||||
int compareToLower(String other) {
|
||||
return toLowerCase().compareTo(
|
||||
other.toLowerCase(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:fl_clash/common/archive.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@@ -413,7 +415,6 @@ class AppController {
|
||||
addProfileFormURL(url);
|
||||
}
|
||||
|
||||
int get columns => other.getColumns(appState.viewMode, config.proxiesColumns);
|
||||
|
||||
updateViewWidth(double width) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -424,7 +425,10 @@ class AppController {
|
||||
List<Proxy> _sortOfName(List<Proxy> proxies) {
|
||||
return List.of(proxies)
|
||||
..sort(
|
||||
(a, b) => other.sortByChar(a.name, b.name),
|
||||
(a, b) => other.sortByChar(
|
||||
PinyinHelper.getPinyin(a.name),
|
||||
PinyinHelper.getPinyin(b.name),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@ enum TunStack { gvisor, system, mixed }
|
||||
|
||||
enum AccessControlMode { acceptSelected, rejectSelected }
|
||||
|
||||
enum AccessSortType { none, name, time }
|
||||
|
||||
enum ProfileType { file, url }
|
||||
|
||||
enum ResultType { success, error }
|
||||
@@ -84,4 +86,6 @@ enum CommonCardType { plain, filled }
|
||||
|
||||
enum ProxiesType { tab, list }
|
||||
|
||||
enum ProxiesLayout{ loose, standard, tight }
|
||||
|
||||
enum ProxyCardType { expand, shrink, min }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
@@ -6,15 +7,9 @@ import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
extension AccessControlExtension on AccessControl {
|
||||
List<String> get currentList => switch (mode) {
|
||||
AccessControlMode.acceptSelected => acceptList,
|
||||
AccessControlMode.rejectSelected => rejectList,
|
||||
};
|
||||
}
|
||||
|
||||
class AccessFragment extends StatefulWidget {
|
||||
const AccessFragment({super.key});
|
||||
|
||||
@@ -23,9 +18,13 @@ class AccessFragment extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AccessFragmentState extends State<AccessFragment> {
|
||||
List<String> acceptList = [];
|
||||
List<String> rejectList = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateInitList();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final appState = globalState.appController.appState;
|
||||
if (appState.packages.isEmpty) {
|
||||
@@ -36,297 +35,83 @@ class _AccessFragmentState extends State<AccessFragment> {
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildAppProxyModePopup() {
|
||||
final items = [
|
||||
CommonPopupMenuItem(
|
||||
action: AccessControlMode.rejectSelected,
|
||||
label: appLocalizations.blacklistMode,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: AccessControlMode.acceptSelected,
|
||||
label: appLocalizations.whitelistMode,
|
||||
),
|
||||
];
|
||||
return Selector<Config, AccessControlMode>(
|
||||
selector: (_, config) => config.accessControl.mode,
|
||||
builder: (context, mode, __) {
|
||||
return CommonPopupMenu<AccessControlMode>.radio(
|
||||
icon: Icon(
|
||||
Icons.mode_standby,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
items: items,
|
||||
onSelected: (value) {
|
||||
final config = context.read<Config>();
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
mode: value,
|
||||
);
|
||||
},
|
||||
selectedValue: mode,
|
||||
);
|
||||
},
|
||||
);
|
||||
_updateInitList() {
|
||||
final accessControl = globalState.appController.config.accessControl;
|
||||
acceptList = accessControl.acceptList;
|
||||
rejectList = accessControl.rejectList;
|
||||
}
|
||||
|
||||
Widget _buildFilterSystemAppButton() {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.accessControl.isFilterSystemApp,
|
||||
builder: (context, isFilterSystemApp, __) {
|
||||
final tooltip = isFilterSystemApp
|
||||
? appLocalizations.cancelFilterSystemApp
|
||||
: appLocalizations.filterSystemApp;
|
||||
return IconButton(
|
||||
tooltip: tooltip,
|
||||
onPressed: () {
|
||||
final config = context.read<Config>();
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
isFilterSystemApp: !isFilterSystemApp,
|
||||
);
|
||||
},
|
||||
icon: isFilterSystemApp
|
||||
? const Icon(Icons.filter_list_off)
|
||||
: const Icon(Icons.filter_list),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearchButton(List<Package> packages) {
|
||||
Widget _buildSearchButton() {
|
||||
return IconButton(
|
||||
tooltip: appLocalizations.search,
|
||||
onPressed: () {
|
||||
showSearch(
|
||||
context: context,
|
||||
delegate: AccessControlSearchDelegate(
|
||||
packages: packages,
|
||||
acceptList: acceptList,
|
||||
rejectList: rejectList,
|
||||
),
|
||||
).then((_) => {setState(() {})});
|
||||
).then((_) => setState(() {
|
||||
_updateInitList();
|
||||
}));
|
||||
},
|
||||
icon: const Icon(Icons.search),
|
||||
);
|
||||
}
|
||||
|
||||
// Widget _buildSelectedAllButton({
|
||||
// required bool isAccessControl,
|
||||
// required bool isSelectedAll,
|
||||
// required List<String> allValueList,
|
||||
// }) {
|
||||
// final tooltip = isSelectedAll
|
||||
// ? appLocalizations.cancelSelectAll
|
||||
// : appLocalizations.selectAll;
|
||||
// return AbsorbPointer(
|
||||
// absorbing: !isAccessControl,
|
||||
// child: FloatingActionButton(
|
||||
// tooltip: tooltip,
|
||||
// onPressed: () {
|
||||
// final config = globalState.appController.config;
|
||||
// final isAccept =
|
||||
// config.accessControl.mode == AccessControlMode.acceptSelected;
|
||||
//
|
||||
// if (isSelectedAll) {
|
||||
// config.accessControl = switch (isAccept) {
|
||||
// true => config.accessControl.copyWith(
|
||||
// acceptList: [],
|
||||
// ),
|
||||
// false => config.accessControl.copyWith(
|
||||
// rejectList: [],
|
||||
// ),
|
||||
// };
|
||||
// } else {
|
||||
// config.accessControl = switch (isAccept) {
|
||||
// true => config.accessControl.copyWith(
|
||||
// acceptList: allValueList,
|
||||
// ),
|
||||
// false => config.accessControl.copyWith(
|
||||
// rejectList: allValueList,
|
||||
// ),
|
||||
// };
|
||||
// }
|
||||
// },
|
||||
// child: isSelectedAll
|
||||
// ? const Icon(Icons.deselect)
|
||||
// : const Icon(Icons.select_all),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget _buildPackageList() {
|
||||
return Selector<AppState, List<Package>>(
|
||||
selector: (_, appState) => appState.packages,
|
||||
builder: (_, packages, ___) {
|
||||
final accessControl = globalState.appController.config.accessControl;
|
||||
final acceptList = accessControl.acceptList;
|
||||
final rejectList = accessControl.rejectList;
|
||||
final acceptPackages = packages.sorted((a, b) {
|
||||
final isSelectA = acceptList.contains(a.packageName);
|
||||
final isSelectB = acceptList.contains(b.packageName);
|
||||
if (isSelectA && isSelectB) return 0;
|
||||
if (isSelectA) return -1;
|
||||
if (isSelectB) return 1;
|
||||
return 0;
|
||||
});
|
||||
final rejectPackages = packages.sorted((a, b) {
|
||||
final isSelectA = rejectList.contains(a.packageName);
|
||||
final isSelectB = rejectList.contains(b.packageName);
|
||||
if (isSelectA && isSelectB) return 0;
|
||||
if (isSelectA) return -1;
|
||||
if (isSelectB) return 1;
|
||||
return 0;
|
||||
});
|
||||
return Selector<Config, PackageListSelectorState>(
|
||||
selector: (_, config) => PackageListSelectorState(
|
||||
accessControl: config.accessControl,
|
||||
isAccessControl: config.isAccessControl,
|
||||
),
|
||||
builder: (context, state, __) {
|
||||
final accessControl = state.accessControl;
|
||||
final isAccessControl = state.isAccessControl;
|
||||
final isFilterSystemApp = accessControl.isFilterSystemApp;
|
||||
final accessControlMode = accessControl.mode;
|
||||
final packages =
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? acceptPackages
|
||||
: rejectPackages;
|
||||
final currentList = accessControl.currentList;
|
||||
final currentPackages = isFilterSystemApp
|
||||
? packages
|
||||
.where((element) => element.isSystem == false)
|
||||
.toList()
|
||||
: packages;
|
||||
final packageNameList =
|
||||
currentPackages.map((e) => e.packageName).toList();
|
||||
final valueList = currentList.intersection(packageNameList);
|
||||
final describe =
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? appLocalizations.accessControlAllowDesc
|
||||
: appLocalizations.accessControlNotAllowDesc;
|
||||
return DisabledMask(
|
||||
status: !isAccessControl,
|
||||
child: Column(
|
||||
children: [
|
||||
AbsorbPointer(
|
||||
absorbing: !isAccessControl,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: 16,
|
||||
right: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
appLocalizations.selected,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Flexible(
|
||||
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(describe),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(
|
||||
child: _buildSearchButton(currentPackages)),
|
||||
Flexible(child: _buildFilterSystemAppButton()),
|
||||
Flexible(child: _buildAppProxyModePopup()),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: currentPackages.isEmpty
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: currentPackages.length,
|
||||
itemBuilder: (_, index) {
|
||||
final package = currentPackages[index];
|
||||
return PackageListItem(
|
||||
key: Key(package.packageName),
|
||||
package: package,
|
||||
value: valueList.contains(package.packageName),
|
||||
isActive: isAccessControl,
|
||||
onChanged: (value) {
|
||||
if (value == true) {
|
||||
valueList.add(package.packageName);
|
||||
} else {
|
||||
valueList.remove(package.packageName);
|
||||
}
|
||||
final config =
|
||||
globalState.appController.config;
|
||||
if (accessControlMode ==
|
||||
AccessControlMode.acceptSelected) {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
acceptList: valueList,
|
||||
);
|
||||
} else {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
rejectList: valueList,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
Widget _buildSelectedAllButton({
|
||||
required bool isSelectedAll,
|
||||
required List<String> allValueList,
|
||||
}) {
|
||||
final tooltip = isSelectedAll
|
||||
? appLocalizations.cancelSelectAll
|
||||
: appLocalizations.selectAll;
|
||||
return IconButton(
|
||||
tooltip: tooltip,
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
final isAccept =
|
||||
config.accessControl.mode == AccessControlMode.acceptSelected;
|
||||
if (isSelectedAll) {
|
||||
config.accessControl = switch (isAccept) {
|
||||
true => config.accessControl.copyWith(
|
||||
acceptList: [],
|
||||
),
|
||||
false => config.accessControl.copyWith(
|
||||
rejectList: [],
|
||||
),
|
||||
};
|
||||
} else {
|
||||
config.accessControl = switch (isAccept) {
|
||||
true => config.accessControl.copyWith(
|
||||
acceptList: allValueList,
|
||||
),
|
||||
false => config.accessControl.copyWith(
|
||||
rejectList: allValueList,
|
||||
),
|
||||
};
|
||||
}
|
||||
},
|
||||
icon: isSelectedAll
|
||||
? const Icon(Icons.deselect)
|
||||
: const Icon(Icons.select_all),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSettingButton() {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
showSheet(
|
||||
title: appLocalizations.proxiesSetting,
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return AccessControlWidget(
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.tune),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -363,7 +148,170 @@ class _AccessFragmentState extends State<AccessFragment> {
|
||||
],
|
||||
);
|
||||
},
|
||||
child: _buildPackageList(),
|
||||
child: Selector<AppState, List<Package>>(
|
||||
selector: (_, appState) => appState.packages,
|
||||
builder: (_, packages, ___) {
|
||||
return Selector2<AppState, Config, PackageListSelectorState>(
|
||||
selector: (_, appState, config) => PackageListSelectorState(
|
||||
accessControl: config.accessControl,
|
||||
isAccessControl: config.isAccessControl,
|
||||
packages: appState.packages,
|
||||
),
|
||||
builder: (context, state, __) {
|
||||
final accessControl = state.accessControl;
|
||||
final isAccessControl = state.isAccessControl;
|
||||
final accessControlMode = accessControl.mode;
|
||||
final packages = state.getList(
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? acceptList
|
||||
: rejectList,
|
||||
);
|
||||
final currentList = accessControl.currentList;
|
||||
final packageNameList =
|
||||
packages.map((e) => e.packageName).toList();
|
||||
final valueList = currentList.intersection(packageNameList);
|
||||
final describe =
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? appLocalizations.accessControlAllowDesc
|
||||
: appLocalizations.accessControlNotAllowDesc;
|
||||
return DisabledMask(
|
||||
status: !isAccessControl,
|
||||
child: Column(
|
||||
children: [
|
||||
AbsorbPointer(
|
||||
absorbing: !isAccessControl,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: 16,
|
||||
right: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
appLocalizations.selected,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Flexible(
|
||||
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(describe),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(
|
||||
child: _buildSearchButton(),
|
||||
),
|
||||
Flexible(
|
||||
child: _buildSelectedAllButton(
|
||||
isSelectedAll: valueList.length ==
|
||||
packageNameList.length,
|
||||
allValueList: packageNameList,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: _buildSettingButton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: packages.isEmpty
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: packages.length,
|
||||
itemBuilder: (_, index) {
|
||||
final package = packages[index];
|
||||
return PackageListItem(
|
||||
key: Key(package.packageName),
|
||||
package: package,
|
||||
value:
|
||||
valueList.contains(package.packageName),
|
||||
isActive: isAccessControl,
|
||||
onChanged: (value) {
|
||||
if (value == true) {
|
||||
valueList.add(package.packageName);
|
||||
} else {
|
||||
valueList.remove(package.packageName);
|
||||
}
|
||||
final config =
|
||||
globalState.appController.config;
|
||||
if (accessControlMode ==
|
||||
AccessControlMode.acceptSelected) {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
acceptList: valueList,
|
||||
);
|
||||
} else {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
rejectList: valueList,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -430,23 +378,14 @@ class PackageListItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
class AccessControlSearchDelegate extends SearchDelegate {
|
||||
final List<Package> packages;
|
||||
List<String> acceptList = [];
|
||||
List<String> rejectList = [];
|
||||
|
||||
AccessControlSearchDelegate({
|
||||
required this.packages,
|
||||
required this.acceptList,
|
||||
required this.rejectList,
|
||||
});
|
||||
|
||||
List<Package> get _results {
|
||||
final lowQuery = query.toLowerCase();
|
||||
return packages
|
||||
.where(
|
||||
(package) =>
|
||||
package.label.toLowerCase().contains(lowQuery) ||
|
||||
package.packageName.contains(lowQuery),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Widget>? buildActions(BuildContext context) {
|
||||
return [
|
||||
@@ -476,26 +415,39 @@ class AccessControlSearchDelegate extends SearchDelegate {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _packageList(List<Package> packages) {
|
||||
return Selector<Config, PackageListSelectorState>(
|
||||
selector: (_, config) => PackageListSelectorState(
|
||||
Widget _packageList() {
|
||||
final lowQuery = query.toLowerCase();
|
||||
return Selector2<AppState, Config, PackageListSelectorState>(
|
||||
selector: (_, appState, config) => PackageListSelectorState(
|
||||
packages: appState.packages,
|
||||
accessControl: config.accessControl,
|
||||
isAccessControl: config.isAccessControl,
|
||||
),
|
||||
builder: (context, state, __) {
|
||||
final accessControl = state.accessControl;
|
||||
final isAccessControl = state.isAccessControl;
|
||||
final accessControlMode = accessControl.mode;
|
||||
final packages = state.getList(
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? acceptList
|
||||
: rejectList,
|
||||
);
|
||||
final queryPackages = packages
|
||||
.where(
|
||||
(package) =>
|
||||
package.label.toLowerCase().contains(lowQuery) ||
|
||||
package.packageName.contains(lowQuery),
|
||||
)
|
||||
.toList();
|
||||
final isAccessControl = state.isAccessControl;
|
||||
final currentList = accessControl.currentList;
|
||||
final packageNameList =
|
||||
this.packages.map((e) => e.packageName).toList();
|
||||
final packageNameList = packages.map((e) => e.packageName).toList();
|
||||
final valueList = currentList.intersection(packageNameList);
|
||||
return DisabledMask(
|
||||
status: !isAccessControl,
|
||||
child: ListView.builder(
|
||||
itemCount: packages.length,
|
||||
itemCount: queryPackages.length,
|
||||
itemBuilder: (_, index) {
|
||||
final package = packages[index];
|
||||
final package = queryPackages[index];
|
||||
return PackageListItem(
|
||||
key: Key(package.packageName),
|
||||
package: package,
|
||||
@@ -533,6 +485,268 @@ class AccessControlSearchDelegate extends SearchDelegate {
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return _packageList(_results);
|
||||
return _packageList();
|
||||
}
|
||||
}
|
||||
|
||||
class AccessControlWidget extends StatelessWidget {
|
||||
final BuildContext context;
|
||||
|
||||
const AccessControlWidget({
|
||||
super.key,
|
||||
required this.context,
|
||||
});
|
||||
|
||||
IconData _getIconWithAccessControlMode(AccessControlMode mode) {
|
||||
return switch (mode) {
|
||||
AccessControlMode.acceptSelected => Icons.adjust_outlined,
|
||||
AccessControlMode.rejectSelected => Icons.block_outlined,
|
||||
};
|
||||
}
|
||||
|
||||
String _getTextWithAccessControlMode(AccessControlMode mode) {
|
||||
return switch (mode) {
|
||||
AccessControlMode.acceptSelected => appLocalizations.whitelistMode,
|
||||
AccessControlMode.rejectSelected => appLocalizations.blacklistMode,
|
||||
};
|
||||
}
|
||||
|
||||
String _getTextWithAccessSortType(AccessSortType type) {
|
||||
return switch (type) {
|
||||
AccessSortType.none => appLocalizations.defaultText,
|
||||
AccessSortType.name => appLocalizations.name,
|
||||
AccessSortType.time => appLocalizations.time,
|
||||
};
|
||||
}
|
||||
|
||||
IconData _getIconWithProxiesSortType(AccessSortType type) {
|
||||
return switch (type) {
|
||||
AccessSortType.none => Icons.sort,
|
||||
AccessSortType.name => Icons.sort_by_alpha,
|
||||
AccessSortType.time => Icons.timeline,
|
||||
};
|
||||
}
|
||||
|
||||
String _getTextWithIsFilterSystemApp(bool isFilterSystemApp) {
|
||||
return switch (isFilterSystemApp) {
|
||||
true => appLocalizations.onlyOtherApps,
|
||||
false => appLocalizations.allApps,
|
||||
};
|
||||
}
|
||||
|
||||
List<Widget> _buildModeSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.mode,
|
||||
items: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Selector<Config, AccessControlMode>(
|
||||
selector: (_, config) => config.accessControl.mode,
|
||||
builder: (_, accessControlMode, __) {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final item in AccessControlMode.values)
|
||||
SettingInfoCard(
|
||||
Info(
|
||||
label: _getTextWithAccessControlMode(item),
|
||||
iconData: _getIconWithAccessControlMode(item),
|
||||
),
|
||||
isSelected: accessControlMode == item,
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
mode: item,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildSortSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.sort,
|
||||
items: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Selector<Config, AccessSortType>(
|
||||
selector: (_, config) => config.accessControl.sort,
|
||||
builder: (_, accessSortType, __) {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final item in AccessSortType.values)
|
||||
SettingInfoCard(
|
||||
Info(
|
||||
label: _getTextWithAccessSortType(item),
|
||||
iconData: _getIconWithProxiesSortType(item),
|
||||
),
|
||||
isSelected: accessSortType == item,
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
sort: item,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildSourceSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.source,
|
||||
items: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Selector<Config, bool>(
|
||||
selector: (_, config) => config.accessControl.isFilterSystemApp,
|
||||
builder: (_, isFilterSystemApp, __) {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final item in [false, true])
|
||||
SettingTextCard(
|
||||
_getTextWithIsFilterSystemApp(item),
|
||||
isSelected: isFilterSystemApp == item,
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
isFilterSystemApp: item,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_intelligentSelected() async {
|
||||
final appState = globalState.appController.appState;
|
||||
final config = globalState.appController.config;
|
||||
final accessControl = config.accessControl;
|
||||
final packageNames = appState.packages
|
||||
.where(
|
||||
(item) =>
|
||||
accessControl.isFilterSystemApp ? item.isSystem == false : true,
|
||||
)
|
||||
.map((item) => item.packageName);
|
||||
Navigator.of(context).pop();
|
||||
final commonScaffoldState = context.commonScaffoldState;
|
||||
if (commonScaffoldState?.mounted != true) return;
|
||||
final selectedPackageNames =
|
||||
(await commonScaffoldState?.loadingRun<List<String>>(
|
||||
() async {
|
||||
return await app?.getChinaPackageNames() ?? [];
|
||||
},
|
||||
))
|
||||
?.toSet() ??
|
||||
{};
|
||||
final acceptList = packageNames
|
||||
.where((item) => !selectedPackageNames.contains(item))
|
||||
.toList();
|
||||
final rejectList = packageNames
|
||||
.where((item) => selectedPackageNames.contains(item))
|
||||
.toList();
|
||||
config.accessControl = accessControl.copyWith(
|
||||
acceptList: acceptList,
|
||||
rejectList: rejectList,
|
||||
);
|
||||
}
|
||||
|
||||
_copyToClipboard() async {
|
||||
await globalState.safeRun(() {
|
||||
final data = globalState.appController.config.accessControl.toJson();
|
||||
Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: json.encode(data),
|
||||
),
|
||||
);
|
||||
});
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
_pasteToClipboard() async {
|
||||
await globalState.safeRun(() async {
|
||||
final config = globalState.appController.config;
|
||||
final data = await Clipboard.getData('text/plain');
|
||||
final text = data?.text;
|
||||
if (text == null) return;
|
||||
config.accessControl = AccessControl.fromJson(
|
||||
json.decode(text),
|
||||
);
|
||||
});
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
List<Widget> _buildActionSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.action,
|
||||
items: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
spacing: 16,
|
||||
children: [
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.auto_awesome),
|
||||
label: appLocalizations.intelligentSelected,
|
||||
onPressed: _intelligentSelected,
|
||||
),
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.paste),
|
||||
label: appLocalizations.clipboardImport,
|
||||
onPressed: _pasteToClipboard,
|
||||
),
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.content_copy),
|
||||
label: appLocalizations.clipboardExport,
|
||||
onPressed: _copyToClipboard,
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
..._buildModeSetting(),
|
||||
..._buildSortSetting(),
|
||||
..._buildSourceSetting(),
|
||||
..._buildActionSetting(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
@@ -7,7 +6,6 @@ import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/config.dart';
|
||||
import 'package:fl_clash/models/dav.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/card.dart';
|
||||
import 'package:fl_clash/widgets/fade_box.dart';
|
||||
import 'package:fl_clash/widgets/list.dart';
|
||||
import 'package:fl_clash/widgets/text.dart';
|
||||
@@ -75,10 +73,11 @@ class BackupAndRecovery extends StatelessWidget {
|
||||
final res = await commonScaffoldState?.loadingRun<bool>(
|
||||
() async {
|
||||
final backupData = await globalState.appController.backupData();
|
||||
await picker.saveFile(
|
||||
final value = await picker.saveFile(
|
||||
other.getBackupFileName(),
|
||||
Uint8List.fromList(backupData),
|
||||
);
|
||||
if(value == null) return false;
|
||||
return true;
|
||||
},
|
||||
title: appLocalizations.backup,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:country_flags/country_flags.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
@@ -19,6 +18,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
final timeoutNotifier = ValueNotifier<bool>(false);
|
||||
bool? _preIsStart;
|
||||
Function? _checkIpDebounce;
|
||||
CancelToken? cancelToken;
|
||||
|
||||
_checkIp() async {
|
||||
final appState = globalState.appController.appState;
|
||||
@@ -29,13 +29,19 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
if (_preIsStart == false && _preIsStart == isStart) return;
|
||||
_preIsStart = isStart;
|
||||
ipInfoNotifier.value = null;
|
||||
final ipInfo = await request.checkIp();
|
||||
if (cancelToken != null) {
|
||||
cancelToken!.cancel();
|
||||
_preIsStart = null;
|
||||
timeoutNotifier.value == false;
|
||||
cancelToken = null;
|
||||
}
|
||||
cancelToken = CancelToken();
|
||||
final ipInfo = await request.checkIp(cancelToken: cancelToken);
|
||||
if (ipInfo == null) {
|
||||
timeoutNotifier.value = true;
|
||||
return;
|
||||
} else {
|
||||
timeoutNotifier.value = false;
|
||||
}
|
||||
timeoutNotifier.value = false;
|
||||
ipInfoNotifier.value = ipInfo;
|
||||
}
|
||||
|
||||
@@ -61,9 +67,19 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
timeoutNotifier.dispose();
|
||||
}
|
||||
|
||||
String countryCodeToEmoji(String countryCode) {
|
||||
final String code = countryCode.toUpperCase();
|
||||
if (code.length != 2) {
|
||||
return countryCode;
|
||||
}
|
||||
final int firstLetter = code.codeUnitAt(0) - 0x41 + 0x1F1E6;
|
||||
final int secondLetter = code.codeUnitAt(1) - 0x41 + 0x1F1E6;
|
||||
return String.fromCharCode(firstLetter) + String.fromCharCode(secondLetter);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_checkIpDebounce = debounce(_checkIp);
|
||||
_checkIpDebounce ??= debounce(_checkIp);
|
||||
return _checkIpContainer(
|
||||
ValueListenableBuilder<IpInfo?>(
|
||||
valueListenable: ipInfoNotifier,
|
||||
@@ -89,10 +105,19 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
flex: 1,
|
||||
child: FadeBox(
|
||||
child: ipInfo != null
|
||||
? CountryFlag.fromCountryCode(
|
||||
ipInfo.countryCode,
|
||||
width: 24,
|
||||
height: 24,
|
||||
? Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
height: globalState.appController.measure
|
||||
.titleMediumHeight,
|
||||
child: Text(
|
||||
countryCodeToEmoji(ipInfo.countryCode),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(
|
||||
fontFamily: "Twemoji",
|
||||
),
|
||||
),
|
||||
)
|
||||
: ValueListenableBuilder(
|
||||
valueListenable: timeoutNotifier,
|
||||
|
||||
@@ -11,8 +11,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'view_profile.dart';
|
||||
|
||||
class EditProfile extends StatefulWidget {
|
||||
final Profile profile;
|
||||
final BuildContext context;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/profiles/edit_profile.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
@@ -37,21 +39,11 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
);
|
||||
}
|
||||
|
||||
_getColumns(ViewMode viewMode) {
|
||||
switch (viewMode) {
|
||||
case ViewMode.mobile:
|
||||
return 1;
|
||||
case ViewMode.laptop:
|
||||
return 1;
|
||||
case ViewMode.desktop:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
_updateProfiles() async {
|
||||
final appController = globalState.appController;
|
||||
final config = appController.config;
|
||||
final profiles = appController.config.profiles;
|
||||
final messages = [];
|
||||
final updateProfiles = profiles.map<Future>(
|
||||
(profile) async {
|
||||
config.setProfile(
|
||||
@@ -62,7 +54,8 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
if (profile.id == appController.config.currentProfile?.id) {
|
||||
appController.applyProfile(isPrue: true);
|
||||
}
|
||||
} catch (_) {
|
||||
} catch (e) {
|
||||
messages.add("${profile.label ?? profile.id}: $e \n");
|
||||
config.setProfile(
|
||||
profile.copyWith(
|
||||
isUpdating: false,
|
||||
@@ -71,15 +64,27 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
}
|
||||
},
|
||||
);
|
||||
final titleMedium = context.textTheme.titleMedium;
|
||||
await Future.wait(updateProfiles);
|
||||
if (messages.isNotEmpty) {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.tip,
|
||||
message: TextSpan(
|
||||
children: [
|
||||
for (final message in messages)
|
||||
TextSpan(text: message, style: titleMedium)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_initScaffoldState() {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) {
|
||||
if (!mounted) return;
|
||||
final commonScaffoldState =
|
||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
||||
if (!context.mounted) return;
|
||||
commonScaffoldState?.actions = [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
@@ -87,6 +92,24 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
},
|
||||
icon: const Icon(Icons.sync),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
final profiles = globalState.appController.config.profiles;
|
||||
showSheet(
|
||||
title: appLocalizations.profilesSort,
|
||||
context: context,
|
||||
builder: (_) => SizedBox(
|
||||
height: 400,
|
||||
child: ReorderableProfiles(profiles: profiles),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.sort),
|
||||
iconSize: 26,
|
||||
),
|
||||
];
|
||||
},
|
||||
);
|
||||
@@ -116,7 +139,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
selector: (_, appState, config) => ProfilesSelectorState(
|
||||
profiles: config.profiles,
|
||||
currentProfileId: config.currentProfileId,
|
||||
viewMode: appState.viewMode,
|
||||
columns: other.getProfilesColumns(appState.viewWidth),
|
||||
),
|
||||
builder: (context, state, child) {
|
||||
if (state.profiles.isEmpty) {
|
||||
@@ -124,7 +147,6 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
label: appLocalizations.nullProfileDesc,
|
||||
);
|
||||
}
|
||||
final columns = _getColumns(state.viewMode);
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: SingleChildScrollView(
|
||||
@@ -137,7 +159,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
child: Grid(
|
||||
mainAxisSpacing: 16,
|
||||
crossAxisSpacing: 16,
|
||||
crossAxisCount: columns,
|
||||
crossAxisCount: state.columns,
|
||||
children: [
|
||||
for (int i = 0; i < state.profiles.length; i++)
|
||||
GridItem(
|
||||
@@ -145,8 +167,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
key: Key(state.profiles[i].id),
|
||||
profile: state.profiles[i],
|
||||
groupValue: state.currentProfileId,
|
||||
onChanged:
|
||||
globalState.appController.changeProfile,
|
||||
onChanged: globalState.appController.changeProfile,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -244,6 +265,7 @@ class ProfileItem extends StatelessWidget {
|
||||
LinearProgressIndicator(
|
||||
minHeight: 6,
|
||||
value: progress,
|
||||
backgroundColor: context.colorScheme.primary.toSoft(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
@@ -372,3 +394,129 @@ class ProfileItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReorderableProfiles extends StatefulWidget {
|
||||
final List<Profile> profiles;
|
||||
|
||||
const ReorderableProfiles({
|
||||
super.key,
|
||||
required this.profiles,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ReorderableProfiles> createState() => _ReorderableProfilesState();
|
||||
}
|
||||
|
||||
class _ReorderableProfilesState extends State<ReorderableProfiles> {
|
||||
late List<Profile> profiles;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
profiles = List.from(widget.profiles);
|
||||
}
|
||||
|
||||
Widget proxyDecorator(
|
||||
Widget child,
|
||||
int index,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
final profile = profiles[index];
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (_, Widget? child) {
|
||||
final double animValue = Curves.easeInOut.transform(animation.value);
|
||||
final double scale = lerpDouble(1, 1.02, animValue)!;
|
||||
return Transform.scale(
|
||||
scale: scale,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
key: Key(profile.id),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: CommonCard(
|
||||
type: CommonCardType.filled,
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
right: 44,
|
||||
left: 16,
|
||||
),
|
||||
title: Text(profile.label ?? profile.id),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: ReorderableListView.builder(
|
||||
buildDefaultDragHandles: false,
|
||||
padding: const EdgeInsets.all(12),
|
||||
proxyDecorator: proxyDecorator,
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
if (oldIndex == newIndex) return;
|
||||
setState(() {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final profile = profiles.removeAt(oldIndex);
|
||||
profiles.insert(newIndex, profile);
|
||||
});
|
||||
},
|
||||
itemBuilder: (_, index) {
|
||||
final profile = profiles[index];
|
||||
return Container(
|
||||
key: Key(profile.id),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: CommonCard(
|
||||
type: CommonCardType.filled,
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
right: 16,
|
||||
left: 16,
|
||||
),
|
||||
title: Text(profile.label ?? profile.id),
|
||||
trailing: ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: const Icon(Icons.drag_handle),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: profiles.length,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 12,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
globalState.appController.config.profiles = profiles;
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.check,
|
||||
),
|
||||
iconSize: 32,
|
||||
padding: const EdgeInsets.all(8),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,23 +69,21 @@ class ProxyCard extends StatelessWidget {
|
||||
if (type == ProxyCardType.min) {
|
||||
return SizedBox(
|
||||
height: measure.bodyMediumHeight * 1,
|
||||
child: Text(
|
||||
child: EmojiText(
|
||||
proxy.name,
|
||||
maxLines: 1,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SizedBox(
|
||||
height: measure.bodyMediumHeight * 2,
|
||||
child: Text(
|
||||
child: EmojiText(
|
||||
proxy.name,
|
||||
maxLines: 2,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -155,14 +153,12 @@ class ProxyCard extends StatelessWidget {
|
||||
proxy.name,
|
||||
),
|
||||
builder: (_, desc, __) {
|
||||
return TooltipText(
|
||||
text: Text(
|
||||
desc,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: context.textTheme.bodySmall?.color
|
||||
?.toLight(),
|
||||
),
|
||||
return EmojiText(
|
||||
desc,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: context.textTheme.bodySmall?.color
|
||||
?.toLight(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/constant.dart';
|
||||
import 'package:fl_clash/common/other.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
@@ -61,7 +61,10 @@ double getScrollToSelectedOffset({
|
||||
required List<Proxy> proxies,
|
||||
}) {
|
||||
final appController = globalState.appController;
|
||||
final columns = appController.columns;
|
||||
final columns = other.getProxiesColumns(
|
||||
appController.appState.viewWidth,
|
||||
appController.config.proxiesLayout,
|
||||
);
|
||||
final proxyCardType = appController.config.proxyCardType;
|
||||
final selectedName = appController.getCurrentSelectedName(groupName);
|
||||
final findSelectedIndex = proxies.indexWhere(
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/card.dart';
|
||||
import 'package:fl_clash/widgets/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -237,7 +238,10 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
|
||||
currentUnfoldSet: config.currentUnfoldSet,
|
||||
proxyCardType: config.proxyCardType,
|
||||
proxiesSortType: config.proxiesSortType,
|
||||
columns: globalState.appController.columns,
|
||||
columns: other.getProxiesColumns(
|
||||
appState.viewWidth,
|
||||
config.proxiesLayout,
|
||||
),
|
||||
sortNum: appState.sortNum,
|
||||
);
|
||||
},
|
||||
@@ -450,7 +454,7 @@ class _ListHeaderState extends State<ListHeader>
|
||||
if (currentGroupName.isNotEmpty) ...[
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
child: EmojiText(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
" · $currentGroupName",
|
||||
style: context
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/app.dart';
|
||||
@@ -50,7 +53,6 @@ class _ProvidersState extends State<Providers> {
|
||||
);
|
||||
await clashCore.updateExternalProvider(
|
||||
providerName: provider.name,
|
||||
providerType: provider.type,
|
||||
);
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
@@ -58,6 +60,7 @@ class _ProvidersState extends State<Providers> {
|
||||
},
|
||||
);
|
||||
await Future.wait(updateProviders);
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -91,28 +94,48 @@ class ProviderItem extends StatelessWidget {
|
||||
required this.provider,
|
||||
});
|
||||
|
||||
_handleUpdateProfile() async {
|
||||
await globalState.safeRun<void>(updateProvider);
|
||||
_handleUpdateProvider() async {
|
||||
await globalState.safeRun<void>(() async {
|
||||
final appState = globalState.appController.appState;
|
||||
if (provider.vehicleType != "HTTP") return;
|
||||
await globalState.safeRun(() async {
|
||||
appState.setProvider(
|
||||
provider.copyWith(
|
||||
isUpdating: true,
|
||||
),
|
||||
);
|
||||
final message = await clashCore.updateExternalProvider(
|
||||
providerName: provider.name,
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
});
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
});
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
}
|
||||
|
||||
updateProvider() async {
|
||||
final appState = globalState.appController.appState;
|
||||
if (provider.vehicleType != "HTTP") return;
|
||||
await globalState.safeRun(() async {
|
||||
appState.setProvider(
|
||||
provider.copyWith(
|
||||
isUpdating: true,
|
||||
),
|
||||
_handleSideLoadProvider() async {
|
||||
await globalState.safeRun<void>(() async {
|
||||
final platformFile = await picker.pickerFile();
|
||||
final appState = globalState.appController.appState;
|
||||
final bytes = platformFile?.bytes;
|
||||
if (bytes == null) return;
|
||||
final file = await File(provider.path).create(recursive: true);
|
||||
await file.writeAsBytes(bytes);
|
||||
final providerName = provider.name;
|
||||
var message = await clashCore.sideLoadExternalProvider(
|
||||
providerName: providerName,
|
||||
data: utf8.decode(bytes),
|
||||
);
|
||||
final message = await clashCore.updateExternalProvider(
|
||||
providerName: provider.name,
|
||||
providerType: provider.type,
|
||||
if (message.isNotEmpty) throw message;
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
});
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
}
|
||||
|
||||
String _buildProviderDesc() {
|
||||
@@ -153,18 +176,16 @@ class ProviderItem extends StatelessWidget {
|
||||
runSpacing: 6,
|
||||
spacing: 12,
|
||||
children: [
|
||||
// CommonChip(
|
||||
// avatar: const Icon(Icons.upload),
|
||||
// label: appLocalizations.upload,
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.upload),
|
||||
label: appLocalizations.upload,
|
||||
onPressed: _handleSideLoadProvider,
|
||||
),
|
||||
if (provider.vehicleType == "HTTP")
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.sync),
|
||||
label: appLocalizations.sync,
|
||||
onPressed: () {
|
||||
_handleUpdateProfile();
|
||||
},
|
||||
onPressed: _handleUpdateProvider,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -33,6 +33,14 @@ class ProxiesSettingWidget extends StatelessWidget {
|
||||
};
|
||||
}
|
||||
|
||||
String getTextForProxiesLayout(ProxiesLayout proxiesLayout) {
|
||||
return switch (proxiesLayout) {
|
||||
ProxiesLayout.tight => appLocalizations.tight,
|
||||
ProxiesLayout.standard => appLocalizations.standard,
|
||||
ProxiesLayout.loose => appLocalizations.loose,
|
||||
};
|
||||
}
|
||||
|
||||
List<Widget> _buildStyleSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.style,
|
||||
@@ -132,36 +140,28 @@ class ProxiesSettingWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildColumnsSetting() {
|
||||
List<Widget> _buildLayoutSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.columns,
|
||||
title: appLocalizations.layout,
|
||||
items: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Selector2<AppState, Config, ColumnsSelectorState>(
|
||||
selector: (_, appState, config) => ColumnsSelectorState(
|
||||
columns: config.proxiesColumns,
|
||||
viewMode: appState.viewMode,
|
||||
),
|
||||
builder: (_, state, __) {
|
||||
child: Selector< Config, ProxiesLayout>(
|
||||
selector: (_, config) => config.proxiesLayout,
|
||||
builder: (_, proxiesLayout, __) {
|
||||
final config = globalState.appController.config;
|
||||
final targetColumnsArray = viewModeColumnsMap[state.viewMode]!;
|
||||
final currentColumns = other.getColumns(
|
||||
state.viewMode,
|
||||
state.columns,
|
||||
);
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final item in targetColumnsArray)
|
||||
for (final item in ProxiesLayout.values)
|
||||
SettingTextCard(
|
||||
other.getColumnsTextForInt(item),
|
||||
isSelected: item == currentColumns,
|
||||
getTextForProxiesLayout(item),
|
||||
isSelected: item == proxiesLayout,
|
||||
onPressed: () {
|
||||
config.proxiesColumns = item;
|
||||
config.proxiesLayout = item;
|
||||
},
|
||||
)
|
||||
],
|
||||
@@ -183,80 +183,10 @@ class ProxiesSettingWidget extends StatelessWidget {
|
||||
children: [
|
||||
..._buildStyleSetting(),
|
||||
..._buildSortSetting(),
|
||||
..._buildColumnsSetting(),
|
||||
..._buildLayoutSetting(),
|
||||
..._buildSizeSetting(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingInfoCard extends StatelessWidget {
|
||||
final Info info;
|
||||
final bool? isSelected;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const SettingInfoCard(
|
||||
this.info, {
|
||||
super.key,
|
||||
this.isSelected,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
isSelected: isSelected,
|
||||
onPressed: onPressed,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Icon(info.iconData),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
info.label,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingTextCard extends StatelessWidget {
|
||||
final String text;
|
||||
final bool? isSelected;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const SettingTextCard(
|
||||
this.text, {
|
||||
super.key,
|
||||
this.isSelected,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
onPressed: onPressed,
|
||||
isSelected: isSelected,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
text,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/proxies/setting.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
@@ -285,7 +284,10 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
return ProxyGroupSelectorState(
|
||||
proxyCardType: config.proxyCardType,
|
||||
proxiesSortType: config.proxiesSortType,
|
||||
columns: globalState.appController.columns,
|
||||
columns: other.getProxiesColumns(
|
||||
appState.viewWidth,
|
||||
config.proxiesLayout,
|
||||
),
|
||||
sortNum: appState.sortNum,
|
||||
proxies: group.all,
|
||||
groupType: group.type,
|
||||
|
||||
@@ -182,9 +182,9 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
||||
updateGeoDateItem() async {
|
||||
isUpdating.value = true;
|
||||
try {
|
||||
final message = await clashCore.updateExternalProvider(
|
||||
providerName: geoItem.fileName,
|
||||
providerType: geoItem.label,
|
||||
final message = await clashCore.updateGeoData(
|
||||
geoName: geoItem.fileName,
|
||||
geoType: geoItem.label,
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
} catch (e) {
|
||||
|
||||
@@ -227,5 +227,19 @@
|
||||
"remoteBackupDesc": "Backup local data to WebDAV",
|
||||
"remoteRecoveryDesc": "Recovery data from WebDAV",
|
||||
"localBackupDesc": "Backup local data to local",
|
||||
"localRecoveryDesc": "Recovery data from file"
|
||||
"localRecoveryDesc": "Recovery data from file",
|
||||
"mode": "Mode",
|
||||
"time": "Time",
|
||||
"source": "Source",
|
||||
"allApps": "All apps",
|
||||
"onlyOtherApps": "Only third-party apps",
|
||||
"action": "Action",
|
||||
"intelligentSelected": "Intelligent selection",
|
||||
"clipboardImport": "Clipboard import",
|
||||
"clipboardExport": "Export clipboard",
|
||||
"layout": "Layout",
|
||||
"tight": "Tight",
|
||||
"standard": "Standard",
|
||||
"loose": "Loose",
|
||||
"profilesSort": "Profiles sort"
|
||||
}
|
||||
@@ -227,5 +227,19 @@
|
||||
"remoteBackupDesc": "备份数据到WebDAV",
|
||||
"remoteRecoveryDesc": "通过WebDAV恢复数据",
|
||||
"localBackupDesc": "备份数据到本地",
|
||||
"localRecoveryDesc": "通过文件恢复数据"
|
||||
"localRecoveryDesc": "通过文件恢复数据",
|
||||
"mode": "模式",
|
||||
"time": "时间",
|
||||
"source": "来源",
|
||||
"allApps": "所有应用",
|
||||
"onlyOtherApps": "仅第三方应用",
|
||||
"action": "操作",
|
||||
"intelligentSelected": "智能选择",
|
||||
"clipboardImport": "剪贴板导入",
|
||||
"clipboardExport": "导出剪贴板",
|
||||
"layout": "布局",
|
||||
"tight": "宽松",
|
||||
"standard": "标准",
|
||||
"loose": "紧凑",
|
||||
"profilesSort": "配置排序"
|
||||
}
|
||||
@@ -33,6 +33,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"account": MessageLookupByLibrary.simpleMessage("Account"),
|
||||
"accountTip":
|
||||
MessageLookupByLibrary.simpleMessage("Account cannot be empty"),
|
||||
"action": MessageLookupByLibrary.simpleMessage("Action"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("Add"),
|
||||
"address": MessageLookupByLibrary.simpleMessage("Address"),
|
||||
"addressHelp":
|
||||
@@ -40,6 +41,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"addressTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Please enter a valid WebDAV address"),
|
||||
"ago": MessageLookupByLibrary.simpleMessage(" Ago"),
|
||||
"allApps": MessageLookupByLibrary.simpleMessage("All apps"),
|
||||
"allowBypass": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow applications to bypass VPN"),
|
||||
"allowBypassDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -89,6 +91,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage(
|
||||
"The current application is already the latest version"),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("Checking..."),
|
||||
"clipboardExport":
|
||||
MessageLookupByLibrary.simpleMessage("Export clipboard"),
|
||||
"clipboardImport":
|
||||
MessageLookupByLibrary.simpleMessage("Clipboard import"),
|
||||
"columns": MessageLookupByLibrary.simpleMessage("Columns"),
|
||||
"compatible":
|
||||
MessageLookupByLibrary.simpleMessage("Compatibility mode"),
|
||||
@@ -167,6 +173,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"infiniteTime":
|
||||
MessageLookupByLibrary.simpleMessage("Long term effective"),
|
||||
"init": MessageLookupByLibrary.simpleMessage("Init"),
|
||||
"intelligentSelected":
|
||||
MessageLookupByLibrary.simpleMessage("Intelligent selection"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
||||
"When turned on it will be able to receive IPv6 traffic"),
|
||||
@@ -174,6 +182,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"keepAliveIntervalDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Tcp keep alive interval"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("Language"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("Layout"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("Light"),
|
||||
"list": MessageLookupByLibrary.simpleMessage("List"),
|
||||
"local": MessageLookupByLibrary.simpleMessage("Local"),
|
||||
@@ -187,12 +196,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Disabling will hide the log entry"),
|
||||
"logs": MessageLookupByLibrary.simpleMessage("Logs"),
|
||||
"logsDesc": MessageLookupByLibrary.simpleMessage("Log capture records"),
|
||||
"loose": MessageLookupByLibrary.simpleMessage("Loose"),
|
||||
"min": MessageLookupByLibrary.simpleMessage("Min"),
|
||||
"minimizeOnExit":
|
||||
MessageLookupByLibrary.simpleMessage("Minimize on exit"),
|
||||
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Modify the default system exit event"),
|
||||
"minutes": MessageLookupByLibrary.simpleMessage("Minutes"),
|
||||
"mode": MessageLookupByLibrary.simpleMessage("Mode"),
|
||||
"months": MessageLookupByLibrary.simpleMessage("Months"),
|
||||
"more": MessageLookupByLibrary.simpleMessage("More"),
|
||||
"name": MessageLookupByLibrary.simpleMessage("Name"),
|
||||
@@ -216,6 +227,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"No profile, Please add a profile"),
|
||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"),
|
||||
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
|
||||
"onlyOtherApps":
|
||||
MessageLookupByLibrary.simpleMessage("Only third-party apps"),
|
||||
"onlyStatisticsProxy":
|
||||
MessageLookupByLibrary.simpleMessage("Only statistics proxy"),
|
||||
"onlyStatisticsProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -255,6 +268,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"profileUrlNullValidationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Please input the profile URL"),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("Profiles"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("Profiles sort"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("Project"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("Proxies"),
|
||||
"proxiesSetting":
|
||||
@@ -300,6 +314,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Start in the background"),
|
||||
"size": MessageLookupByLibrary.simpleMessage("Size"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("Sort"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("Source"),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("Standard"),
|
||||
"startVpn": MessageLookupByLibrary.simpleMessage("Staring VPN..."),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("Style"),
|
||||
@@ -322,6 +338,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Set dark mode,adjust the color"),
|
||||
"themeMode": MessageLookupByLibrary.simpleMessage("Theme mode"),
|
||||
"threeColumns": MessageLookupByLibrary.simpleMessage("Three columns"),
|
||||
"tight": MessageLookupByLibrary.simpleMessage("Tight"),
|
||||
"time": MessageLookupByLibrary.simpleMessage("Time"),
|
||||
"tip": MessageLookupByLibrary.simpleMessage("tip"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("Tools"),
|
||||
"trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"),
|
||||
|
||||
@@ -31,11 +31,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("选中应用将会被排除在VPN之外"),
|
||||
"account": MessageLookupByLibrary.simpleMessage("账号"),
|
||||
"accountTip": MessageLookupByLibrary.simpleMessage("账号不能为空"),
|
||||
"action": MessageLookupByLibrary.simpleMessage("操作"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("添加"),
|
||||
"address": MessageLookupByLibrary.simpleMessage("地址"),
|
||||
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
|
||||
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
|
||||
"ago": MessageLookupByLibrary.simpleMessage("前"),
|
||||
"allApps": MessageLookupByLibrary.simpleMessage("所有应用"),
|
||||
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过VPN"),
|
||||
"allowBypassDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"),
|
||||
@@ -73,6 +75,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"),
|
||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("检测中..."),
|
||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
|
||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
|
||||
"columns": MessageLookupByLibrary.simpleMessage("列数"),
|
||||
"compatible": MessageLookupByLibrary.simpleMessage("兼容模式"),
|
||||
"compatibleDesc":
|
||||
@@ -136,12 +140,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
|
||||
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
|
||||
"init": MessageLookupByLibrary.simpleMessage("初始化"),
|
||||
"intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
|
||||
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
|
||||
"keepAliveIntervalDesc":
|
||||
MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("语言"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("布局"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("浅色"),
|
||||
"list": MessageLookupByLibrary.simpleMessage("列表"),
|
||||
"local": MessageLookupByLibrary.simpleMessage("本地"),
|
||||
@@ -152,11 +158,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"),
|
||||
"logs": MessageLookupByLibrary.simpleMessage("日志"),
|
||||
"logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"),
|
||||
"loose": MessageLookupByLibrary.simpleMessage("紧凑"),
|
||||
"min": MessageLookupByLibrary.simpleMessage("最小"),
|
||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
|
||||
"minimizeOnExitDesc":
|
||||
MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
|
||||
"minutes": MessageLookupByLibrary.simpleMessage("分钟"),
|
||||
"mode": MessageLookupByLibrary.simpleMessage("模式"),
|
||||
"months": MessageLookupByLibrary.simpleMessage("月"),
|
||||
"more": MessageLookupByLibrary.simpleMessage("更多"),
|
||||
"name": MessageLookupByLibrary.simpleMessage("名称"),
|
||||
@@ -176,6 +184,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
|
||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
|
||||
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
|
||||
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("仅第三方应用"),
|
||||
"onlyStatisticsProxy": MessageLookupByLibrary.simpleMessage("仅统计代理"),
|
||||
"onlyStatisticsProxyDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启后,将只统计代理流量"),
|
||||
@@ -207,6 +216,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"profileUrlNullValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage("请输入配置URL"),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("配置"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("配置排序"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("项目"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("代理"),
|
||||
"proxiesSetting": MessageLookupByLibrary.simpleMessage("代理设置"),
|
||||
@@ -241,6 +251,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("后台启动"),
|
||||
"size": MessageLookupByLibrary.simpleMessage("尺寸"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("排序"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("来源"),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("标准"),
|
||||
"startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("风格"),
|
||||
@@ -261,6 +273,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
|
||||
"themeMode": MessageLookupByLibrary.simpleMessage("主题模式"),
|
||||
"threeColumns": MessageLookupByLibrary.simpleMessage("三列"),
|
||||
"tight": MessageLookupByLibrary.simpleMessage("宽松"),
|
||||
"time": MessageLookupByLibrary.simpleMessage("时间"),
|
||||
"tip": MessageLookupByLibrary.simpleMessage("提示"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("工具"),
|
||||
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),
|
||||
|
||||
@@ -2339,6 +2339,146 @@ class AppLocalizations {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Mode`
|
||||
String get mode {
|
||||
return Intl.message(
|
||||
'Mode',
|
||||
name: 'mode',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Time`
|
||||
String get time {
|
||||
return Intl.message(
|
||||
'Time',
|
||||
name: 'time',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Source`
|
||||
String get source {
|
||||
return Intl.message(
|
||||
'Source',
|
||||
name: 'source',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `All apps`
|
||||
String get allApps {
|
||||
return Intl.message(
|
||||
'All apps',
|
||||
name: 'allApps',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Only third-party apps`
|
||||
String get onlyOtherApps {
|
||||
return Intl.message(
|
||||
'Only third-party apps',
|
||||
name: 'onlyOtherApps',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Action`
|
||||
String get action {
|
||||
return Intl.message(
|
||||
'Action',
|
||||
name: 'action',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Intelligent selection`
|
||||
String get intelligentSelected {
|
||||
return Intl.message(
|
||||
'Intelligent selection',
|
||||
name: 'intelligentSelected',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Clipboard import`
|
||||
String get clipboardImport {
|
||||
return Intl.message(
|
||||
'Clipboard import',
|
||||
name: 'clipboardImport',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Export clipboard`
|
||||
String get clipboardExport {
|
||||
return Intl.message(
|
||||
'Export clipboard',
|
||||
name: 'clipboardExport',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Layout`
|
||||
String get layout {
|
||||
return Intl.message(
|
||||
'Layout',
|
||||
name: 'layout',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tight`
|
||||
String get tight {
|
||||
return Intl.message(
|
||||
'Tight',
|
||||
name: 'tight',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Standard`
|
||||
String get standard {
|
||||
return Intl.message(
|
||||
'Standard',
|
||||
name: 'standard',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Loose`
|
||||
String get loose {
|
||||
return Intl.message(
|
||||
'Loose',
|
||||
name: 'loose',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Profiles sort`
|
||||
String get profilesSort {
|
||||
return Intl.message(
|
||||
'Profiles sort',
|
||||
name: 'profilesSort',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -130,13 +130,13 @@ class ServiceMessageHandler with ServiceMessageListener {
|
||||
final Function(Fd fd) _onProtect;
|
||||
final Function(Process process) _onProcess;
|
||||
final Function(String runTime) _onStarted;
|
||||
final Function(String groupName) _onLoaded;
|
||||
final Function(String providerName) _onLoaded;
|
||||
|
||||
const ServiceMessageHandler({
|
||||
required Function(Fd fd) onProtect,
|
||||
required Function(Process process) onProcess,
|
||||
required Function(String runTime) onStarted,
|
||||
required Function(String groupName) onLoaded,
|
||||
required Function(String providerName) onLoaded,
|
||||
}) : _onProtect = onProtect,
|
||||
_onProcess = onProcess,
|
||||
_onStarted = onStarted,
|
||||
@@ -158,8 +158,8 @@ class ServiceMessageHandler with ServiceMessageListener {
|
||||
}
|
||||
|
||||
@override
|
||||
onLoaded(String groupName) {
|
||||
_onLoaded(groupName);
|
||||
onLoaded(String providerName) {
|
||||
_onLoaded(providerName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -353,7 +353,8 @@ class AppState with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
setProvider(ExternalProvider provider) {
|
||||
setProvider(ExternalProvider? provider) {
|
||||
if(provider == null) return;
|
||||
final index = _providers.indexWhere((item) => item.name == provider.name);
|
||||
if (index == -1) return;
|
||||
_providers = List.from(_providers)..[index] = provider;
|
||||
|
||||
@@ -18,6 +18,7 @@ class AccessControl with _$AccessControl {
|
||||
@Default(AccessControlMode.rejectSelected) AccessControlMode mode,
|
||||
@Default([]) List<String> acceptList,
|
||||
@Default([]) List<String> rejectList,
|
||||
@Default(AccessSortType.none) AccessSortType sort,
|
||||
@Default(true) bool isFilterSystemApp,
|
||||
}) = _AccessControl;
|
||||
|
||||
@@ -25,6 +26,13 @@ class AccessControl with _$AccessControl {
|
||||
_$AccessControlFromJson(json);
|
||||
}
|
||||
|
||||
extension AccessControlExt on AccessControl {
|
||||
List<String> get currentList => switch (mode) {
|
||||
AccessControlMode.acceptSelected => acceptList,
|
||||
AccessControlMode.rejectSelected => rejectList,
|
||||
};
|
||||
}
|
||||
|
||||
@freezed
|
||||
class CoreState with _$CoreState {
|
||||
const factory CoreState({
|
||||
@@ -36,7 +44,8 @@ class CoreState with _$CoreState {
|
||||
required bool onlyProxy,
|
||||
}) = _CoreState;
|
||||
|
||||
factory CoreState.fromJson(Map<String, Object?> json) => _$CoreStateFromJson(json);
|
||||
factory CoreState.fromJson(Map<String, Object?> json) =>
|
||||
_$CoreStateFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -79,7 +88,7 @@ class Config extends ChangeNotifier {
|
||||
bool _isCloseConnections;
|
||||
ProxiesType _proxiesType;
|
||||
ProxyCardType _proxyCardType;
|
||||
int _proxiesColumns;
|
||||
ProxiesLayout _proxiesLayout;
|
||||
String _testUrl;
|
||||
WindowProps _windowProps;
|
||||
bool _onlyProxy;
|
||||
@@ -108,9 +117,9 @@ class Config extends ChangeNotifier {
|
||||
_proxyCardType = ProxyCardType.expand,
|
||||
_windowProps = defaultWindowProps,
|
||||
_proxiesType = ProxiesType.tab,
|
||||
_proxiesColumns = 2,
|
||||
_prueBlack = false,
|
||||
_onlyProxy = false;
|
||||
_onlyProxy = false,
|
||||
_proxiesLayout = ProxiesLayout.standard;
|
||||
|
||||
deleteProfileById(String id) {
|
||||
_profiles = profiles.where((element) => element.id != id).toList();
|
||||
@@ -312,6 +321,16 @@ class Config extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: ProxiesLayout.standard)
|
||||
ProxiesLayout get proxiesLayout => _proxiesLayout;
|
||||
|
||||
set proxiesLayout(ProxiesLayout value) {
|
||||
if (_proxiesLayout != value) {
|
||||
_proxiesLayout = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
bool get isMinimizeOnExit => _isMinimizeOnExit;
|
||||
|
||||
@@ -473,16 +492,6 @@ class Config extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: 2)
|
||||
int get proxiesColumns => _proxiesColumns;
|
||||
|
||||
set proxiesColumns(int value) {
|
||||
if (_proxiesColumns != value) {
|
||||
_proxiesColumns = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(name: "test-url", defaultValue: defaultTestUrl)
|
||||
String get testUrl => _testUrl;
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ mixin _$AccessControl {
|
||||
AccessControlMode get mode => throw _privateConstructorUsedError;
|
||||
List<String> get acceptList => throw _privateConstructorUsedError;
|
||||
List<String> get rejectList => throw _privateConstructorUsedError;
|
||||
AccessSortType get sort => throw _privateConstructorUsedError;
|
||||
bool get isFilterSystemApp => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -41,6 +42,7 @@ abstract class $AccessControlCopyWith<$Res> {
|
||||
{AccessControlMode mode,
|
||||
List<String> acceptList,
|
||||
List<String> rejectList,
|
||||
AccessSortType sort,
|
||||
bool isFilterSystemApp});
|
||||
}
|
||||
|
||||
@@ -60,6 +62,7 @@ class _$AccessControlCopyWithImpl<$Res, $Val extends AccessControl>
|
||||
Object? mode = null,
|
||||
Object? acceptList = null,
|
||||
Object? rejectList = null,
|
||||
Object? sort = null,
|
||||
Object? isFilterSystemApp = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
@@ -75,6 +78,10 @@ class _$AccessControlCopyWithImpl<$Res, $Val extends AccessControl>
|
||||
? _value.rejectList
|
||||
: rejectList // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
sort: null == sort
|
||||
? _value.sort
|
||||
: sort // ignore: cast_nullable_to_non_nullable
|
||||
as AccessSortType,
|
||||
isFilterSystemApp: null == isFilterSystemApp
|
||||
? _value.isFilterSystemApp
|
||||
: isFilterSystemApp // ignore: cast_nullable_to_non_nullable
|
||||
@@ -95,6 +102,7 @@ abstract class _$$AccessControlImplCopyWith<$Res>
|
||||
{AccessControlMode mode,
|
||||
List<String> acceptList,
|
||||
List<String> rejectList,
|
||||
AccessSortType sort,
|
||||
bool isFilterSystemApp});
|
||||
}
|
||||
|
||||
@@ -112,6 +120,7 @@ class __$$AccessControlImplCopyWithImpl<$Res>
|
||||
Object? mode = null,
|
||||
Object? acceptList = null,
|
||||
Object? rejectList = null,
|
||||
Object? sort = null,
|
||||
Object? isFilterSystemApp = null,
|
||||
}) {
|
||||
return _then(_$AccessControlImpl(
|
||||
@@ -127,6 +136,10 @@ class __$$AccessControlImplCopyWithImpl<$Res>
|
||||
? _value._rejectList
|
||||
: rejectList // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
sort: null == sort
|
||||
? _value.sort
|
||||
: sort // ignore: cast_nullable_to_non_nullable
|
||||
as AccessSortType,
|
||||
isFilterSystemApp: null == isFilterSystemApp
|
||||
? _value.isFilterSystemApp
|
||||
: isFilterSystemApp // ignore: cast_nullable_to_non_nullable
|
||||
@@ -142,6 +155,7 @@ class _$AccessControlImpl implements _AccessControl {
|
||||
{this.mode = AccessControlMode.rejectSelected,
|
||||
final List<String> acceptList = const [],
|
||||
final List<String> rejectList = const [],
|
||||
this.sort = AccessSortType.none,
|
||||
this.isFilterSystemApp = true})
|
||||
: _acceptList = acceptList,
|
||||
_rejectList = rejectList;
|
||||
@@ -170,13 +184,16 @@ class _$AccessControlImpl implements _AccessControl {
|
||||
return EqualUnmodifiableListView(_rejectList);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final AccessSortType sort;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFilterSystemApp;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AccessControl(mode: $mode, acceptList: $acceptList, rejectList: $rejectList, isFilterSystemApp: $isFilterSystemApp)';
|
||||
return 'AccessControl(mode: $mode, acceptList: $acceptList, rejectList: $rejectList, sort: $sort, isFilterSystemApp: $isFilterSystemApp)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -189,6 +206,7 @@ class _$AccessControlImpl implements _AccessControl {
|
||||
.equals(other._acceptList, _acceptList) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._rejectList, _rejectList) &&
|
||||
(identical(other.sort, sort) || other.sort == sort) &&
|
||||
(identical(other.isFilterSystemApp, isFilterSystemApp) ||
|
||||
other.isFilterSystemApp == isFilterSystemApp));
|
||||
}
|
||||
@@ -200,6 +218,7 @@ class _$AccessControlImpl implements _AccessControl {
|
||||
mode,
|
||||
const DeepCollectionEquality().hash(_acceptList),
|
||||
const DeepCollectionEquality().hash(_rejectList),
|
||||
sort,
|
||||
isFilterSystemApp);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@@ -221,6 +240,7 @@ abstract class _AccessControl implements AccessControl {
|
||||
{final AccessControlMode mode,
|
||||
final List<String> acceptList,
|
||||
final List<String> rejectList,
|
||||
final AccessSortType sort,
|
||||
final bool isFilterSystemApp}) = _$AccessControlImpl;
|
||||
|
||||
factory _AccessControl.fromJson(Map<String, dynamic> json) =
|
||||
@@ -233,6 +253,8 @@ abstract class _AccessControl implements AccessControl {
|
||||
@override
|
||||
List<String> get rejectList;
|
||||
@override
|
||||
AccessSortType get sort;
|
||||
@override
|
||||
bool get isFilterSystemApp;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
|
||||
@@ -23,6 +23,9 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
|
||||
..proxiesSortType =
|
||||
$enumDecodeNullable(_$ProxiesSortTypeEnumMap, json['proxiesSortType']) ??
|
||||
ProxiesSortType.none
|
||||
..proxiesLayout =
|
||||
$enumDecodeNullable(_$ProxiesLayoutEnumMap, json['proxiesLayout']) ??
|
||||
ProxiesLayout.standard
|
||||
..isMinimizeOnExit = json['isMinimizeOnExit'] as bool? ?? true
|
||||
..isAccessControl = json['isAccessControl'] as bool? ?? false
|
||||
..accessControl =
|
||||
@@ -44,7 +47,6 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
|
||||
..proxyCardType =
|
||||
$enumDecodeNullable(_$ProxyCardTypeEnumMap, json['proxyCardType']) ??
|
||||
ProxyCardType.expand
|
||||
..proxiesColumns = (json['proxiesColumns'] as num?)?.toInt() ?? 2
|
||||
..testUrl =
|
||||
json['test-url'] as String? ?? 'https://www.gstatic.com/generate_204'
|
||||
..isExclude = json['isExclude'] as bool? ?? false
|
||||
@@ -62,6 +64,7 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
||||
'locale': instance.locale,
|
||||
'primaryColor': instance.primaryColor,
|
||||
'proxiesSortType': _$ProxiesSortTypeEnumMap[instance.proxiesSortType]!,
|
||||
'proxiesLayout': _$ProxiesLayoutEnumMap[instance.proxiesLayout]!,
|
||||
'isMinimizeOnExit': instance.isMinimizeOnExit,
|
||||
'isAccessControl': instance.isAccessControl,
|
||||
'accessControl': instance.accessControl,
|
||||
@@ -76,7 +79,6 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
||||
'isCloseConnections': instance.isCloseConnections,
|
||||
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
|
||||
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
|
||||
'proxiesColumns': instance.proxiesColumns,
|
||||
'test-url': instance.testUrl,
|
||||
'isExclude': instance.isExclude,
|
||||
'windowProps': instance.windowProps,
|
||||
@@ -94,6 +96,12 @@ const _$ProxiesSortTypeEnumMap = {
|
||||
ProxiesSortType.name: 'name',
|
||||
};
|
||||
|
||||
const _$ProxiesLayoutEnumMap = {
|
||||
ProxiesLayout.loose: 'loose',
|
||||
ProxiesLayout.standard: 'standard',
|
||||
ProxiesLayout.tight: 'tight',
|
||||
};
|
||||
|
||||
const _$ProxiesTypeEnumMap = {
|
||||
ProxiesType.tab: 'tab',
|
||||
ProxiesType.list: 'list',
|
||||
@@ -117,6 +125,8 @@ _$AccessControlImpl _$$AccessControlImplFromJson(Map<String, dynamic> json) =>
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const [],
|
||||
sort: $enumDecodeNullable(_$AccessSortTypeEnumMap, json['sort']) ??
|
||||
AccessSortType.none,
|
||||
isFilterSystemApp: json['isFilterSystemApp'] as bool? ?? true,
|
||||
);
|
||||
|
||||
@@ -125,6 +135,7 @@ Map<String, dynamic> _$$AccessControlImplToJson(_$AccessControlImpl instance) =>
|
||||
'mode': _$AccessControlModeEnumMap[instance.mode]!,
|
||||
'acceptList': instance.acceptList,
|
||||
'rejectList': instance.rejectList,
|
||||
'sort': _$AccessSortTypeEnumMap[instance.sort]!,
|
||||
'isFilterSystemApp': instance.isFilterSystemApp,
|
||||
};
|
||||
|
||||
@@ -133,6 +144,12 @@ const _$AccessControlModeEnumMap = {
|
||||
AccessControlMode.rejectSelected: 'rejectSelected',
|
||||
};
|
||||
|
||||
const _$AccessSortTypeEnumMap = {
|
||||
AccessSortType.none: 'none',
|
||||
AccessSortType.name: 'name',
|
||||
AccessSortType.time: 'time',
|
||||
};
|
||||
|
||||
_$CoreStateImpl _$$CoreStateImplFromJson(Map<String, dynamic> json) =>
|
||||
_$CoreStateImpl(
|
||||
accessControl: json['accessControl'] == null
|
||||
|
||||
@@ -23,6 +23,7 @@ mixin _$Package {
|
||||
String get packageName => throw _privateConstructorUsedError;
|
||||
String get label => throw _privateConstructorUsedError;
|
||||
bool get isSystem => throw _privateConstructorUsedError;
|
||||
int get firstInstallTime => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
@@ -34,7 +35,8 @@ abstract class $PackageCopyWith<$Res> {
|
||||
factory $PackageCopyWith(Package value, $Res Function(Package) then) =
|
||||
_$PackageCopyWithImpl<$Res, Package>;
|
||||
@useResult
|
||||
$Res call({String packageName, String label, bool isSystem});
|
||||
$Res call(
|
||||
{String packageName, String label, bool isSystem, int firstInstallTime});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -53,6 +55,7 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
|
||||
Object? packageName = null,
|
||||
Object? label = null,
|
||||
Object? isSystem = null,
|
||||
Object? firstInstallTime = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
packageName: null == packageName
|
||||
@@ -67,6 +70,10 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
|
||||
? _value.isSystem
|
||||
: isSystem // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
firstInstallTime: null == firstInstallTime
|
||||
? _value.firstInstallTime
|
||||
: firstInstallTime // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -78,7 +85,8 @@ abstract class _$$PackageImplCopyWith<$Res> implements $PackageCopyWith<$Res> {
|
||||
__$$PackageImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String packageName, String label, bool isSystem});
|
||||
$Res call(
|
||||
{String packageName, String label, bool isSystem, int firstInstallTime});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -95,6 +103,7 @@ class __$$PackageImplCopyWithImpl<$Res>
|
||||
Object? packageName = null,
|
||||
Object? label = null,
|
||||
Object? isSystem = null,
|
||||
Object? firstInstallTime = null,
|
||||
}) {
|
||||
return _then(_$PackageImpl(
|
||||
packageName: null == packageName
|
||||
@@ -109,6 +118,10 @@ class __$$PackageImplCopyWithImpl<$Res>
|
||||
? _value.isSystem
|
||||
: isSystem // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
firstInstallTime: null == firstInstallTime
|
||||
? _value.firstInstallTime
|
||||
: firstInstallTime // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -117,7 +130,10 @@ class __$$PackageImplCopyWithImpl<$Res>
|
||||
@JsonSerializable()
|
||||
class _$PackageImpl implements _Package {
|
||||
const _$PackageImpl(
|
||||
{required this.packageName, required this.label, required this.isSystem});
|
||||
{required this.packageName,
|
||||
required this.label,
|
||||
required this.isSystem,
|
||||
required this.firstInstallTime});
|
||||
|
||||
factory _$PackageImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$PackageImplFromJson(json);
|
||||
@@ -128,10 +144,12 @@ class _$PackageImpl implements _Package {
|
||||
final String label;
|
||||
@override
|
||||
final bool isSystem;
|
||||
@override
|
||||
final int firstInstallTime;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Package(packageName: $packageName, label: $label, isSystem: $isSystem)';
|
||||
return 'Package(packageName: $packageName, label: $label, isSystem: $isSystem, firstInstallTime: $firstInstallTime)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -143,12 +161,15 @@ class _$PackageImpl implements _Package {
|
||||
other.packageName == packageName) &&
|
||||
(identical(other.label, label) || other.label == label) &&
|
||||
(identical(other.isSystem, isSystem) ||
|
||||
other.isSystem == isSystem));
|
||||
other.isSystem == isSystem) &&
|
||||
(identical(other.firstInstallTime, firstInstallTime) ||
|
||||
other.firstInstallTime == firstInstallTime));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, packageName, label, isSystem);
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, packageName, label, isSystem, firstInstallTime);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -168,7 +189,8 @@ abstract class _Package implements Package {
|
||||
const factory _Package(
|
||||
{required final String packageName,
|
||||
required final String label,
|
||||
required final bool isSystem}) = _$PackageImpl;
|
||||
required final bool isSystem,
|
||||
required final int firstInstallTime}) = _$PackageImpl;
|
||||
|
||||
factory _Package.fromJson(Map<String, dynamic> json) = _$PackageImpl.fromJson;
|
||||
|
||||
@@ -179,6 +201,8 @@ abstract class _Package implements Package {
|
||||
@override
|
||||
bool get isSystem;
|
||||
@override
|
||||
int get firstInstallTime;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$PackageImplCopyWith<_$PackageImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
@@ -11,6 +11,7 @@ _$PackageImpl _$$PackageImplFromJson(Map<String, dynamic> json) =>
|
||||
packageName: json['packageName'] as String,
|
||||
label: json['label'] as String,
|
||||
isSystem: json['isSystem'] as bool,
|
||||
firstInstallTime: (json['firstInstallTime'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
|
||||
@@ -18,4 +19,5 @@ Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
|
||||
'packageName': instance.packageName,
|
||||
'label': instance.label,
|
||||
'isSystem': instance.isSystem,
|
||||
'firstInstallTime': instance.firstInstallTime,
|
||||
};
|
||||
|
||||
@@ -458,7 +458,7 @@ abstract class _NetworkDetectionSelectorState
|
||||
mixin _$ProfilesSelectorState {
|
||||
List<Profile> get profiles => throw _privateConstructorUsedError;
|
||||
String? get currentProfileId => throw _privateConstructorUsedError;
|
||||
ViewMode get viewMode => throw _privateConstructorUsedError;
|
||||
int get columns => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ProfilesSelectorStateCopyWith<ProfilesSelectorState> get copyWith =>
|
||||
@@ -471,8 +471,7 @@ abstract class $ProfilesSelectorStateCopyWith<$Res> {
|
||||
$Res Function(ProfilesSelectorState) then) =
|
||||
_$ProfilesSelectorStateCopyWithImpl<$Res, ProfilesSelectorState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{List<Profile> profiles, String? currentProfileId, ViewMode viewMode});
|
||||
$Res call({List<Profile> profiles, String? currentProfileId, int columns});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -491,7 +490,7 @@ class _$ProfilesSelectorStateCopyWithImpl<$Res,
|
||||
$Res call({
|
||||
Object? profiles = null,
|
||||
Object? currentProfileId = freezed,
|
||||
Object? viewMode = null,
|
||||
Object? columns = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
profiles: null == profiles
|
||||
@@ -502,10 +501,10 @@ class _$ProfilesSelectorStateCopyWithImpl<$Res,
|
||||
? _value.currentProfileId
|
||||
: currentProfileId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
viewMode: null == viewMode
|
||||
? _value.viewMode
|
||||
: viewMode // ignore: cast_nullable_to_non_nullable
|
||||
as ViewMode,
|
||||
columns: null == columns
|
||||
? _value.columns
|
||||
: columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -519,8 +518,7 @@ abstract class _$$ProfilesSelectorStateImplCopyWith<$Res>
|
||||
__$$ProfilesSelectorStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{List<Profile> profiles, String? currentProfileId, ViewMode viewMode});
|
||||
$Res call({List<Profile> profiles, String? currentProfileId, int columns});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -537,7 +535,7 @@ class __$$ProfilesSelectorStateImplCopyWithImpl<$Res>
|
||||
$Res call({
|
||||
Object? profiles = null,
|
||||
Object? currentProfileId = freezed,
|
||||
Object? viewMode = null,
|
||||
Object? columns = null,
|
||||
}) {
|
||||
return _then(_$ProfilesSelectorStateImpl(
|
||||
profiles: null == profiles
|
||||
@@ -548,10 +546,10 @@ class __$$ProfilesSelectorStateImplCopyWithImpl<$Res>
|
||||
? _value.currentProfileId
|
||||
: currentProfileId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
viewMode: null == viewMode
|
||||
? _value.viewMode
|
||||
: viewMode // ignore: cast_nullable_to_non_nullable
|
||||
as ViewMode,
|
||||
columns: null == columns
|
||||
? _value.columns
|
||||
: columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -562,7 +560,7 @@ class _$ProfilesSelectorStateImpl implements _ProfilesSelectorState {
|
||||
const _$ProfilesSelectorStateImpl(
|
||||
{required final List<Profile> profiles,
|
||||
required this.currentProfileId,
|
||||
required this.viewMode})
|
||||
required this.columns})
|
||||
: _profiles = profiles;
|
||||
|
||||
final List<Profile> _profiles;
|
||||
@@ -576,11 +574,11 @@ class _$ProfilesSelectorStateImpl implements _ProfilesSelectorState {
|
||||
@override
|
||||
final String? currentProfileId;
|
||||
@override
|
||||
final ViewMode viewMode;
|
||||
final int columns;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfilesSelectorState(profiles: $profiles, currentProfileId: $currentProfileId, viewMode: $viewMode)';
|
||||
return 'ProfilesSelectorState(profiles: $profiles, currentProfileId: $currentProfileId, columns: $columns)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -591,8 +589,7 @@ class _$ProfilesSelectorStateImpl implements _ProfilesSelectorState {
|
||||
const DeepCollectionEquality().equals(other._profiles, _profiles) &&
|
||||
(identical(other.currentProfileId, currentProfileId) ||
|
||||
other.currentProfileId == currentProfileId) &&
|
||||
(identical(other.viewMode, viewMode) ||
|
||||
other.viewMode == viewMode));
|
||||
(identical(other.columns, columns) || other.columns == columns));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -600,7 +597,7 @@ class _$ProfilesSelectorStateImpl implements _ProfilesSelectorState {
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(_profiles),
|
||||
currentProfileId,
|
||||
viewMode);
|
||||
columns);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -614,14 +611,14 @@ abstract class _ProfilesSelectorState implements ProfilesSelectorState {
|
||||
const factory _ProfilesSelectorState(
|
||||
{required final List<Profile> profiles,
|
||||
required final String? currentProfileId,
|
||||
required final ViewMode viewMode}) = _$ProfilesSelectorStateImpl;
|
||||
required final int columns}) = _$ProfilesSelectorStateImpl;
|
||||
|
||||
@override
|
||||
List<Profile> get profiles;
|
||||
@override
|
||||
String? get currentProfileId;
|
||||
@override
|
||||
ViewMode get viewMode;
|
||||
int get columns;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ProfilesSelectorStateImplCopyWith<_$ProfilesSelectorStateImpl>
|
||||
@@ -2372,6 +2369,7 @@ abstract class _MoreToolsSelectorState implements MoreToolsSelectorState {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PackageListSelectorState {
|
||||
List<Package> get packages => throw _privateConstructorUsedError;
|
||||
AccessControl get accessControl => throw _privateConstructorUsedError;
|
||||
bool get isAccessControl => throw _privateConstructorUsedError;
|
||||
|
||||
@@ -2386,7 +2384,10 @@ abstract class $PackageListSelectorStateCopyWith<$Res> {
|
||||
$Res Function(PackageListSelectorState) then) =
|
||||
_$PackageListSelectorStateCopyWithImpl<$Res, PackageListSelectorState>;
|
||||
@useResult
|
||||
$Res call({AccessControl accessControl, bool isAccessControl});
|
||||
$Res call(
|
||||
{List<Package> packages,
|
||||
AccessControl accessControl,
|
||||
bool isAccessControl});
|
||||
|
||||
$AccessControlCopyWith<$Res> get accessControl;
|
||||
}
|
||||
@@ -2405,10 +2406,15 @@ class _$PackageListSelectorStateCopyWithImpl<$Res,
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? packages = null,
|
||||
Object? accessControl = null,
|
||||
Object? isAccessControl = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
packages: null == packages
|
||||
? _value.packages
|
||||
: packages // ignore: cast_nullable_to_non_nullable
|
||||
as List<Package>,
|
||||
accessControl: null == accessControl
|
||||
? _value.accessControl
|
||||
: accessControl // ignore: cast_nullable_to_non_nullable
|
||||
@@ -2438,7 +2444,10 @@ abstract class _$$PackageListSelectorStateImplCopyWith<$Res>
|
||||
__$$PackageListSelectorStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({AccessControl accessControl, bool isAccessControl});
|
||||
$Res call(
|
||||
{List<Package> packages,
|
||||
AccessControl accessControl,
|
||||
bool isAccessControl});
|
||||
|
||||
@override
|
||||
$AccessControlCopyWith<$Res> get accessControl;
|
||||
@@ -2457,10 +2466,15 @@ class __$$PackageListSelectorStateImplCopyWithImpl<$Res>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? packages = null,
|
||||
Object? accessControl = null,
|
||||
Object? isAccessControl = null,
|
||||
}) {
|
||||
return _then(_$PackageListSelectorStateImpl(
|
||||
packages: null == packages
|
||||
? _value._packages
|
||||
: packages // ignore: cast_nullable_to_non_nullable
|
||||
as List<Package>,
|
||||
accessControl: null == accessControl
|
||||
? _value.accessControl
|
||||
: accessControl // ignore: cast_nullable_to_non_nullable
|
||||
@@ -2477,7 +2491,18 @@ class __$$PackageListSelectorStateImplCopyWithImpl<$Res>
|
||||
|
||||
class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
|
||||
const _$PackageListSelectorStateImpl(
|
||||
{required this.accessControl, required this.isAccessControl});
|
||||
{required final List<Package> packages,
|
||||
required this.accessControl,
|
||||
required this.isAccessControl})
|
||||
: _packages = packages;
|
||||
|
||||
final List<Package> _packages;
|
||||
@override
|
||||
List<Package> get packages {
|
||||
if (_packages is EqualUnmodifiableListView) return _packages;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_packages);
|
||||
}
|
||||
|
||||
@override
|
||||
final AccessControl accessControl;
|
||||
@@ -2486,7 +2511,7 @@ class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PackageListSelectorState(accessControl: $accessControl, isAccessControl: $isAccessControl)';
|
||||
return 'PackageListSelectorState(packages: $packages, accessControl: $accessControl, isAccessControl: $isAccessControl)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2494,6 +2519,7 @@ class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$PackageListSelectorStateImpl &&
|
||||
const DeepCollectionEquality().equals(other._packages, _packages) &&
|
||||
(identical(other.accessControl, accessControl) ||
|
||||
other.accessControl == accessControl) &&
|
||||
(identical(other.isAccessControl, isAccessControl) ||
|
||||
@@ -2501,7 +2527,11 @@ class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, accessControl, isAccessControl);
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(_packages),
|
||||
accessControl,
|
||||
isAccessControl);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -2513,9 +2543,12 @@ class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
|
||||
|
||||
abstract class _PackageListSelectorState implements PackageListSelectorState {
|
||||
const factory _PackageListSelectorState(
|
||||
{required final AccessControl accessControl,
|
||||
{required final List<Package> packages,
|
||||
required final AccessControl accessControl,
|
||||
required final bool isAccessControl}) = _$PackageListSelectorStateImpl;
|
||||
|
||||
@override
|
||||
List<Package> get packages;
|
||||
@override
|
||||
AccessControl get accessControl;
|
||||
@override
|
||||
@@ -2526,146 +2559,6 @@ abstract class _PackageListSelectorState implements PackageListSelectorState {
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ColumnsSelectorState {
|
||||
int get columns => throw _privateConstructorUsedError;
|
||||
ViewMode get viewMode => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ColumnsSelectorStateCopyWith<ColumnsSelectorState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ColumnsSelectorStateCopyWith<$Res> {
|
||||
factory $ColumnsSelectorStateCopyWith(ColumnsSelectorState value,
|
||||
$Res Function(ColumnsSelectorState) then) =
|
||||
_$ColumnsSelectorStateCopyWithImpl<$Res, ColumnsSelectorState>;
|
||||
@useResult
|
||||
$Res call({int columns, ViewMode viewMode});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ColumnsSelectorStateCopyWithImpl<$Res,
|
||||
$Val extends ColumnsSelectorState>
|
||||
implements $ColumnsSelectorStateCopyWith<$Res> {
|
||||
_$ColumnsSelectorStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? columns = null,
|
||||
Object? viewMode = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
columns: null == columns
|
||||
? _value.columns
|
||||
: columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
viewMode: null == viewMode
|
||||
? _value.viewMode
|
||||
: viewMode // ignore: cast_nullable_to_non_nullable
|
||||
as ViewMode,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ColumnsSelectorStateImplCopyWith<$Res>
|
||||
implements $ColumnsSelectorStateCopyWith<$Res> {
|
||||
factory _$$ColumnsSelectorStateImplCopyWith(_$ColumnsSelectorStateImpl value,
|
||||
$Res Function(_$ColumnsSelectorStateImpl) then) =
|
||||
__$$ColumnsSelectorStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({int columns, ViewMode viewMode});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ColumnsSelectorStateImplCopyWithImpl<$Res>
|
||||
extends _$ColumnsSelectorStateCopyWithImpl<$Res, _$ColumnsSelectorStateImpl>
|
||||
implements _$$ColumnsSelectorStateImplCopyWith<$Res> {
|
||||
__$$ColumnsSelectorStateImplCopyWithImpl(_$ColumnsSelectorStateImpl _value,
|
||||
$Res Function(_$ColumnsSelectorStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? columns = null,
|
||||
Object? viewMode = null,
|
||||
}) {
|
||||
return _then(_$ColumnsSelectorStateImpl(
|
||||
columns: null == columns
|
||||
? _value.columns
|
||||
: columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
viewMode: null == viewMode
|
||||
? _value.viewMode
|
||||
: viewMode // ignore: cast_nullable_to_non_nullable
|
||||
as ViewMode,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ColumnsSelectorStateImpl implements _ColumnsSelectorState {
|
||||
const _$ColumnsSelectorStateImpl(
|
||||
{required this.columns, required this.viewMode});
|
||||
|
||||
@override
|
||||
final int columns;
|
||||
@override
|
||||
final ViewMode viewMode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ColumnsSelectorState(columns: $columns, viewMode: $viewMode)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ColumnsSelectorStateImpl &&
|
||||
(identical(other.columns, columns) || other.columns == columns) &&
|
||||
(identical(other.viewMode, viewMode) ||
|
||||
other.viewMode == viewMode));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, columns, viewMode);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ColumnsSelectorStateImplCopyWith<_$ColumnsSelectorStateImpl>
|
||||
get copyWith =>
|
||||
__$$ColumnsSelectorStateImplCopyWithImpl<_$ColumnsSelectorStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ColumnsSelectorState implements ColumnsSelectorState {
|
||||
const factory _ColumnsSelectorState(
|
||||
{required final int columns,
|
||||
required final ViewMode viewMode}) = _$ColumnsSelectorStateImpl;
|
||||
|
||||
@override
|
||||
int get columns;
|
||||
@override
|
||||
ViewMode get viewMode;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ColumnsSelectorStateImplCopyWith<_$ColumnsSelectorStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProxiesListHeaderSelectorState {
|
||||
double get offset => throw _privateConstructorUsedError;
|
||||
|
||||
@@ -9,6 +9,7 @@ class Package with _$Package {
|
||||
required String packageName,
|
||||
required String label,
|
||||
required bool isSystem,
|
||||
required int firstInstallTime,
|
||||
}) = _Package;
|
||||
|
||||
factory Package.fromJson(Map<String, Object?> json) =>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
|
||||
part 'generated/selector.freezed.dart';
|
||||
|
||||
@@ -34,7 +37,7 @@ class ProfilesSelectorState with _$ProfilesSelectorState {
|
||||
const factory ProfilesSelectorState({
|
||||
required List<Profile> profiles,
|
||||
required String? currentProfileId,
|
||||
required ViewMode viewMode,
|
||||
required int columns,
|
||||
}) = _ProfilesSelectorState;
|
||||
}
|
||||
|
||||
@@ -132,17 +135,41 @@ class MoreToolsSelectorState with _$MoreToolsSelectorState {
|
||||
@freezed
|
||||
class PackageListSelectorState with _$PackageListSelectorState {
|
||||
const factory PackageListSelectorState({
|
||||
required List<Package> packages,
|
||||
required AccessControl accessControl,
|
||||
required bool isAccessControl,
|
||||
}) = _PackageListSelectorState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ColumnsSelectorState with _$ColumnsSelectorState {
|
||||
const factory ColumnsSelectorState({
|
||||
required int columns,
|
||||
required ViewMode viewMode,
|
||||
}) = _ColumnsSelectorState;
|
||||
extension PackageListSelectorStateExt on PackageListSelectorState {
|
||||
List<Package> getList(List<String> selectedList) {
|
||||
final isFilterSystemApp = accessControl.isFilterSystemApp;
|
||||
final sort = accessControl.sort;
|
||||
return packages
|
||||
.where((item) => isFilterSystemApp ? item.isSystem == false : true)
|
||||
.sorted(
|
||||
(a, b) {
|
||||
return switch (sort) {
|
||||
AccessSortType.none => 0,
|
||||
AccessSortType.name =>
|
||||
other.sortByChar(
|
||||
PinyinHelper.getPinyin(a.label),
|
||||
PinyinHelper.getPinyin(b.label),
|
||||
),
|
||||
AccessSortType.time => a.firstInstallTime.compareTo(b.firstInstallTime),
|
||||
};
|
||||
},
|
||||
).sorted(
|
||||
(a, b) {
|
||||
final isSelectA = selectedList.contains(a.packageName);
|
||||
final isSelectB = selectedList.contains(b.packageName);
|
||||
if (isSelectA && isSelectB) return 0;
|
||||
if (isSelectA) return -1;
|
||||
if (isSelectB) return 1;
|
||||
return 0;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -153,11 +180,10 @@ class ProxiesListHeaderSelectorState with _$ProxiesListHeaderSelectorState {
|
||||
}) = _ProxiesListHeaderSelectorState;
|
||||
}
|
||||
|
||||
|
||||
@freezed
|
||||
class ProxiesActionsState with _$ProxiesActionsState {
|
||||
const factory ProxiesActionsState({
|
||||
required bool isCurrent,
|
||||
required bool hasProvider,
|
||||
}) = _ProxiesActionsState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@ class HomePage extends StatelessWidget {
|
||||
),
|
||||
selectedLabelTextStyle: context.textTheme.labelLarge!.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
unselectedLabelTextStyle: context.textTheme.labelLarge!.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
|
||||
@@ -48,6 +48,16 @@ class App {
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<String>> getChinaPackageNames() async {
|
||||
final packageNamesString =
|
||||
await methodChannel.invokeMethod<String>("getChinaPackageNames");
|
||||
return Isolate.run<List<String>>(() {
|
||||
final List<dynamic> packageNamesRaw =
|
||||
packageNamesString != null ? json.decode(packageNamesString) : [];
|
||||
return packageNamesRaw.map((e) => e.toString()).toList();
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> openFile(String path) async {
|
||||
return await methodChannel.invokeMethod<bool>("openFile", {
|
||||
"path": path,
|
||||
|
||||
@@ -81,7 +81,6 @@ class Proxy extends ProxyPlatform {
|
||||
bool get isStart => startTime != null && startTime!.isBeforeNow;
|
||||
|
||||
onStarted(int? fd) {
|
||||
debugPrint("onStarted ==> $fd");
|
||||
if (fd == null) return;
|
||||
if (receiver != null) {
|
||||
receiver!.close();
|
||||
|
||||
@@ -79,10 +79,11 @@ class _ClashContainerState extends State<ClashContainer>
|
||||
}
|
||||
|
||||
@override
|
||||
void onDelay(Delay delay) {
|
||||
Future<void> onDelay(Delay delay) async {
|
||||
final appController = globalState.appController;
|
||||
appController.setDelay(delay);
|
||||
super.onDelay(delay);
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
74
lib/widgets/setting.dart
Normal file
74
lib/widgets/setting.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'card.dart';
|
||||
|
||||
class SettingInfoCard extends StatelessWidget {
|
||||
final Info info;
|
||||
final bool? isSelected;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const SettingInfoCard(
|
||||
this.info, {
|
||||
super.key,
|
||||
this.isSelected,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
isSelected: isSelected,
|
||||
onPressed: onPressed,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Icon(info.iconData),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
info.label,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingTextCard extends StatelessWidget {
|
||||
final String text;
|
||||
final bool? isSelected;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const SettingTextCard(
|
||||
this.text, {
|
||||
super.key,
|
||||
this.isSelected,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
onPressed: onPressed,
|
||||
isSelected: isSelected,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
text,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:emoji_regex/emoji_regex.dart';
|
||||
|
||||
import '../state.dart';
|
||||
|
||||
@@ -30,3 +31,63 @@ class TooltipText extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EmojiText extends StatelessWidget {
|
||||
final String text;
|
||||
final TextStyle? style;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
|
||||
const EmojiText(
|
||||
this.text, {
|
||||
super.key,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
List<TextSpan> _buildTextSpans(String emojis) {
|
||||
final List<TextSpan> spans = [];
|
||||
final matches = emojiRegex().allMatches(text);
|
||||
|
||||
int lastMatchEnd = 0;
|
||||
for (final match in matches) {
|
||||
if (match.start > lastMatchEnd) {
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text: text.substring(lastMatchEnd, match.start), style: style),
|
||||
);
|
||||
}
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text:match.group(0),
|
||||
style: style?.copyWith(
|
||||
fontFamily: "Twemoji",
|
||||
),
|
||||
),
|
||||
);
|
||||
lastMatchEnd = match.end;
|
||||
}
|
||||
if (lastMatchEnd < text.length) {
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text: text.substring(lastMatchEnd),
|
||||
style: style,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return spans;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RichText(
|
||||
maxLines: maxLines,
|
||||
overflow: overflow ?? TextOverflow.clip,
|
||||
text: TextSpan(
|
||||
children: _buildTextSpans(text),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,4 +24,5 @@ export 'fade_box.dart';
|
||||
export 'app_state_container.dart';
|
||||
export 'text.dart';
|
||||
export 'connection_item.dart';
|
||||
export 'builder.dart';
|
||||
export 'builder.dart';
|
||||
export 'setting.dart';
|
||||
52
pubspec.lock
52
pubspec.lock
@@ -193,14 +193,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
country_flags:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: country_flags
|
||||
sha256: dbc4f76e7c801619b2d841023e0327191ba00663f1f1b4770394e9bc6632c444
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -249,6 +241,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
emoji_regex:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: emoji_regex
|
||||
sha256: "3a25dd4d16f98b6f76dc37cc9ae49b8511891ac4b87beac9443a1e9f4634b6c7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.5"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -541,22 +541,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.1"
|
||||
jovial_misc:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jovial_misc
|
||||
sha256: f6e64f789ee311025bb367be9c9afe9759f76dd8209070b7f38e735b5f529eb1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.5"
|
||||
jovial_svg:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jovial_svg
|
||||
sha256: "000cafe183bdeba28f76d65bf93fc9b636d6f7eaa7e2a33e80c4345a28866ea8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.21"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -629,6 +613,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
lpinyin:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: lpinyin
|
||||
sha256: "0bb843363f1f65170efd09fbdfc760c7ec34fc6354f9fcb2f89e74866a0d814a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -638,7 +630,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
@@ -789,14 +781,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1243,4 +1227,4 @@ packages:
|
||||
version: "0.2.3"
|
||||
sdks:
|
||||
dart: ">=3.4.0 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
flutter: ">=3.22.3"
|
||||
|
||||
11
pubspec.yaml
11
pubspec.yaml
@@ -1,7 +1,7 @@
|
||||
name: fl_clash
|
||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||
publish_to: 'none'
|
||||
version: 0.8.51+202408051
|
||||
version: 0.8.53+202408151
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
|
||||
@@ -37,13 +37,13 @@ dependencies:
|
||||
image: ^4.1.7
|
||||
webdav_client: ^1.2.2
|
||||
dio: ^5.4.3+1
|
||||
country_flags: ^2.2.0
|
||||
win32: ^5.5.1
|
||||
ffi: ^2.1.2
|
||||
material_color_utilities: ^0.8.0
|
||||
re_editor: ^0.3.1
|
||||
re_highlight: ^0.0.3
|
||||
archive: ^3.6.1
|
||||
lpinyin: ^2.0.3
|
||||
emoji_regex: ^0.0.5
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@@ -58,8 +58,13 @@ flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/data/
|
||||
- assets/fonts/
|
||||
- assets/images/
|
||||
- assets/images/avatars/
|
||||
fonts:
|
||||
- family: Twemoji
|
||||
fonts:
|
||||
- asset: assets/fonts/Twemoji.Mozilla.ttf
|
||||
ffigen:
|
||||
name: "ClashFFI"
|
||||
output: 'lib/clash/generated/clash_ffi.dart'
|
||||
|
||||
@@ -2,7 +2,18 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
void main() async {
|
||||
import 'package:fl_clash/common/other.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
|
||||
void main() {
|
||||
print(PinyinHelper.getPinyin("ABC"));
|
||||
print(PinyinHelper.getPinyin("阿里巴巴"));
|
||||
|
||||
print('a'.compareTo('B'));
|
||||
print('A'.compareTo('B'));
|
||||
}
|
||||
|
||||
startService() async {
|
||||
// 定义服务器将要监听的地址和端口
|
||||
final host = InternetAddress.anyIPv4; // 监听所有网络接口
|
||||
const port = 8080; // 使用 8080 端口
|
||||
|
||||
Reference in New Issue
Block a user