Compare commits

..

39 Commits

Author SHA1 Message Date
chen08209
ece8a48181 Remake desktop
Optimize change proxy

Optimize network check

Fix fallback issues

Optimize lots of details
2024-12-06 20:38:51 +08:00
chen08209
4b32a096dd Update change.yaml 2024-12-06 19:24:11 +08:00
chen08209
04d6a928eb Fix android tile issues 2024-12-06 19:24:11 +08:00
chen08209
22e71ec6e1 Fix windows tray issues
Support setting bypassDomain

Update flutter version

Fix android service issues

Fix macos dock exit button issues

Add route address setting

Optimize provider view
2024-12-06 19:24:11 +08:00
chen08209
6f1b07e26b Update changelog 2024-12-06 19:24:11 +08:00
chen08209
1e3ab1c717 Update CHANGELOG.md 2024-12-06 19:24:11 +08:00
chen08209
3a43dc2fe9 Add android shortcuts
Fix init params issues

Fix dynamic color issues

Optimize navigator animate

Optimize window init

Optimize fab

Optimize save
2024-12-06 19:24:11 +08:00
chen08209
c94f64cf78 Fix the collapse issues
Add fontFamily options
2024-12-06 19:24:11 +08:00
chen08209
25764fc4d3 Update core version
Update flutter version

Optimize ip check

Optimize url-test
2024-12-06 19:24:11 +08:00
chen08209
649dbb9886 Update release message
Init auto gen changelog
2024-12-06 19:24:11 +08:00
chen08209
e1dd616d74 Fix windows tray issues
Fix urltest issues

Add auto changelog
2024-12-06 19:24:11 +08:00
chen08209
c91d78b63f Fix windows admin auto launch issues
Add android vpn options

Support proxies icon configuration

Optimize android immersion display

Fix some issues
2024-12-06 19:24:11 +08:00
chen08209
82767325e5 Optimize ip detection
Support android vpn ipv6 inbound switch

Support log export

Optimize more details
2024-12-06 19:24:11 +08:00
chen08209
3f0f7f051b Fix android system dns issues
Optimize dns default option

Fix some issues
2024-12-06 19:24:11 +08:00
chen08209
15c64327db Update readme 2024-12-06 19:24:11 +08:00
chen08209
09e35eb5dd Fix build error2 2024-12-06 19:24:11 +08:00
chen08209
77c4935106 Fix build error 2024-12-06 19:24:11 +08:00
chen08209
e6da643186 Support desktop hotkey
Support android ipv6 inbound

Support android system dns

fix some bugs
2024-12-06 19:24:11 +08:00
chen08209
61bd4e4549 Fix delete profile error 2024-12-06 19:24:11 +08:00
chen08209
a5f4d12748 Fix submit error 2 2024-12-06 19:24:11 +08:00
chen08209
9dc67f31a3 Fix submit error 2024-12-06 19:24:11 +08:00
chen08209
846ec9728f Optimize DNS strategy
Fix the problem that the tray is not displayed in some cases

Optimize tray

Update core

Fix some error
2024-12-06 19:24:11 +08:00
chen08209
6e5f1b8e5f Fix tun update issues 2024-12-06 19:24:11 +08:00
chen08209
aca4a3e979 Add DNS override
Fixed some bugs
Optimize more detail
2024-12-06 19:24:11 +08:00
chen08209
3783c3c650 Add Hosts override 2024-12-06 19:24:11 +08:00
chen08209
01aed99002 fix android tip error
fix windows auto launch error
2024-12-06 19:24:11 +08:00
chen08209
9aec1e75e4 Fix windows tray issues
Optimize windows logic
2024-12-06 19:24:11 +08:00
chen08209
5dfb95a22d Optimize app logic
Support windows administrator auto launch

Support android close vpn
2024-12-06 19:24:11 +08:00
chen08209
0f8cfa20b2 Change flutter version 2024-12-06 19:24:11 +08:00
chen08209
3933fd9d9e Support profiles sort
Support windows country flags display

Optimize proxies page and profiles page columns
2024-12-06 19:24:11 +08:00
chen08209
b7400f2ce8 Update flutter version 2024-12-06 19:24:11 +08:00
chen08209
d0ec580932 Update version 2024-12-06 19:24:11 +08:00
chen08209
6bc0be9876 Update timeout time 2024-12-06 19:24:11 +08:00
chen08209
38221bcd10 Update access control page
Fix bug
2024-12-06 19:24:11 +08:00
chen08209
bee2f8aa4f Optimize provider page
Optimize delay test

Support local backup and recovery
2024-12-06 19:24:11 +08:00
chen08209
6cfcaa4edc Fix android tile service issues 2024-12-06 19:24:11 +08:00
chen08209
3fdc69edd5 Fix linux core build error 2024-12-06 19:24:11 +08:00
chen08209
ed7ade0d72 Add proxy-only traffic statistics
Update core

Optimize more details
2024-12-06 19:24:11 +08:00
txyyh
621ddefc65 Add fdroid-repo 2024-12-06 19:24:11 +08:00
165 changed files with 12637 additions and 9610 deletions

View File

@@ -27,25 +27,6 @@ jobs:
arch: arm64
steps:
- name: Setup Mingw64
if: startsWith(matrix.platform,'windows')
uses: msys2/setup-msys2@v2
with:
msystem: mingw64
install: mingw-w64-x86_64-gcc
update: true
- name: Set Mingw64 Env
if: startsWith(matrix.platform,'windows')
run: |
echo "${{ runner.temp }}\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Check Matrix
run: |
echo "Running on ${{ matrix.os }}"
echo "Arch: ${{ runner.arch }}"
gcc --version
- name: Checkout
uses: actions/checkout@v4
with:
@@ -103,7 +84,6 @@ jobs:
path: ./dist
overwrite: true
upload:
permissions: write-all
needs: [ build ]

View File

@@ -1,12 +1,13 @@
name: change
name: changelog
on:
push:
branches:
- 'main'
tags:
- 'v*'
jobs:
changelog:
if: ${{ !contains(github.ref, '+') }}
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -16,30 +17,50 @@ jobs:
- name: Generate
run: |
tags=$(git tag --sort=creatordate)
previous=""
if [ ! -f CHANGELOG.md ]; then
echo "" > CHANGELOG.md
else
previous=$(grep -oP '^## \K.*' CHANGELOG.md | tail -n 1)
fi
for tag in $tags; do
if [ -n "$previous" ]; then
echo "## $tag" >> CHANGELOG.md
git log --pretty=format:"* %s (%h)" "$previous..$tag" >> CHANGELOG.md
echo -e "\n" >> CHANGELOG.md
tags=($(git tag --merged $(git rev-parse HEAD) --sort=-creatordate))
preTag=$(grep -oP '^## \K.*' CHANGELOG.md | head -n 1)
currentTag=""
for ((i = 0; i <= ${#tags[@]}; i++)); do
if (( i < ${#tags[@]} )); then
tag=${tags[$i]}
else
tag=""
fi
previous=$tag
if [ -n "$currentTag" ]; then
if [ "$(echo -e "$currentTag\n$preTag" | sort -V | head -n 1)" == "$currentTag" ]; then
break
fi
fi
if [ -n "$currentTag" ]; then
echo "## $currentTag" >> NEW_CHANGELOG.md
echo "" >> NEW_CHANGELOG.md
if [ -n "$tag" ]; then
git log --pretty=format:"%B" "$tag..$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> NEW_CHANGELOG.md
else
git log --pretty=format:"%B" "$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> NEW_CHANGELOG.md
fi
echo "" >> NEW_CHANGELOG.md
fi
currentTag=$tag
done
cat CHANGELOG.md >> NEW_CHANGELOG.md
cat NEW_CHANGELOG.md > CHANGELOG.md
- name: Commit
run: |
if git diff --cached --quiet; then
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add CHANGELOG.md
git commit -m "Update Changelog"
git add CHANGELOG.md
if ! git diff --cached --quiet; then
echo "Commit pushing"
git config --local user.email "chen08209@gmail.com"
git config --local user.name "chen08209"
git commit -m "Update changelog"
git push
if [ $? -eq 0 ]; then
echo "Push succeeded"
else
echo "Push failed"
exit 1
fi
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,4 +1,46 @@
## v0.8.63
## v0.8.67
- Add android shortcuts
- Fix init params issues
- Fix dynamic color issues
- Optimize navigator animate
- Optimize window init
- Optimize fab
- Optimize save
## v0.8.66
- Fix the collapse issues
- Add fontFamily options
## v0.8.65
- Update core version
- Update flutter version
- Optimize ip check
- Optimize url-test
## v0.8.64
- Update release message
- Init auto gen changelog
- Fix windows tray issues
- Fix urltest issues
- Add auto changelog
- Fix windows admin auto launch issues
@@ -10,8 +52,6 @@
- Fix some issues
## v0.8.62
- Optimize ip detection
- Support android vpn ipv6 inbound switch
@@ -28,12 +68,6 @@
- Update readme
- Update README.md 2
- Update README.md 2
- Update README.md
## v0.8.60
- Fix build error2
@@ -461,7 +495,8 @@
## v0.8.12
- Fix the problem that the download of remote resources failed after GeodataMode was turned on, which caused the application to flash back.
- Fix the problem that the download of remote resources failed after GeodataMode was turned on, which caused the
application to flash back.
- Fix edit profile error
@@ -626,5 +661,4 @@
- update mobile_scanner
- Initial commit
- Initial commit

View File

@@ -15,6 +15,7 @@ import androidx.core.graphics.drawable.toBitmap
import com.follow.clash.TempActivity
import com.follow.clash.models.CIDR
import com.follow.clash.models.Metadata
import com.follow.clash.models.VpnOptions
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -42,6 +43,40 @@ fun Metadata.getProtocol(): Int? {
return null
}
fun VpnOptions.getIpv4RouteAddress(): List<CIDR> {
return routeAddress.filter {
it.isIpv4()
}.map {
it.toCIDR()
}
}
fun VpnOptions.getIpv6RouteAddress(): List<CIDR> {
return routeAddress.filter {
it.isIpv6()
}.map {
it.toCIDR()
}
}
fun String.isIpv4(): Boolean {
val parts = split("/")
if (parts.size != 2) {
throw IllegalArgumentException("Invalid CIDR format")
}
val address = InetAddress.getByName(parts[0])
return address.address.size == 4
}
fun String.isIpv6(): Boolean {
val parts = split("/")
if (parts.size != 2) {
throw IllegalArgumentException("Invalid CIDR format")
}
val address = InetAddress.getByName(parts[0])
return address.address.size == 16
}
fun String.toCIDR(): CIDR {
val parts = split("/")
if (parts.size != 2) {
@@ -79,7 +114,7 @@ fun InetAddress.asSocketAddressText(port: Int): String {
}
}
fun Context.wrapAction(action: String):String{
fun Context.wrapAction(action: String): String {
return "${this.packageName}.action.$action"
}

View File

@@ -3,8 +3,7 @@ package com.follow.clash.models
import java.net.InetAddress
enum class AccessControlMode {
acceptSelected,
rejectSelected,
acceptSelected, rejectSelected,
}
data class AccessControl(
@@ -22,6 +21,7 @@ data class VpnOptions(
val allowBypass: Boolean,
val systemProxy: Boolean,
val bypassDomain: List<String>,
val routeAddress: List<String>,
val ipv4Address: String,
val ipv6Address: String,
val dnsServerAddress: String,

View File

@@ -17,6 +17,9 @@ import com.follow.clash.GlobalState
import com.follow.clash.MainActivity
import com.follow.clash.extensions.getActionPendingIntent
import com.follow.clash.models.VpnOptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class FlClashService : Service(), BaseServiceInterface {
@@ -69,7 +72,7 @@ class FlClashService : Service(), BaseServiceInterface {
addAction(
0,
GlobalState.getText("stop"),
getActionPendingIntent("CHANGE")
getActionPendingIntent("STOP")
)
setOngoing(true)
setShowWhen(false)
@@ -89,21 +92,23 @@ class FlClashService : Service(), BaseServiceInterface {
@SuppressLint("ForegroundServiceType", "WrongConstant")
override fun startForeground(title: String, content: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(NotificationManager::class.java)
var channel = manager?.getNotificationChannel(CHANNEL)
if (channel == null) {
channel =
NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
manager?.createNotificationChannel(channel)
CoroutineScope(Dispatchers.Default).launch {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(NotificationManager::class.java)
var channel = manager?.getNotificationChannel(CHANNEL)
if (channel == null) {
channel =
NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
manager?.createNotificationChannel(channel)
}
}
val notification =
notificationBuilder.setContentTitle(title).setContentText(content).build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else {
startForeground(notificationId, notification)
}
}
val notification =
notificationBuilder.setContentTitle(title).setContentText(content).build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else {
startForeground(notificationId, notification)
}
}
}

View File

@@ -15,20 +15,21 @@ import android.os.Build
import android.os.IBinder
import android.os.Parcel
import android.os.RemoteException
import android.util.Log
import androidx.core.app.NotificationCompat
import com.follow.clash.BaseServiceInterface
import com.follow.clash.GlobalState
import com.follow.clash.MainActivity
import com.follow.clash.R
import com.follow.clash.TempActivity
import com.follow.clash.extensions.getActionPendingIntent
import com.follow.clash.extensions.getIpv4RouteAddress
import com.follow.clash.extensions.getIpv6RouteAddress
import com.follow.clash.extensions.toCIDR
import com.follow.clash.models.AccessControlMode
import com.follow.clash.models.VpnOptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class FlClashVpnService : VpnService(), BaseServiceInterface {
@@ -42,12 +43,28 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
if (options.ipv4Address.isNotEmpty()) {
val cidr = options.ipv4Address.toCIDR()
addAddress(cidr.address, cidr.prefixLength)
addRoute("0.0.0.0", 0)
val routeAddress = options.getIpv4RouteAddress()
if (routeAddress.isNotEmpty()) {
routeAddress.forEach { i ->
Log.d("addRoute4", "address: ${i.address} prefixLength:${i.prefixLength}")
addRoute(i.address, i.prefixLength)
}
} else {
addRoute("0.0.0.0", 0)
}
}
if (options.ipv6Address.isNotEmpty()) {
val cidr = options.ipv6Address.toCIDR()
addAddress(cidr.address, cidr.prefixLength)
addRoute("::", 0)
val routeAddress = options.getIpv6RouteAddress()
if (routeAddress.isNotEmpty()) {
routeAddress.forEach { i ->
Log.d("addRoute6", "address: ${i.address} prefixLength:${i.prefixLength}")
addRoute(i.address, i.prefixLength)
}
} else {
addRoute("::", 0)
}
}
addDnsServer(options.dnsServerAddress)
setMtu(9000)

32
core/action.go Normal file
View File

@@ -0,0 +1,32 @@
//go:build !cgo
package main
import (
"encoding/json"
)
func (action Action) Json() ([]byte, error) {
data, err := json.Marshal(action)
return data, err
}
func (action Action) callback(data interface{}) bool {
if conn == nil {
return false
}
sendAction := Action{
Id: action.Id,
Method: action.Method,
Data: data,
}
res, err := sendAction.Json()
if err != nil {
return false
}
_, err = conn.Write(append(res, []byte("\n")...))
if err != nil {
return false
}
return true
}

View File

@@ -1,23 +1,10 @@
package main
import "C"
import (
"context"
"core/state"
"encoding/json"
"errors"
"fmt"
"github.com/metacubex/mihomo/constant/features"
"github.com/metacubex/mihomo/hub/route"
"github.com/samber/lo"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"time"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/adapter/outboundgroup"
@@ -27,52 +14,28 @@ import (
"github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
cp "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/hub"
"github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/hub/route"
"github.com/metacubex/mihomo/listener"
"github.com/metacubex/mihomo/log"
rp "github.com/metacubex/mihomo/rules/provider"
"github.com/metacubex/mihomo/tunnel"
"github.com/samber/lo"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
)
type ConfigExtendedParams struct {
IsPatch bool `json:"is-patch"`
IsCompatible bool `json:"is-compatible"`
SelectedMap map[string]string `json:"selected-map"`
TestURL *string `json:"test-url"`
OverrideDns bool `json:"override-dns"`
}
type GenerateConfigParams struct {
ProfileId string `json:"profile-id"`
Config config.RawConfig `json:"config" `
Params ConfigExtendedParams `json:"params"`
}
type ChangeProxyParams struct {
GroupName *string `json:"group-name"`
ProxyName *string `json:"proxy-name"`
}
type TestDelayParams struct {
ProxyName string `json:"proxy-name"`
Timeout int64 `json:"timeout"`
}
type ProcessMapItem struct {
Id int64 `json:"id"`
Value string `json:"value"`
}
type ExternalProvider struct {
Name string `json:"name"`
Type string `json:"type"`
VehicleType string `json:"vehicle-type"`
Count int `json:"count"`
Path string `json:"path"`
UpdateAt time.Time `json:"update-at"`
}
var (
isRunning = false
runLock sync.Mutex
ips = []string{"ipinfo.io", "ipapi.co", "api.ip.sb", "ipwho.is"}
b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
)
type ExternalProviders []ExternalProvider
@@ -80,30 +43,9 @@ 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) {
var err error
executor.Shutdown()
if runtime.GOOS == "windows" {
cmd := exec.Command(execPath, os.Args[1:]...)
log.Infoln("restarting: %q %q", execPath, os.Args[1:])
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Start()
if err != nil {
log.Fatalln("restarting: %s", err)
}
os.Exit(0)
}
log.Infoln("restarting: %q %q", execPath, os.Args[1:])
err = syscall.Exec(execPath, os.Args, os.Environ())
if err != nil {
log.Fatalln("restarting: %s", err)
}
func (message *Message) Json() (string, error) {
data, err := json.Marshal(message)
return string(data), err
}
func readFile(path string) ([]byte, error) {
@@ -118,19 +60,6 @@ func readFile(path string) ([]byte, error) {
return data, err
}
func removeFile(path string) error {
absPath, err := filepath.Abs(path)
if err != nil {
return err
}
err = os.Remove(absPath)
if err != nil {
return err
}
return nil
}
func getProfilePath(id string) string {
return filepath.Join(constant.Path.HomeDir(), "profiles", id+".yaml")
}
@@ -198,12 +127,13 @@ func toExternalProvider(p cp.Provider) (*ExternalProvider, error) {
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(),
Name: psp.Name(),
Type: psp.Type().String(),
VehicleType: psp.VehicleType().String(),
Count: psp.Count(),
UpdateAt: psp.UpdatedAt(),
Path: psp.Vehicle().Path(),
SubscriptionInfo: psp.GetSubscriptionInfo(),
}, nil
case *rp.RuleSetProvider:
rsp := p.(*rp.RuleSetProvider)
@@ -212,8 +142,8 @@ func toExternalProvider(p cp.Provider) (*ExternalProvider, error) {
Type: rsp.Type().String(),
VehicleType: rsp.VehicleType().String(),
Count: rsp.Count(),
Path: rsp.Vehicle().Path(),
UpdateAt: rsp.UpdatedAt(),
Path: rsp.Vehicle().Path(),
}, nil
default:
return nil, errors.New("not external provider")
@@ -247,152 +177,6 @@ func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig
return prof
}
//func Reduce[T any, U any](s []T, initVal U, f func(U, T) U) U {
// for _, v := range s {
// initVal = f(initVal, v)
// }
// return initVal
//}
//
//func Map[T, U any](slice []T, fn func(T) U) []U {
// result := make([]U, len(slice))
// for i, v := range slice {
// result[i] = fn(v)
// }
// return result
//}
//
//func replaceFromMap(s string, m map[string]string) string {
// for k, v := range m {
// s = strings.ReplaceAll(s, k, v)
// }
// return s
//}
//
//func removeDuplicateFromSlice[T any](slice []T) []T {
// result := make([]T, 0)
// seen := make(map[any]struct{})
// for _, value := range slice {
// if _, ok := seen[value]; !ok {
// result = append(result, value)
// seen[value] = struct{}{}
// }
// }
// return result
//}
//func generateProxyGroupAndRule(proxyGroup *[]map[string]any, rule *[]string) {
// var replacements = map[string]string{}
// var selectArr []map[string]any
// var urlTestArr []map[string]any
// var fallbackArr []map[string]any
// for _, group := range *proxyGroup {
// switch group["type"] {
// case "select":
// selectArr = append(selectArr, group)
// replacements[group["name"].(string)] = "Proxy"
// break
// case "url-test":
// urlTestArr = append(urlTestArr, group)
// replacements[group["name"].(string)] = "Auto"
// break
// case "fallback":
// fallbackArr = append(fallbackArr, group)
// replacements[group["name"].(string)] = "Fallback"
// break
// default:
// break
// }
// }
//
// ProxyProxies := Reduce(selectArr, []string{}, func(res []string, cur map[string]any) []string {
// if cur["proxies"] == nil {
// return res
// }
// for _, proxyName := range cur["proxies"].([]interface{}) {
// if str, ok := proxyName.(string); ok {
// str = replaceFromMap(str, replacements)
// if str != "Proxy" {
// res = append(res, str)
// }
// }
// }
// return res
// })
//
// ProxyProxies = removeDuplicateFromSlice(ProxyProxies)
//
// AutoProxies := Reduce(urlTestArr, []string{}, func(res []string, cur map[string]any) []string {
// if cur["proxies"] == nil {
// return res
// }
// for _, proxyName := range cur["proxies"].([]interface{}) {
// if str, ok := proxyName.(string); ok {
// str = replaceFromMap(str, replacements)
// if str != "Auto" {
// res = append(res, str)
// }
// }
// }
// return res
// })
//
// AutoProxies = removeDuplicateFromSlice(AutoProxies)
//
// FallbackProxies := Reduce(fallbackArr, []string{}, func(res []string, cur map[string]any) []string {
// if cur["proxies"] == nil {
// return res
// }
// for _, proxyName := range cur["proxies"].([]interface{}) {
// if str, ok := proxyName.(string); ok {
// str = replaceFromMap(str, replacements)
// if str != "Fallback" {
// res = append(res, str)
// }
// }
// }
// return res
// })
//
// FallbackProxies = removeDuplicateFromSlice(FallbackProxies)
//
// var computedProxyGroup []map[string]any
//
// if len(ProxyProxies) > 0 {
// computedProxyGroup = append(computedProxyGroup,
// map[string]any{
// "name": "Proxy",
// "type": "select",
// "proxies": ProxyProxies,
// })
// }
//
// if len(AutoProxies) > 0 {
// computedProxyGroup = append(computedProxyGroup,
// map[string]any{
// "name": "Auto",
// "type": "url-test",
// "proxies": AutoProxies,
// })
// }
//
// if len(FallbackProxies) > 0 {
// computedProxyGroup = append(computedProxyGroup,
// map[string]any{
// "name": "Fallback",
// "type": "fallback",
// "proxies": FallbackProxies,
// })
// }
//
// computedRule := Map(*rule, func(value string) string {
// return replaceFromMap(value, replacements)
// })
//
// *proxyGroup = computedProxyGroup
// *rule = computedRule
//}
func genHosts(hosts, patchHosts map[string]any) {
for k, v := range patchHosts {
hosts[k] = v
@@ -406,8 +190,6 @@ func trimArr(arr []string) (r []string) {
return
}
var ips = []string{"ipinfo.io", "ipapi.co", "api.ip.sb", "ipwho.is"}
func overrideRules(rules *[]string) {
var target = ""
for _, line := range *rules {
@@ -469,20 +251,13 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
}
}
overrideRules(&targetConfig.Rule)
//if runtime.GOOS == "android" {
// targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, "dhcp://"+dns.SystemDNSPlaceholder)
//} else if runtime.GOOS == "windows" {
// targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder)
//}
//if configParams.IsCompatible == false {
// targetConfig.ProxyProvider = make(map[string]map[string]any)
// targetConfig.RuleProvider = make(map[string]map[string]any)
// generateProxyGroupAndRule(&targetConfig.ProxyGroup, &targetConfig.Rule)
//}
}
func patchConfig(general *config.General, controller *config.Controller, tls *config.TLS) {
func patchConfig() {
log.Infoln("[Apply] patch")
general := currentConfig.General
controller := currentConfig.Controller
tls := currentConfig.TLS
tunnel.SetSniffing(general.Sniffing)
tunnel.SetFindProcessMode(general.FindProcessMode)
dialer.SetTcpConcurrent(general.TCPConcurrent)
@@ -509,17 +284,15 @@ func patchConfig(general *config.General, controller *config.Controller, tls *co
})
}
var isRunning = false
var runLock sync.Mutex
func updateListeners(general *config.General, listeners map[string]constant.InboundListener) {
func updateListeners(force bool) {
if !isRunning {
return
}
runLock.Lock()
defer runLock.Unlock()
stopListeners()
general := currentConfig.General
listeners := currentConfig.Listeners
if force == true {
stopListeners()
}
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
listener.SetAllowLan(general.AllowLan)
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
@@ -568,20 +341,22 @@ func patchSelectGroup() {
}
}
func applyConfig() error {
cfg, err := config.ParseRawConfig(state.CurrentRawConfig)
func applyConfig(rawConfig *config.RawConfig) error {
runLock.Lock()
defer runLock.Unlock()
var err error
currentConfig, err = config.ParseRawConfig(rawConfig)
if err != nil {
cfg, _ = config.ParseRawConfig(config.DefaultRawConfig())
currentConfig, _ = config.ParseRawConfig(config.DefaultRawConfig())
}
if configParams.IsPatch {
patchConfig(cfg.General, cfg.Controller, cfg.TLS)
patchConfig()
} else {
closeConnections()
handleCloseConnectionsUnLock()
runtime.GC()
hub.ApplyConfig(cfg)
hub.ApplyConfig(currentConfig)
patchSelectGroup()
}
updateListeners(cfg.General, cfg.Listeners)
externalProviders = getExternalProvidersRaw()
updateListeners(false)
return err
}

110
core/constant.go Normal file
View File

@@ -0,0 +1,110 @@
package main
import (
"github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant"
"time"
)
type ConfigExtendedParams struct {
IsPatch bool `json:"is-patch"`
IsCompatible bool `json:"is-compatible"`
SelectedMap map[string]string `json:"selected-map"`
TestURL *string `json:"test-url"`
OverrideDns bool `json:"override-dns"`
}
type GenerateConfigParams struct {
ProfileId string `json:"profile-id"`
Config config.RawConfig `json:"config" `
Params ConfigExtendedParams `json:"params"`
}
type ChangeProxyParams struct {
GroupName *string `json:"group-name"`
ProxyName *string `json:"proxy-name"`
}
type TestDelayParams struct {
ProxyName string `json:"proxy-name"`
Timeout int64 `json:"timeout"`
}
type ProcessMapItem struct {
Id int64 `json:"id"`
Value string `json:"value"`
}
type ExternalProvider struct {
Name string `json:"name"`
Type string `json:"type"`
VehicleType string `json:"vehicle-type"`
Count int `json:"count"`
Path string `json:"path"`
UpdateAt time.Time `json:"update-at"`
SubscriptionInfo *provider.SubscriptionInfo `json:"subscription-info"`
}
const (
messageMethod Method = "message"
initClashMethod Method = "initClash"
getIsInitMethod Method = "getIsInit"
forceGcMethod Method = "forceGc"
shutdownMethod Method = "shutdown"
validateConfigMethod Method = "validateConfig"
updateConfigMethod Method = "updateConfig"
getProxiesMethod Method = "getProxies"
changeProxyMethod Method = "changeProxy"
getTrafficMethod Method = "getTraffic"
getTotalTrafficMethod Method = "getTotalTraffic"
resetTrafficMethod Method = "resetTraffic"
asyncTestDelayMethod Method = "asyncTestDelay"
getConnectionsMethod Method = "getConnections"
closeConnectionsMethod Method = "closeConnections"
closeConnectionMethod Method = "closeConnection"
getExternalProvidersMethod Method = "getExternalProviders"
getExternalProviderMethod Method = "getExternalProvider"
updateGeoDataMethod Method = "updateGeoData"
updateExternalProviderMethod Method = "updateExternalProvider"
sideLoadExternalProviderMethod Method = "sideLoadExternalProvider"
startLogMethod Method = "startLog"
stopLogMethod Method = "stopLog"
startListenerMethod Method = "startListener"
stopListenerMethod Method = "stopListener"
)
type Method string
type Action struct {
Id string `json:"id"`
Method Method `json:"method"`
Data interface{} `json:"data"`
}
type MessageType string
type Delay struct {
Name string `json:"name"`
Value int32 `json:"value"`
}
type Message struct {
Type MessageType `json:"type"`
Data interface{} `json:"data"`
}
type Process struct {
Id int64 `json:"id"`
Metadata *constant.Metadata `json:"metadata"`
}
const (
LogMessage MessageType = "log"
ProtectMessage MessageType = "protect"
DelayMessage MessageType = "delay"
ProcessMessage MessageType = "process"
RequestMessage MessageType = "request"
StartedMessage MessageType = "started"
LoadedMessage MessageType = "loaded"
)

View File

@@ -1,3 +1,5 @@
//go:build cgo
package dart_bridge
/*

View File

@@ -0,0 +1,7 @@
//go:build !cgo
package dart_bridge
func SendToPort(port int64, msg string) bool {
return false
}

View File

@@ -1,20 +0,0 @@
//go:build android
package main
import "C"
import (
"github.com/metacubex/mihomo/dns"
"github.com/metacubex/mihomo/log"
"strings"
)
//export updateDns
func updateDns(s *C.char) {
dnsList := C.GoString(s)
go func() {
log.Infoln("[DNS] updateDns %s", dnsList)
dns.UpdateSystemDNS(strings.Split(dnsList, ","))
dns.FlushCacheWithDefaultResolver()
}()
}

View File

@@ -1,6 +1,6 @@
module core
go 1.21.0
go 1.21
replace github.com/metacubex/mihomo => ./Clash.Meta

View File

@@ -1,82 +1,71 @@
package main
/*
#include <stdlib.h>
*/
import "C"
import (
"context"
bridge "core/dart-bridge"
"core/state"
"encoding/json"
"fmt"
"github.com/metacubex/mihomo/common/utils"
"os"
"runtime"
"sort"
"sync"
"time"
"unsafe"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/common/observable"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant"
cp "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/listener"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic"
"runtime"
"sort"
"time"
)
var configParams = ConfigExtendedParams{}
var (
isInit = false
configParams = ConfigExtendedParams{}
externalProviders = map[string]cp.Provider{}
logSubscriber observable.Subscription[log.Event]
currentConfig *config.Config
)
var externalProviders = map[string]cp.Provider{}
var isInit = false
//export start
func start() {
runLock.Lock()
defer runLock.Unlock()
isRunning = true
}
//export stop
func stop() {
runLock.Lock()
go func() {
defer runLock.Unlock()
isRunning = false
stopListeners()
}()
}
//export initClash
func initClash(homeDirStr *C.char) bool {
func handleInitClash(homeDirStr string) bool {
if !isInit {
constant.SetHomeDir(C.GoString(homeDirStr))
constant.SetHomeDir(homeDirStr)
isInit = true
}
return isInit
}
//export getIsInit
func getIsInit() bool {
return isInit
}
//export restartClash
func restartClash() bool {
execPath, _ := os.Executable()
go restartExecutable(execPath)
func handleStartListener() bool {
runLock.Lock()
defer runLock.Unlock()
isRunning = true
updateListeners(true)
return true
}
//export shutdownClash
func shutdownClash() bool {
func handleStopListener() bool {
runLock.Lock()
defer runLock.Unlock()
isRunning = false
listener.StopListener()
return true
}
func handleGetIsInit() bool {
return isInit
}
func handleForceGc() {
go func() {
log.Infoln("[APP] request force GC")
runtime.GC()
}()
}
func handleShutdown() bool {
stopListeners()
executor.Shutdown()
runtime.GC()
@@ -84,106 +73,81 @@ func shutdownClash() bool {
return true
}
//export forceGc
func forceGc() {
go func() {
log.Infoln("[APP] request force GC")
runtime.GC()
}()
func handleValidateConfig(bytes []byte) string {
_, err := config.UnmarshalRawConfig(bytes)
if err != nil {
return err.Error()
}
return ""
}
//export validateConfig
func validateConfig(s *C.char, port C.longlong) {
i := int64(port)
bytes := []byte(C.GoString(s))
go func() {
_, err := config.UnmarshalRawConfig(bytes)
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
bridge.SendToPort(i, "")
}()
func handleUpdateConfig(bytes []byte) string {
var params = &GenerateConfigParams{}
err := json.Unmarshal(bytes, params)
if err != nil {
return err.Error()
}
configParams = params.Params
prof := decorationConfig(params.ProfileId, params.Config)
err = applyConfig(prof)
if err != nil {
return err.Error()
}
return ""
}
var updateLock sync.Mutex
//export updateConfig
func updateConfig(s *C.char, port C.longlong) {
i := int64(port)
paramsString := C.GoString(s)
go func() {
updateLock.Lock()
defer updateLock.Unlock()
var params = &GenerateConfigParams{}
err := json.Unmarshal([]byte(paramsString), params)
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
configParams = params.Params
prof := decorationConfig(params.ProfileId, params.Config)
state.CurrentRawConfig = prof
err = applyConfig()
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
bridge.SendToPort(i, "")
}()
}
//export clearEffect
func clearEffect(s *C.char) {
id := C.GoString(s)
go func() {
_ = removeFile(getProfilePath(id))
_ = removeFile(getProfileProvidersPath(id))
}()
}
//export getProxies
func getProxies() *C.char {
func handleGetProxies() string {
runLock.Lock()
defer runLock.Unlock()
data, err := json.Marshal(tunnel.ProxiesWithProviders())
if err != nil {
return C.CString("")
return ""
}
return C.CString(string(data))
return string(data)
}
//export changeProxy
func changeProxy(s *C.char) {
paramsString := C.GoString(s)
var params = &ChangeProxyParams{}
err := json.Unmarshal([]byte(paramsString), params)
if err != nil {
log.Infoln("Unmarshal ChangeProxyParams %v", err)
}
groupName := *params.GroupName
proxyName := *params.ProxyName
proxies := tunnel.ProxiesWithProviders()
group, ok := proxies[groupName]
if !ok {
func handleChangeProxy(data string, fn func(string string)) {
runLock.Lock()
go func() {
defer runLock.Unlock()
var params = &ChangeProxyParams{}
err := json.Unmarshal([]byte(data), params)
if err != nil {
fn(err.Error())
return
}
groupName := *params.GroupName
proxyName := *params.ProxyName
proxies := tunnel.ProxiesWithProviders()
group, ok := proxies[groupName]
if !ok {
fn("Not found group")
return
}
adapterProxy := group.(*adapter.Proxy)
selector, ok := adapterProxy.ProxyAdapter.(outboundgroup.SelectAble)
if !ok {
fn("Group is not selectable")
return
}
if proxyName == "" {
selector.ForceSet(proxyName)
} else {
err = selector.Set(proxyName)
}
if err != nil {
fn(err.Error())
return
}
fn("")
return
}
adapterProxy := group.(*adapter.Proxy)
selector, ok := adapterProxy.ProxyAdapter.(outboundgroup.SelectAble)
if !ok {
return
}
if proxyName == "" {
selector.ForceSet(proxyName)
} else {
err = selector.Set(proxyName)
}
if err == nil {
log.Infoln("[SelectAble] %s selected %s", groupName, proxyName)
}
}()
}
//export getTraffic
func getTraffic() *C.char {
up, down := statistic.DefaultManager.Current(state.CurrentState.OnlyProxy)
func handleGetTraffic(onlyProxy bool) string {
up, down := statistic.DefaultManager.Current(onlyProxy)
traffic := map[string]int64{
"up": up,
"down": down,
@@ -191,14 +155,13 @@ func getTraffic() *C.char {
data, err := json.Marshal(traffic)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
return ""
}
return C.CString(string(data))
return string(data)
}
//export getTotalTraffic
func getTotalTraffic() *C.char {
up, down := statistic.DefaultManager.Total(state.CurrentState.OnlyProxy)
func handleGetTotalTraffic(onlyProxy bool) string {
up, down := statistic.DefaultManager.Total(onlyProxy)
traffic := map[string]int64{
"up": up,
"down": down,
@@ -206,31 +169,27 @@ func getTotalTraffic() *C.char {
data, err := json.Marshal(traffic)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
return ""
}
return C.CString(string(data))
return string(data)
}
//export resetTraffic
func resetTraffic() {
func handleResetTraffic() {
statistic.DefaultManager.ResetStatistic()
}
//export asyncTestDelay
func asyncTestDelay(s *C.char, port C.longlong) {
i := int64(port)
paramsString := C.GoString(s)
func handleAsyncTestDelay(paramsString string, fn func(string)) {
b.Go(paramsString, func() (bool, error) {
var params = &TestDelayParams{}
err := json.Unmarshal([]byte(paramsString), params)
if err != nil {
bridge.SendToPort(i, "")
fn("")
return false, nil
}
expectedStatus, err := utils.NewUnsignedRanges[uint16]("")
if err != nil {
bridge.SendToPort(i, "")
fn("")
return false, nil
}
@@ -247,7 +206,7 @@ func asyncTestDelay(s *C.char, port C.longlong) {
if proxy == nil {
delayData.Value = -1
data, _ := json.Marshal(delayData)
bridge.SendToPort(i, string(data))
fn(string(data))
return false, nil
}
@@ -255,44 +214,30 @@ func asyncTestDelay(s *C.char, port C.longlong) {
if err != nil || delay == 0 {
delayData.Value = -1
data, _ := json.Marshal(delayData)
bridge.SendToPort(i, string(data))
fn(string(data))
return false, nil
}
delayData.Value = int32(delay)
data, _ := json.Marshal(delayData)
bridge.SendToPort(i, string(data))
fn(string(data))
return false, nil
})
}
//export getVersionInfo
func getVersionInfo() *C.char {
versionInfo := map[string]string{
"clashName": constant.Name,
"version": "1.18.5",
}
data, err := json.Marshal(versionInfo)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
}
return C.CString(string(data))
}
//export getConnections
func getConnections() *C.char {
func handleGetConnections() string {
runLock.Lock()
defer runLock.Unlock()
snapshot := statistic.DefaultManager.Snapshot()
data, err := json.Marshal(snapshot)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
return ""
}
return C.CString(string(data))
return string(data)
}
//export closeConnections
func closeConnections() {
func handleCloseConnectionsUnLock() bool {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
err := c.Close()
if err != nil {
@@ -300,43 +245,37 @@ func closeConnections() {
}
return true
})
return true
}
//export closeConnection
func closeConnection(id *C.char) {
connectionId := C.GoString(id)
func handleCloseConnections() bool {
runLock.Lock()
defer runLock.Unlock()
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
err := c.Close()
if err != nil {
return false
}
return true
})
return true
}
func handleCloseConnection(connectionId string) bool {
runLock.Lock()
defer runLock.Unlock()
c := statistic.DefaultManager.Get(connectionId)
if c == nil {
return
return false
}
_ = c.Close()
return true
}
//export getProviders
func getProviders() *C.char {
data, err := json.Marshal(tunnel.Providers())
var msg *C.char
if err != nil {
msg = C.CString("")
return msg
}
msg = C.CString(string(data))
return msg
}
//export getProvider
func getProvider(name *C.char) *C.char {
providerName := C.GoString(name)
providers := tunnel.Providers()
data, err := json.Marshal(providers[providerName])
if err != nil {
return C.CString("")
}
return C.CString(string(data))
}
//export getExternalProviders
func getExternalProviders() *C.char {
func handleGetExternalProviders() string {
runLock.Lock()
defer runLock.Unlock()
externalProviders = getExternalProvidersRaw()
eps := make([]ExternalProvider, 0)
for _, p := range externalProviders {
externalProvider, err := toExternalProvider(p)
@@ -348,123 +287,125 @@ func getExternalProviders() *C.char {
sort.Sort(ExternalProviders(eps))
data, err := json.Marshal(eps)
if err != nil {
return C.CString("")
return ""
}
return C.CString(string(data))
return string(data)
}
//export getExternalProvider
func getExternalProvider(name *C.char) *C.char {
externalProviderName := C.GoString(name)
func handleGetExternalProvider(externalProviderName string) string {
runLock.Lock()
defer runLock.Unlock()
externalProvider, exist := externalProviders[externalProviderName]
if !exist {
return C.CString("")
return ""
}
e, err := toExternalProvider(externalProvider)
if err != nil {
return C.CString("")
return ""
}
data, err := json.Marshal(e)
if err != nil {
return C.CString("")
return ""
}
return C.CString(string(data))
return string(data)
}
//export updateGeoData
func updateGeoData(geoType *C.char, geoName *C.char, port C.longlong) {
i := int64(port)
geoTypeString := C.GoString(geoType)
geoNameString := C.GoString(geoName)
func handleUpdateGeoData(geoType string, geoName string, fn func(value string)) {
go func() {
path := constant.Path.Resolve(geoNameString)
switch geoTypeString {
path := constant.Path.Resolve(geoName)
switch geoType {
case "MMDB":
err := updater.UpdateMMDBWithPath(path)
if err != nil {
bridge.SendToPort(i, err.Error())
fn(err.Error())
return
}
case "ASN":
err := updater.UpdateASNWithPath(path)
if err != nil {
bridge.SendToPort(i, err.Error())
fn(err.Error())
return
}
case "GeoIp":
err := updater.UpdateGeoIpWithPath(path)
if err != nil {
bridge.SendToPort(i, err.Error())
fn(err.Error())
return
}
case "GeoSite":
err := updater.UpdateGeoSiteWithPath(path)
if err != nil {
bridge.SendToPort(i, err.Error())
fn(err.Error())
return
}
}
bridge.SendToPort(i, "")
fn("")
}()
}
//export updateExternalProvider
func updateExternalProvider(providerName *C.char, port C.longlong) {
i := int64(port)
providerNameString := C.GoString(providerName)
func handleUpdateExternalProvider(providerName string, fn func(value string)) {
go func() {
externalProvider, exist := externalProviders[providerNameString]
externalProvider, exist := externalProviders[providerName]
if !exist {
bridge.SendToPort(i, "external provider is not exist")
fn("external provider is not exist")
return
}
err := externalProvider.Update()
if err != nil {
bridge.SendToPort(i, err.Error())
fn(err.Error())
return
}
bridge.SendToPort(i, "")
fn("")
}()
}
//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)
func handleSideLoadExternalProvider(providerName string, data []byte, fn func(value string)) {
go func() {
externalProvider, exist := externalProviders[providerNameString]
runLock.Lock()
defer runLock.Unlock()
externalProvider, exist := externalProviders[providerName]
if !exist {
bridge.SendToPort(i, "external provider is not exist")
fn("external provider is not exist")
return
}
err := sideUpdateExternalProvider(externalProvider, bytes)
err := sideUpdateExternalProvider(externalProvider, data)
if err != nil {
bridge.SendToPort(i, err.Error())
fn(err.Error())
return
}
bridge.SendToPort(i, "")
fn("")
}()
}
//export initNativeApiBridge
func initNativeApiBridge(api unsafe.Pointer) {
bridge.InitDartApi(api)
func handleStartLog() {
if logSubscriber != nil {
log.UnSubscribe(logSubscriber)
logSubscriber = nil
}
logSubscriber = log.Subscribe()
go func() {
for logData := range logSubscriber {
if logData.LogLevel < log.Level() {
continue
}
message := &Message{
Type: LogMessage,
Data: logData,
}
SendMessage(*message)
}
}()
}
//export initMessage
func initMessage(port C.longlong) {
i := int64(port)
Port = i
}
//export freeCString
func freeCString(s *C.char) {
C.free(unsafe.Pointer(s))
func handleStopLog() {
if logSubscriber != nil {
log.UnSubscribe(logSubscriber)
logSubscriber = nil
}
}
func init() {
provider.HealthcheckHook = func(name string, delay uint16) {
adapter.UrlTestHook = func(name string, delay uint16) {
delayData := &Delay{
Name: name,
}

182
core/lib.go Normal file
View File

@@ -0,0 +1,182 @@
//go:build cgo
package main
/*
#include <stdlib.h>
*/
import "C"
import (
bridge "core/dart-bridge"
"unsafe"
)
//export initNativeApiBridge
func initNativeApiBridge(api unsafe.Pointer) {
bridge.InitDartApi(api)
}
//export initMessage
func initMessage(port C.longlong) {
i := int64(port)
Port = i
}
//export freeCString
func freeCString(s *C.char) {
C.free(unsafe.Pointer(s))
}
//export initClash
func initClash(homeDirStr *C.char) bool {
return handleInitClash(C.GoString(homeDirStr))
}
//export startListener
func startListener() {
handleStartListener()
}
//export stopListener
func stopListener() {
handleStopListener()
}
//export getIsInit
func getIsInit() bool {
return handleGetIsInit()
}
//export shutdownClash
func shutdownClash() bool {
return handleShutdown()
}
//export forceGc
func forceGc() {
handleForceGc()
}
//export validateConfig
func validateConfig(s *C.char, port C.longlong) {
i := int64(port)
bytes := []byte(C.GoString(s))
go func() {
bridge.SendToPort(i, handleValidateConfig(bytes))
}()
}
//export updateConfig
func updateConfig(s *C.char, port C.longlong) {
i := int64(port)
bytes := []byte(C.GoString(s))
go func() {
bridge.SendToPort(i, handleUpdateConfig(bytes))
}()
}
//export getProxies
func getProxies() *C.char {
return C.CString(handleGetProxies())
}
//export changeProxy
func changeProxy(s *C.char, port C.longlong) {
i := int64(port)
paramsString := C.GoString(s)
handleChangeProxy(paramsString, func(value string) {
bridge.SendToPort(i, value)
})
}
//export getTraffic
func getTraffic(port C.int) *C.char {
onlyProxy := int(port) == 1
return C.CString(handleGetTraffic(onlyProxy))
}
//export getTotalTraffic
func getTotalTraffic(port C.int) *C.char {
onlyProxy := int(port) == 1
return C.CString(handleGetTotalTraffic(onlyProxy))
}
//export resetTraffic
func resetTraffic() {
handleResetTraffic()
}
//export asyncTestDelay
func asyncTestDelay(s *C.char, port C.longlong) {
i := int64(port)
paramsString := C.GoString(s)
handleAsyncTestDelay(paramsString, func(value string) {
bridge.SendToPort(i, value)
})
}
//export getConnections
func getConnections() *C.char {
return C.CString(handleGetConnections())
}
//export closeConnections
func closeConnections() {
handleCloseConnections()
}
//export closeConnection
func closeConnection(id *C.char) {
connectionId := C.GoString(id)
handleCloseConnection(connectionId)
}
//export getExternalProviders
func getExternalProviders() *C.char {
return C.CString(handleGetExternalProviders())
}
//export getExternalProvider
func getExternalProvider(externalProviderNameChar *C.char) *C.char {
externalProviderName := C.GoString(externalProviderNameChar)
return C.CString(handleGetExternalProvider(externalProviderName))
}
//export updateGeoData
func updateGeoData(geoTypeChar *C.char, geoNameChar *C.char, port C.longlong) {
i := int64(port)
geoType := C.GoString(geoTypeChar)
geoName := C.GoString(geoNameChar)
handleUpdateGeoData(geoType, geoName, func(value string) {
bridge.SendToPort(i, value)
})
}
//export updateExternalProvider
func updateExternalProvider(providerNameChar *C.char, port C.longlong) {
i := int64(port)
providerName := C.GoString(providerNameChar)
handleUpdateExternalProvider(providerName, func(value string) {
bridge.SendToPort(i, value)
})
}
//export sideLoadExternalProvider
func sideLoadExternalProvider(providerNameChar *C.char, dataChar *C.char, port C.longlong) {
i := int64(port)
providerName := C.GoString(providerNameChar)
data := []byte(C.GoString(dataChar))
handleSideLoadExternalProvider(providerName, data, func(value string) {
bridge.SendToPort(i, value)
})
}
//export startLog
func startLog() {
handleStartLog()
}
//export stopLog
func stopLog() {
handleStopLog()
}

270
core/lib_android.go Normal file
View File

@@ -0,0 +1,270 @@
//go:build android && cgo
package main
import "C"
import (
"core/platform"
"core/state"
t "core/tun"
"encoding/json"
"errors"
"fmt"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/dns"
"github.com/metacubex/mihomo/listener/sing_tun"
"github.com/metacubex/mihomo/log"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
)
type ProcessMap struct {
m sync.Map
}
type FdMap struct {
m sync.Map
}
type Fd struct {
Id int64 `json:"id"`
Value int64 `json:"value"`
}
var (
tunListener *sing_tun.Listener
fdMap FdMap
fdCounter int64 = 0
counter int64 = 0
processMap ProcessMap
tunLock sync.Mutex
runTime *time.Time
errBlocked = errors.New("blocked")
)
func (cm *ProcessMap) Store(key int64, value string) {
cm.m.Store(key, value)
}
func (cm *ProcessMap) Load(key int64) (string, bool) {
value, ok := cm.m.Load(key)
if !ok || value == nil {
return "", false
}
return value.(string), true
}
func (cm *FdMap) Store(key int64) {
cm.m.Store(key, struct{}{})
}
func (cm *FdMap) Load(key int64) bool {
_, ok := cm.m.Load(key)
return ok
}
//export startTUN
func startTUN(fd C.int, port C.longlong) {
i := int64(port)
ServicePort = i
if fd == 0 {
tunLock.Lock()
defer tunLock.Unlock()
now := time.Now()
runTime = &now
SendMessage(Message{
Type: StartedMessage,
Data: strconv.FormatInt(runTime.UnixMilli(), 10),
})
return
}
initSocketHook()
go func() {
tunLock.Lock()
defer tunLock.Unlock()
f := int(fd)
tunListener, _ = t.Start(f, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack)
if tunListener != nil {
log.Infoln("TUN address: %v", tunListener.Address())
}
now := time.Now()
runTime = &now
}()
}
//export getRunTime
func getRunTime() *C.char {
if runTime == nil {
return C.CString("")
}
return C.CString(strconv.FormatInt(runTime.UnixMilli(), 10))
}
//export stopTun
func stopTun() {
removeSocketHook()
go func() {
tunLock.Lock()
defer tunLock.Unlock()
runTime = nil
if tunListener != nil {
_ = tunListener.Close()
}
}()
}
//export setFdMap
func setFdMap(fd C.long) {
fdInt := int64(fd)
go func() {
fdMap.Store(fdInt)
}()
}
func markSocket(fd Fd) {
SendMessage(Message{
Type: ProtectMessage,
Data: fd,
})
}
func initSocketHook() {
dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error {
if platform.ShouldBlockConnection() {
return errBlocked
}
return conn.Control(func(fd uintptr) {
fdInt := int64(fd)
timeout := time.After(500 * time.Millisecond)
id := atomic.AddInt64(&fdCounter, 1)
markSocket(Fd{
Id: id,
Value: fdInt,
})
for {
select {
case <-timeout:
return
default:
exists := fdMap.Load(id)
if exists {
return
}
time.Sleep(20 * time.Millisecond)
}
}
})
}
}
func removeSocketHook() {
dialer.DefaultSocketHook = nil
}
func init() {
process.DefaultPackageNameResolver = func(metadata *constant.Metadata) (string, error) {
if metadata == nil {
return "", process.ErrInvalidNetwork
}
id := atomic.AddInt64(&counter, 1)
timeout := time.After(200 * time.Millisecond)
SendMessage(Message{
Type: ProcessMessage,
Data: Process{
Id: id,
Metadata: metadata,
},
})
for {
select {
case <-timeout:
return "", errors.New("package resolver timeout")
default:
value, exists := processMap.Load(id)
if exists {
return value, nil
}
time.Sleep(20 * time.Millisecond)
}
}
}
}
//export setProcessMap
func setProcessMap(s *C.char) {
if s == nil {
return
}
paramsString := C.GoString(s)
go func() {
var processMapItem = &ProcessMapItem{}
err := json.Unmarshal([]byte(paramsString), processMapItem)
if err == nil {
processMap.Store(processMapItem.Id, processMapItem.Value)
}
}()
}
//export getCurrentProfileName
func getCurrentProfileName() *C.char {
if state.CurrentState == nil {
return C.CString("")
}
return C.CString(state.CurrentState.CurrentProfileName)
}
//export getAndroidVpnOptions
func getAndroidVpnOptions() *C.char {
tunLock.Lock()
defer tunLock.Unlock()
options := state.AndroidVpnOptions{
Enable: state.CurrentState.Enable,
Port: currentConfig.General.MixedPort,
Ipv4Address: state.DefaultIpv4Address,
Ipv6Address: state.GetIpv6Address(),
AccessControl: state.CurrentState.AccessControl,
SystemProxy: state.CurrentState.SystemProxy,
AllowBypass: state.CurrentState.AllowBypass,
RouteAddress: state.CurrentState.RouteAddress,
BypassDomain: state.CurrentState.BypassDomain,
DnsServerAddress: state.GetDnsServerAddress(),
}
data, err := json.Marshal(options)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
}
return C.CString(string(data))
}
//export setState
func setState(s *C.char) {
paramsString := C.GoString(s)
err := json.Unmarshal([]byte(paramsString), state.CurrentState)
if err != nil {
return
}
}
//export updateDns
func updateDns(s *C.char) {
dnsList := C.GoString(s)
go func() {
log.Infoln("[DNS] updateDns %s", dnsList)
dns.UpdateSystemDNS(strings.Split(dnsList, ","))
dns.FlushCacheWithDefaultResolver()
}()
}

View File

@@ -1,38 +0,0 @@
package main
import "C"
import (
"github.com/metacubex/mihomo/common/observable"
"github.com/metacubex/mihomo/log"
)
var logSubscriber observable.Subscription[log.Event]
//export startLog
func startLog() {
if logSubscriber != nil {
log.UnSubscribe(logSubscriber)
logSubscriber = nil
}
logSubscriber = log.Subscribe()
go func() {
for logData := range logSubscriber {
if logData.LogLevel < log.Level() {
continue
}
message := &Message{
Type: LogMessage,
Data: logData,
}
SendMessage(*message)
}
}()
}
//export stopLog
func stopLog() {
if logSubscriber != nil {
log.UnSubscribe(logSubscriber)
logSubscriber = nil
}
}

View File

@@ -1,10 +1,17 @@
//go:build !cgo
package main
import "C"
import (
"fmt"
"os"
)
func main() {
fmt.Println("init clash")
args := os.Args
if len(args) <= 1 {
fmt.Println("Arguments error")
os.Exit(1)
}
startServer(args[1])
}

8
core/main_cgo.go Normal file
View File

@@ -0,0 +1,8 @@
//go:build cgo
package main
import "C"
func main() {
}

View File

@@ -1,77 +1,13 @@
//go:build !cgo
package main
import (
bridge "core/dart-bridge"
"encoding/json"
"github.com/metacubex/mihomo/constant"
)
var Port int64
var ServicePort int64
type MessageType string
const (
LogMessage MessageType = "log"
ProtectMessage MessageType = "protect"
DelayMessage MessageType = "delay"
ProcessMessage MessageType = "process"
RequestMessage MessageType = "request"
StartedMessage MessageType = "started"
LoadedMessage MessageType = "loaded"
)
type Delay struct {
Name string `json:"name"`
Value int32 `json:"value"`
}
type Process struct {
Id int64 `json:"id"`
Metadata *constant.Metadata `json:"metadata"`
}
type Message struct {
Type MessageType `json:"type"`
Data interface{} `json:"data"`
}
func (message *Message) Json() (string, error) {
data, err := json.Marshal(message)
return string(data), err
}
func SendMessage(message Message) {
s, err := message.Json()
if err != nil {
return
}
if handler, ok := messageHandlers[message.Type]; ok {
handler(s)
} else {
sendToPort(s)
}
}
var messageHandlers = map[MessageType]func(string) bool{
ProtectMessage: sendToServicePort,
ProcessMessage: sendToServicePort,
StartedMessage: conditionalSend,
LoadedMessage: conditionalSend,
}
func sendToPort(s string) bool {
return bridge.SendToPort(Port, s)
}
func sendToServicePort(s string) bool {
return bridge.SendToPort(ServicePort, s)
}
func conditionalSend(s string) bool {
isSuccess := sendToPort(s)
if !isSuccess {
return sendToServicePort(s)
}
return isSuccess
Action{
Method: messageMethod,
}.callback(s)
}

47
core/message_cgo.go Normal file
View File

@@ -0,0 +1,47 @@
//go:build cgo
package main
import (
bridge "core/dart-bridge"
)
var (
Port int64 = -1
ServicePort int64 = -1
)
func SendMessage(message Message) {
s, err := message.Json()
if err != nil {
return
}
if handler, ok := messageHandlers[message.Type]; ok {
handler(s)
} else {
sendToPort(s)
}
}
var messageHandlers = map[MessageType]func(string) bool{
ProtectMessage: sendToServicePort,
ProcessMessage: sendToServicePort,
StartedMessage: conditionalSend,
LoadedMessage: conditionalSend,
}
func sendToPort(s string) bool {
return bridge.SendToPort(Port, s)
}
func sendToServicePort(s string) bool {
return bridge.SendToPort(ServicePort, s)
}
func conditionalSend(s string) bool {
isSuccess := sendToPort(s)
if !isSuccess {
return sendToServicePort(s)
}
return isSuccess
}

View File

@@ -1,4 +1,4 @@
//go:build android
//go:build android && cgo
package platform

View File

@@ -1,81 +0,0 @@
//go:build android
package main
import "C"
import (
"encoding/json"
"errors"
"github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/constant"
"sync"
"sync/atomic"
"time"
)
type ProcessMap struct {
m sync.Map
}
func (cm *ProcessMap) Store(key int64, value string) {
cm.m.Store(key, value)
}
func (cm *ProcessMap) Load(key int64) (string, bool) {
value, ok := cm.m.Load(key)
if !ok || value == nil {
return "", false
}
return value.(string), true
}
var counter int64 = 0
var processMap ProcessMap
func init() {
process.DefaultPackageNameResolver = func(metadata *constant.Metadata) (string, error) {
if metadata == nil {
return "", process.ErrInvalidNetwork
}
id := atomic.AddInt64(&counter, 1)
timeout := time.After(200 * time.Millisecond)
SendMessage(Message{
Type: ProcessMessage,
Data: Process{
Id: id,
Metadata: metadata,
},
})
for {
select {
case <-timeout:
return "", errors.New("package resolver timeout")
default:
value, exists := processMap.Load(id)
if exists {
return value, nil
}
time.Sleep(20 * time.Millisecond)
}
}
}
}
//export setProcessMap
func setProcessMap(s *C.char) {
if s == nil {
return
}
paramsString := C.GoString(s)
go func() {
var processMapItem = &ProcessMapItem{}
err := json.Unmarshal([]byte(paramsString), processMapItem)
if err == nil {
processMap.Store(processMapItem.Id, processMapItem.Value)
}
}()
}

162
core/server.go Normal file
View File

@@ -0,0 +1,162 @@
//go:build !cgo
package main
import (
"bufio"
"encoding/json"
"fmt"
"net"
"strconv"
)
var conn net.Conn = nil
func startServer(arg string) {
_, err := strconv.Atoi(arg)
if err != nil {
conn, err = net.Dial("unix", arg)
} else {
conn, err = net.Dial("tcp", fmt.Sprintf("127.0.0.1:%s", arg))
}
if err != nil {
panic(err.Error())
}
defer func(conn net.Conn) {
_ = conn.Close()
}(conn)
reader := bufio.NewReader(conn)
for {
data, err := reader.ReadString('\n')
if err != nil {
return
}
var action = &Action{}
err = json.Unmarshal([]byte(data), action)
if err != nil {
return
}
go handleAction(action)
}
}
func handleAction(action *Action) {
switch action.Method {
case initClashMethod:
data := action.Data.(string)
action.callback(handleInitClash(data))
return
case getIsInitMethod:
action.callback(handleGetIsInit())
return
case forceGcMethod:
handleForceGc()
return
case shutdownMethod:
action.callback(handleShutdown())
return
case validateConfigMethod:
data := []byte(action.Data.(string))
action.callback(handleValidateConfig(data))
return
case updateConfigMethod:
data := []byte(action.Data.(string))
action.callback(handleUpdateConfig(data))
return
case getProxiesMethod:
action.callback(handleGetProxies())
return
case changeProxyMethod:
data := action.Data.(string)
handleChangeProxy(data, func(value string) {
action.callback(value)
})
return
case getTrafficMethod:
data := action.Data.(bool)
action.callback(handleGetTraffic(data))
return
case getTotalTrafficMethod:
data := action.Data.(bool)
action.callback(handleGetTotalTraffic(data))
return
case resetTrafficMethod:
handleResetTraffic()
return
case asyncTestDelayMethod:
data := action.Data.(string)
handleAsyncTestDelay(data, func(value string) {
action.callback(value)
})
return
case getConnectionsMethod:
action.callback(handleGetConnections())
return
case closeConnectionsMethod:
action.callback(handleCloseConnections())
return
case closeConnectionMethod:
id := action.Data.(string)
action.callback(handleCloseConnection(id))
return
case getExternalProvidersMethod:
action.callback(handleGetExternalProviders())
return
case getExternalProviderMethod:
externalProviderName := action.Data.(string)
action.callback(handleGetExternalProvider(externalProviderName))
case updateGeoDataMethod:
paramsString := action.Data.(string)
var params = map[string]string{}
err := json.Unmarshal([]byte(paramsString), &params)
if err != nil {
action.callback(err.Error())
return
}
geoType := params["geoType"]
geoName := params["geoName"]
handleUpdateGeoData(geoType, geoName, func(value string) {
action.callback(value)
})
return
case updateExternalProviderMethod:
providerName := action.Data.(string)
handleUpdateExternalProvider(providerName, func(value string) {
action.callback(value)
})
return
case sideLoadExternalProviderMethod:
paramsString := action.Data.(string)
var params = map[string]string{}
err := json.Unmarshal([]byte(paramsString), &params)
if err != nil {
action.callback(err.Error())
return
}
providerName := params["providerName"]
data := params["data"]
handleSideLoadExternalProvider(providerName, []byte(data), func(value string) {
action.callback(value)
})
return
case startLogMethod:
handleStartLog()
return
case stopLogMethod:
handleStopLog()
return
case startListenerMethod:
action.callback(handleStartListener())
return
case stopListenerMethod:
action.callback(handleStopListener())
return
}
}

View File

@@ -1,46 +0,0 @@
package main
import "C"
import (
"core/state"
"encoding/json"
"fmt"
)
//export getCurrentProfileName
func getCurrentProfileName() *C.char {
if state.CurrentState == nil {
return C.CString("")
}
return C.CString(state.CurrentState.CurrentProfileName)
}
//export getAndroidVpnOptions
func getAndroidVpnOptions() *C.char {
options := state.AndroidVpnOptions{
Enable: state.CurrentState.Enable,
Port: state.CurrentRawConfig.MixedPort,
Ipv4Address: state.DefaultIpv4Address,
Ipv6Address: state.GetIpv6Address(),
AccessControl: state.CurrentState.AccessControl,
SystemProxy: state.CurrentState.SystemProxy,
AllowBypass: state.CurrentState.AllowBypass,
BypassDomain: state.CurrentState.BypassDomain,
DnsServerAddress: state.GetDnsServerAddress(),
}
data, err := json.Marshal(options)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
}
return C.CString(string(data))
}
//export setState
func setState(s *C.char) {
paramsString := C.GoString(s)
err := json.Unmarshal([]byte(paramsString), state.CurrentState)
if err != nil {
return
}
}

View File

@@ -1,13 +1,11 @@
package state
//go:build android && cgo
import "github.com/metacubex/mihomo/config"
package state
var DefaultIpv4Address = "172.19.0.1/30"
var DefaultDnsAddress = "172.19.0.2"
var DefaultIpv6Address = "fdfe:dcba:9876::1/126"
var CurrentRawConfig = config.DefaultRawConfig()
type AndroidVpnOptions struct {
Enable bool `json:"enable"`
Port int `json:"port"`
@@ -15,6 +13,7 @@ type AndroidVpnOptions struct {
AllowBypass bool `json:"allowBypass"`
SystemProxy bool `json:"systemProxy"`
BypassDomain []string `json:"bypassDomain"`
RouteAddress []string `json:"routeAddress"`
Ipv4Address string `json:"ipv4Address"`
Ipv6Address string `json:"ipv6Address"`
DnsServerAddress string `json:"dnsServerAddress"`
@@ -32,6 +31,7 @@ type AndroidVpnRawOptions struct {
AccessControl *AccessControl `json:"accessControl"`
AllowBypass bool `json:"allowBypass"`
SystemProxy bool `json:"systemProxy"`
RouteAddress []string `json:"routeAddress"`
Ipv6 bool `json:"ipv6"`
BypassDomain []string `json:"bypassDomain"`
}
@@ -39,7 +39,6 @@ type AndroidVpnRawOptions struct {
type State struct {
AndroidVpnRawOptions
CurrentProfileName string `json:"currentProfileName"`
OnlyProxy bool `json:"onlyProxy"`
}
var CurrentState = &State{}
@@ -53,7 +52,5 @@ func GetIpv6Address() string {
}
func GetDnsServerAddress() string {
//prefix, _ := netip.ParsePrefix(DefaultIpv4Address)
//return prefix.Addr().String()
return DefaultDnsAddress
}

View File

@@ -1,157 +0,0 @@
//go:build android
package main
import "C"
import (
"core/platform"
t "core/tun"
"errors"
"github.com/metacubex/mihomo/listener/sing_tun"
"strconv"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/log"
)
var tunLock sync.Mutex
var runTime *time.Time
type FdMap struct {
m sync.Map
}
func (cm *FdMap) Store(key int64) {
cm.m.Store(key, struct{}{})
}
func (cm *FdMap) Load(key int64) bool {
_, ok := cm.m.Load(key)
return ok
}
var (
tunListener *sing_tun.Listener
fdMap FdMap
fdCounter int64 = 0
)
//export startTUN
func startTUN(fd C.int, port C.longlong) {
i := int64(port)
ServicePort = i
if fd == 0 {
tunLock.Lock()
defer tunLock.Unlock()
now := time.Now()
runTime = &now
SendMessage(Message{
Type: StartedMessage,
Data: strconv.FormatInt(runTime.UnixMilli(), 10),
})
return
}
initSocketHook()
go func() {
tunLock.Lock()
defer tunLock.Unlock()
f := int(fd)
tunListener, _ = t.Start(f)
if tunListener != nil {
log.Infoln("TUN address: %v", tunListener.Address())
}
now := time.Now()
runTime = &now
SendMessage(Message{
Type: StartedMessage,
Data: strconv.FormatInt(runTime.UnixMilli(), 10),
})
}()
}
//export getRunTime
func getRunTime() *C.char {
if runTime == nil {
return C.CString("")
}
return C.CString(strconv.FormatInt(runTime.UnixMilli(), 10))
}
//export stopTun
func stopTun() {
removeSocketHook()
go func() {
tunLock.Lock()
defer tunLock.Unlock()
runTime = nil
if tunListener != nil {
_ = tunListener.Close()
}
}()
}
var errBlocked = errors.New("blocked")
//export setFdMap
func setFdMap(fd C.long) {
fdInt := int64(fd)
go func() {
fdMap.Store(fdInt)
}()
}
type Fd struct {
Id int64 `json:"id"`
Value int64 `json:"value"`
}
func markSocket(fd Fd) {
SendMessage(Message{
Type: ProtectMessage,
Data: fd,
})
}
func initSocketHook() {
dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error {
if platform.ShouldBlockConnection() {
return errBlocked
}
return conn.Control(func(fd uintptr) {
fdInt := int64(fd)
timeout := time.After(500 * time.Millisecond)
id := atomic.AddInt64(&fdCounter, 1)
markSocket(Fd{
Id: id,
Value: fdInt,
})
for {
select {
case <-timeout:
return
default:
exists := fdMap.Load(id)
if exists {
return
}
time.Sleep(20 * time.Millisecond)
}
}
})
}
}
func removeSocketHook() {
dialer.DefaultSocketHook = nil
}

View File

@@ -1,10 +1,11 @@
//go:build android
//go:build android && cgo
package tun
import "C"
import (
"core/state"
"github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing_tun"
"github.com/metacubex/mihomo/log"
@@ -23,7 +24,7 @@ type Props struct {
Dns6 string `json:"dns6"`
}
func Start(fd int) (*sing_tun.Listener, error) {
func Start(fd int, device string, stack constant.TUNStack) (*sing_tun.Listener, error) {
var prefix4 []netip.Prefix
tempPrefix4, err := netip.ParsePrefix(state.DefaultIpv4Address)
if err != nil {
@@ -46,8 +47,8 @@ func Start(fd int) (*sing_tun.Listener, error) {
options := LC.Tun{
Enable: true,
Device: state.CurrentRawConfig.Tun.Device,
Stack: state.CurrentRawConfig.Tun.Stack,
Device: device,
Stack: stack,
DNSHijack: dnsHijack,
AutoRoute: false,
AutoDetectInterface: false,

View File

@@ -1,8 +1,10 @@
import 'dart:async';
import 'package:animations/animations.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/manager/hotkey_manager.dart';
import 'package:fl_clash/manager/manager.dart';
import 'package:fl_clash/plugins/app.dart';
@@ -19,6 +21,7 @@ runAppWithPreferences(
Widget child, {
required AppState appState,
required Config config,
required AppFlowingState appFlowingState,
required ClashConfig clashConfig,
}) {
runApp(MultiProvider(
@@ -30,7 +33,7 @@ runAppWithPreferences(
create: (_) => config,
),
ChangeNotifierProvider<AppFlowingState>(
create: (_) => AppFlowingState(),
create: (_) => appFlowingState,
),
ChangeNotifierProxyProvider2<Config, ClashConfig, AppState>(
create: (_) => appState,
@@ -244,8 +247,10 @@ class ApplicationState extends State<Application> {
@override
Future<void> dispose() async {
linkManager.destroy();
await globalState.appController.savePreferences();
super.dispose();
_cancelTimer();
await clashService?.destroy();
await globalState.appController.savePreferences();
await globalState.appController.handleExit();
super.dispose();
}
}

View File

@@ -1,3 +1,4 @@
export 'core.dart';
export 'lib.dart';
export 'message.dart';
export 'service.dart';
export 'message.dart';

View File

@@ -1,41 +1,26 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:ffi/ffi.dart';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/clash/interface.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'generated/clash_ffi.dart';
import 'package:flutter/services.dart';
import 'package:path/path.dart';
class ClashCore {
static ClashCore? _instance;
static final receiver = ReceivePort();
late final ClashFFI clashFFI;
late final DynamicLibrary lib;
DynamicLibrary _getClashLib() {
if (Platform.isWindows) {
return DynamicLibrary.open("libclash.dll");
}
if (Platform.isMacOS) {
return DynamicLibrary.open("libclash.dylib");
}
if (Platform.isAndroid || Platform.isLinux) {
return DynamicLibrary.open("libclash.so");
}
throw "Platform is not supported";
}
late ClashInterface clashInterface;
ClashCore._internal() {
lib = _getClashLib();
clashFFI = ClashFFI(lib);
clashFFI.initNativeApiBridge(
NativeApi.initializeApiDLData,
);
if (Platform.isAndroid) {
clashInterface = clashLib!;
} else {
clashInterface = clashService!;
}
}
factory ClashCore() {
@@ -43,67 +28,62 @@ class ClashCore {
return _instance!;
}
bool init(String homeDir) {
final homeDirChar = homeDir.toNativeUtf8().cast<Char>();
final isInit = clashFFI.initClash(homeDirChar) == 1;
malloc.free(homeDirChar);
return isInit;
}
shutdown() {
clashFFI.shutdownClash();
lib.close();
}
bool get isInit => clashFFI.getIsInit() == 1;
Future<String> validateConfig(String data) {
final completer = Completer<String>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
completer.complete(message);
receiver.close();
Future<void> _initGeo() async {
final homePath = await appPath.getHomeDirPath();
final homeDir = Directory(homePath);
final isExists = await homeDir.exists();
if (!isExists) {
await homeDir.create(recursive: true);
}
const geoFileNameList = [
mmdbFileName,
geoIpFileName,
geoSiteFileName,
asnFileName,
];
try {
for (final geoFileName in geoFileNameList) {
final geoFile = File(
join(homePath, geoFileName),
);
final isExists = await geoFile.exists();
if (isExists) {
continue;
}
final data = await rootBundle.load('assets/data/$geoFileName');
List<int> bytes = data.buffer.asUint8List();
await geoFile.writeAsBytes(bytes, flush: true);
}
});
final dataChar = data.toNativeUtf8().cast<Char>();
clashFFI.validateConfig(
dataChar,
receiver.sendPort.nativePort,
);
malloc.free(dataChar);
return completer.future;
} catch (e) {
exit(0);
}
}
Future<String> updateConfig(UpdateConfigParams updateConfigParams) {
final completer = Completer<String>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
completer.complete(message);
receiver.close();
}
});
final params = json.encode(updateConfigParams);
final paramsChar = params.toNativeUtf8().cast<Char>();
clashFFI.updateConfig(
paramsChar,
receiver.sendPort.nativePort,
);
malloc.free(paramsChar);
return completer.future;
Future<bool> init({
required ClashConfig clashConfig,
required Config config,
}) async {
await _initGeo();
final homeDirPath = await appPath.getHomeDirPath();
return await clashInterface.init(homeDirPath);
}
initMessage() {
clashFFI.initMessage(
receiver.sendPort.nativePort,
);
shutdown() async {
await clashInterface.shutdown();
}
Future<List<Group>> getProxiesGroups() {
final proxiesRaw = clashFFI.getProxies();
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(proxiesRaw);
FutureOr<bool> get isInit => clashInterface.isInit;
FutureOr<String> validateConfig(String data) {
return clashInterface.validateConfig(data);
}
Future<String> updateConfig(UpdateConfigParams updateConfigParams) async {
return await clashInterface.updateConfig(updateConfigParams);
}
Future<List<Group>> getProxiesGroups() async {
final proxiesRawString = await clashInterface.getProxies();
return Isolate.run<List<Group>>(() {
if (proxiesRawString.isEmpty) return [];
final proxies = (json.decode(proxiesRawString) ?? {}) as Map;
@@ -133,256 +113,112 @@ class ClashCore {
});
}
Future<List<ExternalProvider>> getExternalProviders() {
final externalProvidersRaw = clashFFI.getExternalProviders();
final externalProvidersRawString =
externalProvidersRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(externalProvidersRaw);
return Isolate.run<List<ExternalProvider>>(() {
final externalProviders =
(json.decode(externalProvidersRawString) as List<dynamic>)
.map(
(item) => ExternalProvider.fromJson(item),
)
.toList();
return externalProviders;
});
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async {
return await clashInterface.changeProxy(changeProxyParams);
}
ExternalProvider? getExternalProvider(String externalProviderName) {
final externalProviderNameChar =
externalProviderName.toNativeUtf8().cast<Char>();
final externalProviderRaw =
clashFFI.getExternalProvider(externalProviderNameChar);
malloc.free(externalProviderNameChar);
final externalProviderRawString =
externalProviderRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(externalProviderRaw);
if (externalProviderRawString.isEmpty) return null;
return ExternalProvider.fromJson(json.decode(externalProviderRawString));
Future<List<Connection>> getConnections() async {
final res = await clashInterface.getConnections();
final connectionsData = json.decode(res) as Map;
final connectionsRaw = connectionsData['connections'] as List? ?? [];
return connectionsRaw.map((e) => Connection.fromJson(e)).toList();
}
closeConnection(String id) {
clashInterface.closeConnection(id);
}
closeConnections() {
clashInterface.closeConnections();
}
Future<List<ExternalProvider>> getExternalProviders() async {
final externalProvidersRawString =
await clashInterface.getExternalProviders();
return Isolate.run<List<ExternalProvider>>(
() {
final externalProviders =
(json.decode(externalProvidersRawString) as List<dynamic>)
.map(
(item) => ExternalProvider.fromJson(item),
)
.toList();
return externalProviders;
},
);
}
Future<ExternalProvider?> getExternalProvider(
String externalProviderName) async {
final externalProvidersRawString =
await clashInterface.getExternalProvider(externalProviderName);
if (externalProvidersRawString == null) {
return null;
}
if (externalProvidersRawString.isEmpty) {
return null;
}
return ExternalProvider.fromJson(json.decode(externalProvidersRawString));
}
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;
return clashInterface.updateGeoData(geoType: geoType, geoName: geoName);
}
Future<String> sideLoadExternalProvider({
required String providerName,
required String data,
}) {
final completer = Completer<String>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
completer.complete(message);
receiver.close();
}
});
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
final dataChar = data.toNativeUtf8().cast<Char>();
clashFFI.sideLoadExternalProvider(
providerNameChar,
dataChar,
receiver.sendPort.nativePort,
);
malloc.free(providerNameChar);
malloc.free(dataChar);
return completer.future;
return clashInterface.sideLoadExternalProvider(
providerName: providerName, data: data);
}
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,
receiver.sendPort.nativePort,
);
malloc.free(providerNameChar);
return completer.future;
}) async {
return clashInterface.updateExternalProvider(providerName);
}
changeProxy(ChangeProxyParams changeProxyParams) {
final params = json.encode(changeProxyParams);
final paramsChar = params.toNativeUtf8().cast<Char>();
clashFFI.changeProxy(paramsChar);
malloc.free(paramsChar);
startListener() async {
await clashInterface.startListener();
}
start() {
clashFFI.start();
stopListener() async {
await clashInterface.stopListener();
}
stop() {
clashFFI.stop();
Future<Delay> getDelay(String proxyName) async {
final data = await clashInterface.asyncTestDelay(proxyName);
return Delay.fromJson(json.decode(data));
}
Future<Delay> getDelay(String proxyName) {
final delayParams = {
"proxy-name": proxyName,
"timeout": httpTimeoutDuration.inMilliseconds,
};
final completer = Completer<Delay>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
completer.complete(Delay.fromJson(json.decode(message)));
receiver.close();
}
});
final delayParamsChar =
json.encode(delayParams).toNativeUtf8().cast<Char>();
clashFFI.asyncTestDelay(
delayParamsChar,
receiver.sendPort.nativePort,
);
malloc.free(delayParamsChar);
return completer.future;
Future<Traffic> getTraffic(bool value) async {
final trafficString = await clashInterface.getTraffic(value);
return Traffic.fromMap(json.decode(trafficString));
}
clearEffect(String profileId) {
final profileIdChar = profileId.toNativeUtf8().cast<Char>();
clashFFI.clearEffect(profileIdChar);
malloc.free(profileIdChar);
Future<Traffic> getTotalTraffic(bool value) async {
final totalTrafficString = await clashInterface.getTotalTraffic(value);
return Traffic.fromMap(json.decode(totalTrafficString));
}
VersionInfo getVersionInfo() {
final versionInfoRaw = clashFFI.getVersionInfo();
final versionInfo = json.decode(versionInfoRaw.cast<Utf8>().toDartString());
clashFFI.freeCString(versionInfoRaw);
return VersionInfo.fromJson(versionInfo);
resetTraffic() {
clashInterface.resetTraffic();
}
setState(CoreState state) {
final stateChar = json.encode(state).toNativeUtf8().cast<Char>();
clashFFI.setState(stateChar);
malloc.free(stateChar);
}
String getCurrentProfileName() {
final currentProfileRaw = clashFFI.getCurrentProfileName();
final currentProfile = currentProfileRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(currentProfileRaw);
return currentProfile;
}
AndroidVpnOptions getAndroidVpnOptions() {
final vpnOptionsRaw = clashFFI.getAndroidVpnOptions();
final vpnOptions = json.decode(vpnOptionsRaw.cast<Utf8>().toDartString());
clashFFI.freeCString(vpnOptionsRaw);
return AndroidVpnOptions.fromJson(vpnOptions);
}
Traffic getTraffic() {
final trafficRaw = clashFFI.getTraffic();
final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString());
clashFFI.freeCString(trafficRaw);
return Traffic.fromMap(trafficMap);
}
Traffic getTotalTraffic() {
final trafficRaw = clashFFI.getTotalTraffic();
final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString());
clashFFI.freeCString(trafficRaw);
return Traffic.fromMap(trafficMap);
}
void resetTraffic() {
clashFFI.resetTraffic();
}
void startLog() {
clashFFI.startLog();
startLog() {
clashInterface.startLog();
}
stopLog() {
clashFFI.stopLog();
}
startTun(int fd, int port) {
if (!Platform.isAndroid) return;
clashFFI.startTUN(fd, port);
}
updateDns(String dns) {
if (!Platform.isAndroid) return;
final dnsChar = dns.toNativeUtf8().cast<Char>();
clashFFI.updateDns(dnsChar);
malloc.free(dnsChar);
clashInterface.stopLog();
}
requestGc() {
clashFFI.forceGc();
}
void stopTun() {
clashFFI.stopTun();
}
void setProcessMap(ProcessMapItem processMapItem) {
final processMapItemChar =
json.encode(processMapItem).toNativeUtf8().cast<Char>();
clashFFI.setProcessMap(processMapItemChar);
malloc.free(processMapItemChar);
}
void setFdMap(int fd) {
clashFFI.setFdMap(fd);
}
DateTime? getRunTime() {
final runTimeRaw = clashFFI.getRunTime();
final runTimeString = runTimeRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(runTimeRaw);
if (runTimeString.isEmpty) return null;
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
}
List<Connection> getConnections() {
final connectionsDataRaw = clashFFI.getConnections();
final connectionsData =
json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map;
clashFFI.freeCString(connectionsDataRaw);
final connectionsRaw = connectionsData['connections'] as List? ?? [];
return connectionsRaw.map((e) => Connection.fromJson(e)).toList();
}
closeConnection(String id) {
final idChar = id.toNativeUtf8().cast<Char>();
clashFFI.closeConnection(idChar);
malloc.free(idChar);
}
closeConnections() {
clashFFI.closeConnections();
clashInterface.forceGc();
}
}

File diff suppressed because it is too large Load Diff

59
lib/clash/interface.dart Normal file
View File

@@ -0,0 +1,59 @@
import 'dart:async';
import 'package:fl_clash/models/models.dart';
mixin ClashInterface {
FutureOr<bool> init(String homeDir);
FutureOr<void> shutdown();
FutureOr<bool> get isInit;
forceGc();
FutureOr<String> validateConfig(String data);
Future<String> asyncTestDelay(String proxyName);
FutureOr<String> updateConfig(UpdateConfigParams updateConfigParams);
FutureOr<String> getProxies();
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams);
Future<bool> startListener();
Future<bool> stopListener();
FutureOr<String> getExternalProviders();
FutureOr<String>? getExternalProvider(String externalProviderName);
Future<String> updateGeoData({
required String geoType,
required String geoName,
});
Future<String> sideLoadExternalProvider({
required String providerName,
required String data,
});
Future<String> updateExternalProvider(String providerName);
FutureOr<String> getTraffic(bool value);
FutureOr<String> getTotalTraffic(bool value);
resetTraffic();
startLog();
stopLog();
FutureOr<String> getConnections();
FutureOr<bool> closeConnection(String id);
FutureOr<bool> closeConnections();
}

367
lib/clash/lib.dart Normal file
View File

@@ -0,0 +1,367 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:ffi/ffi.dart';
import 'package:fl_clash/common/constant.dart';
import 'package:fl_clash/models/models.dart';
import 'generated/clash_ffi.dart';
import 'interface.dart';
class ClashLib with ClashInterface {
static ClashLib? _instance;
final receiver = ReceivePort();
late final ClashFFI clashFFI;
late final DynamicLibrary lib;
ClashLib._internal() {
lib = DynamicLibrary.open("libclash.so");
clashFFI = ClashFFI(lib);
clashFFI.initNativeApiBridge(
NativeApi.initializeApiDLData,
);
}
factory ClashLib() {
_instance ??= ClashLib._internal();
return _instance!;
}
initMessage() {
clashFFI.initMessage(
receiver.sendPort.nativePort,
);
}
@override
bool init(String homeDir) {
final homeDirChar = homeDir.toNativeUtf8().cast<Char>();
final isInit = clashFFI.initClash(homeDirChar) == 1;
malloc.free(homeDirChar);
return isInit;
}
@override
shutdown() async {
clashFFI.shutdownClash();
lib.close();
}
@override
bool get isInit => clashFFI.getIsInit() == 1;
@override
Future<String> validateConfig(String data) {
final completer = Completer<String>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
completer.complete(message);
receiver.close();
}
});
final dataChar = data.toNativeUtf8().cast<Char>();
clashFFI.validateConfig(
dataChar,
receiver.sendPort.nativePort,
);
malloc.free(dataChar);
return completer.future;
}
@override
Future<String> updateConfig(UpdateConfigParams updateConfigParams) {
final completer = Completer<String>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
completer.complete(message);
receiver.close();
}
});
final params = json.encode(updateConfigParams);
final paramsChar = params.toNativeUtf8().cast<Char>();
clashFFI.updateConfig(
paramsChar,
receiver.sendPort.nativePort,
);
malloc.free(paramsChar);
return completer.future;
}
@override
String getProxies() {
final proxiesRaw = clashFFI.getProxies();
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(proxiesRaw);
return proxiesRawString;
}
@override
String getExternalProviders() {
final externalProvidersRaw = clashFFI.getExternalProviders();
final externalProvidersRawString =
externalProvidersRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(externalProvidersRaw);
return externalProvidersRawString;
}
@override
String getExternalProvider(String externalProviderName) {
final externalProviderNameChar =
externalProviderName.toNativeUtf8().cast<Char>();
final externalProviderRaw =
clashFFI.getExternalProvider(externalProviderNameChar);
malloc.free(externalProviderNameChar);
final externalProviderRawString =
externalProviderRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(externalProviderRaw);
return externalProviderRawString;
}
@override
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;
}
@override
Future<String> sideLoadExternalProvider({
required String providerName,
required String data,
}) {
final completer = Completer<String>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
completer.complete(message);
receiver.close();
}
});
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
final dataChar = data.toNativeUtf8().cast<Char>();
clashFFI.sideLoadExternalProvider(
providerNameChar,
dataChar,
receiver.sendPort.nativePort,
);
malloc.free(providerNameChar);
malloc.free(dataChar);
return completer.future;
}
@override
Future<String> updateExternalProvider(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,
receiver.sendPort.nativePort,
);
malloc.free(providerNameChar);
return completer.future;
}
@override
Future<String> changeProxy(ChangeProxyParams changeProxyParams) {
final completer = Completer<String>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
completer.complete(message);
receiver.close();
}
});
final params = json.encode(changeProxyParams);
final paramsChar = params.toNativeUtf8().cast<Char>();
clashFFI.changeProxy(
paramsChar,
receiver.sendPort.nativePort,
);
malloc.free(paramsChar);
return completer.future;
}
@override
String getConnections() {
final connectionsDataRaw = clashFFI.getConnections();
final connectionsString = connectionsDataRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(connectionsDataRaw);
return connectionsString;
}
@override
closeConnection(String id) {
final idChar = id.toNativeUtf8().cast<Char>();
clashFFI.closeConnection(idChar);
malloc.free(idChar);
return true;
}
@override
closeConnections() {
clashFFI.closeConnections();
return true;
}
@override
startListener() async {
clashFFI.startListener();
return true;
}
@override
stopListener() async {
clashFFI.stopListener();
return true;
}
@override
Future<String> asyncTestDelay(String proxyName) {
final delayParams = {
"proxy-name": proxyName,
"timeout": httpTimeoutDuration.inMilliseconds,
};
final completer = Completer<String>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
completer.complete(message);
receiver.close();
}
});
final delayParamsChar =
json.encode(delayParams).toNativeUtf8().cast<Char>();
clashFFI.asyncTestDelay(
delayParamsChar,
receiver.sendPort.nativePort,
);
malloc.free(delayParamsChar);
return completer.future;
}
@override
String getTraffic(bool value) {
final trafficRaw = clashFFI.getTraffic(value ? 1 : 0);
final trafficString = trafficRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(trafficRaw);
return trafficString;
}
@override
String getTotalTraffic(bool value) {
final trafficRaw = clashFFI.getTotalTraffic(value ? 1 : 0);
clashFFI.freeCString(trafficRaw);
return trafficRaw.cast<Utf8>().toDartString();
}
@override
void resetTraffic() {
clashFFI.resetTraffic();
}
@override
void startLog() {
clashFFI.startLog();
}
@override
stopLog() {
clashFFI.stopLog();
}
@override
forceGc() {
clashFFI.forceGc();
}
/// Android
startTun(int fd, int port) {
if (!Platform.isAndroid) return;
clashFFI.startTUN(fd, port);
}
stopTun() {
clashFFI.stopTun();
}
updateDns(String dns) {
if (!Platform.isAndroid) return;
final dnsChar = dns.toNativeUtf8().cast<Char>();
clashFFI.updateDns(dnsChar);
malloc.free(dnsChar);
}
setProcessMap(ProcessMapItem processMapItem) {
final processMapItemChar =
json.encode(processMapItem).toNativeUtf8().cast<Char>();
clashFFI.setProcessMap(processMapItemChar);
malloc.free(processMapItemChar);
}
setState(CoreState state) {
final stateChar = json.encode(state).toNativeUtf8().cast<Char>();
clashFFI.setState(stateChar);
malloc.free(stateChar);
}
String getCurrentProfileName() {
final currentProfileRaw = clashFFI.getCurrentProfileName();
final currentProfile = currentProfileRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(currentProfileRaw);
return currentProfile;
}
AndroidVpnOptions getAndroidVpnOptions() {
final vpnOptionsRaw = clashFFI.getAndroidVpnOptions();
final vpnOptions = json.decode(vpnOptionsRaw.cast<Utf8>().toDartString());
clashFFI.freeCString(vpnOptionsRaw);
return AndroidVpnOptions.fromJson(vpnOptions);
}
setFdMap(int fd) {
clashFFI.setFdMap(fd);
}
DateTime? getRunTime() {
final runTimeRaw = clashFFI.getRunTime();
final runTimeString = runTimeRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(runTimeRaw);
if (runTimeString.isEmpty) return null;
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
}
}
final clashLib = Platform.isAndroid ? ClashLib() : null;

View File

@@ -1,42 +1,40 @@
import 'dart:async';
import 'dart:convert';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/foundation.dart';
import 'core.dart';
class ClashMessage {
StreamSubscription? subscription;
final controller = StreamController();
ClashMessage._() {
if (subscription != null) {
subscription!.cancel();
subscription = null;
}
subscription = ClashCore.receiver.listen((message) {
final m = AppMessage.fromJson(json.decode(message));
for (final AppMessageListener listener in _listeners) {
switch (m.type) {
case AppMessageType.log:
listener.onLog(Log.fromJson(m.data));
break;
case AppMessageType.delay:
listener.onDelay(Delay.fromJson(m.data));
break;
case AppMessageType.request:
listener.onRequest(Connection.fromJson(m.data));
break;
case AppMessageType.started:
listener.onStarted(m.data);
break;
case AppMessageType.loaded:
listener.onLoaded(m.data);
break;
clashLib?.receiver.listen(controller.add);
controller.stream.listen(
(message) {
final m = AppMessage.fromJson(json.decode(message));
for (final AppMessageListener listener in _listeners) {
switch (m.type) {
case AppMessageType.log:
listener.onLog(Log.fromJson(m.data));
break;
case AppMessageType.delay:
listener.onDelay(Delay.fromJson(m.data));
break;
case AppMessageType.request:
listener.onRequest(Connection.fromJson(m.data));
break;
case AppMessageType.started:
listener.onStarted(m.data);
break;
case AppMessageType.loaded:
listener.onLoaded(m.data);
break;
}
}
}
});
},
);
}
static final ClashMessage instance = ClashMessage._();

View File

@@ -1,54 +1,414 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/clash/interface.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:path/path.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/core.dart';
import 'core.dart';
class ClashService with ClashInterface {
static ClashService? _instance;
class ClashService {
Future<void> initGeo() async {
final homePath = await appPath.getHomeDirPath();
final homeDir = Directory(homePath);
final isExists = await homeDir.exists();
if (!isExists) {
await homeDir.create(recursive: true);
}
const geoFileNameList = [
mmdbFileName,
geoIpFileName,
geoSiteFileName,
asnFileName,
];
try {
for (final geoFileName in geoFileNameList) {
final geoFile = File(
join(homePath, geoFileName),
);
final isExists = await geoFile.exists();
if (isExists) {
continue;
}
final data = await rootBundle.load('assets/data/$geoFileName');
List<int> bytes = data.buffer.asUint8List();
await geoFile.writeAsBytes(bytes, flush: true);
}
} catch (e) {
debugPrint("$e");
exit(0);
Completer<ServerSocket> serverCompleter = Completer();
Completer<Socket> socketCompleter = Completer();
Map<String, Completer> callbackCompleterMap = {};
Process? process;
factory ClashService() {
_instance ??= ClashService._internal();
return _instance!;
}
ClashService._internal() {
_createServer();
startCore();
}
_createServer() async {
final address = !Platform.isWindows
? InternetAddress(
unixSocketPath,
type: InternetAddressType.unix,
)
: InternetAddress(
localhost,
type: InternetAddressType.IPv4,
);
await _deleteSocketFile();
final server = await ServerSocket.bind(
address,
0,
shared: true,
);
serverCompleter.complete(server);
await for (final socket in server) {
await _destroySocket();
socketCompleter.complete(socket);
socket
.transform(
StreamTransformer<Uint8List, String>.fromHandlers(
handleData: (Uint8List data, EventSink<String> sink) {
sink.add(utf8.decode(data, allowMalformed: true));
},
),
)
.transform(LineSplitter())
.listen(
(data) {
_handleAction(
Action.fromJson(
json.decode(data.trim()),
),
);
},
);
}
}
Future<bool> init({
required ClashConfig clashConfig,
required Config config,
startCore() async {
if (process != null) {
await shutdown();
}
final serverSocket = await serverCompleter.future;
final arg = Platform.isWindows
? "${serverSocket.port}"
: serverSocket.address.address;
bool isSuccess = false;
if (Platform.isWindows && await system.checkIsAdmin()) {
isSuccess = await request.startCoreByHelper(arg);
}
if (isSuccess) {
return;
}
process = await Process.start(
appPath.corePath,
[
arg,
],
);
process!.stdout.listen((_) {});
}
_deleteSocketFile() async {
if (!Platform.isWindows) {
final file = File(unixSocketPath);
if (await file.exists()) {
await file.delete();
}
}
}
_destroySocket() async {
if (socketCompleter.isCompleted) {
final lastSocket = await socketCompleter.future;
await lastSocket.close();
socketCompleter = Completer();
}
}
_handleAction(Action action) {
final completer = callbackCompleterMap[action.id];
switch (action.method) {
case ActionMethod.initClash:
case ActionMethod.shutdown:
case ActionMethod.getIsInit:
case ActionMethod.startListener:
case ActionMethod.resetTraffic:
case ActionMethod.closeConnections:
case ActionMethod.closeConnection:
case ActionMethod.stopListener:
completer?.complete(action.data as bool);
return;
case ActionMethod.changeProxy:
case ActionMethod.getProxies:
case ActionMethod.getTraffic:
case ActionMethod.getTotalTraffic:
case ActionMethod.asyncTestDelay:
case ActionMethod.getConnections:
case ActionMethod.getExternalProviders:
case ActionMethod.getExternalProvider:
case ActionMethod.validateConfig:
case ActionMethod.updateConfig:
case ActionMethod.updateGeoData:
case ActionMethod.updateExternalProvider:
case ActionMethod.sideLoadExternalProvider:
completer?.complete(action.data as String);
return;
case ActionMethod.message:
clashMessage.controller.add(action.data as String);
return;
case ActionMethod.forceGc:
case ActionMethod.startLog:
case ActionMethod.stopLog:
default:
return;
}
}
Future<T> _invoke<T>({
required ActionMethod method,
dynamic data,
Duration? timeout,
FutureOr<T> Function()? onTimeout,
}) async {
await initGeo();
final homeDirPath = await appPath.getHomeDirPath();
final isInit = clashCore.init(homeDirPath);
return isInit;
final id = "${method.name}#${other.id}";
final socket = await socketCompleter.future;
callbackCompleterMap[id] = Completer<T>();
socket.writeln(
json.encode(
Action(
id: id,
method: method,
data: data,
),
),
);
return (callbackCompleterMap[id] as Completer<T>).safeFuture(
timeout: timeout,
onLast: () {
callbackCompleterMap.remove(id);
},
onTimeout: onTimeout,
functionName: id,
);
}
_prueInvoke({
required ActionMethod method,
dynamic data,
}) async {
final id = "${method.name}#${other.id}";
final socket = await socketCompleter.future;
socket.writeln(
json.encode(
Action(
id: id,
method: method,
data: data,
),
),
);
}
@override
Future<bool> init(String homeDir) {
return _invoke<bool>(
method: ActionMethod.initClash,
data: homeDir,
);
}
@override
shutdown() async {
await _invoke<bool>(
method: ActionMethod.shutdown,
);
if (Platform.isWindows) {
await request.stopCoreByHelper();
}
await _destroySocket();
process?.kill();
process = null;
}
@override
Future<bool> get isInit {
return _invoke<bool>(
method: ActionMethod.getIsInit,
);
}
@override
forceGc() {
_prueInvoke(method: ActionMethod.forceGc);
}
@override
FutureOr<String> validateConfig(String data) {
return _invoke<String>(
method: ActionMethod.validateConfig,
data: data,
);
}
@override
Future<String> updateConfig(UpdateConfigParams updateConfigParams) async {
return await _invoke<String>(
method: ActionMethod.updateConfig,
data: json.encode(updateConfigParams),
timeout: const Duration(seconds: 20),
);
}
@override
Future<String> getProxies() {
return _invoke<String>(
method: ActionMethod.getProxies,
);
}
@override
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) {
return _invoke<String>(
method: ActionMethod.changeProxy,
data: json.encode(changeProxyParams),
);
}
@override
FutureOr<String> getExternalProviders() {
return _invoke<String>(
method: ActionMethod.getExternalProviders,
);
}
@override
FutureOr<String> getExternalProvider(String externalProviderName) {
return _invoke<String>(
method: ActionMethod.getExternalProvider,
data: externalProviderName,
);
}
@override
Future<String> updateGeoData({
required String geoType,
required String geoName,
}) {
return _invoke<String>(
method: ActionMethod.updateGeoData,
data: json.encode(
{
"geoType": geoType,
"geoName": geoName,
},
),
);
}
@override
Future<String> sideLoadExternalProvider({
required String providerName,
required String data,
}) {
return _invoke<String>(
method: ActionMethod.sideLoadExternalProvider,
data: json.encode({
"providerName": providerName,
"data": data,
}),
);
}
@override
Future<String> updateExternalProvider(String providerName) {
return _invoke<String>(
method: ActionMethod.updateExternalProvider,
data: providerName,
);
}
@override
FutureOr<String> getConnections() {
return _invoke<String>(
method: ActionMethod.getConnections,
);
}
@override
Future<bool> closeConnections() {
return _invoke<bool>(
method: ActionMethod.closeConnections,
);
}
@override
Future<bool> closeConnection(String id) {
return _invoke<bool>(
method: ActionMethod.closeConnection,
data: id,
);
}
@override
FutureOr<String> getTotalTraffic(bool value) {
return _invoke<String>(
method: ActionMethod.getTotalTraffic,
data: value,
);
}
@override
FutureOr<String> getTraffic(bool value) {
return _invoke<String>(
method: ActionMethod.getTraffic,
data: value,
);
}
@override
resetTraffic() {
_prueInvoke(method: ActionMethod.resetTraffic);
}
@override
startLog() {
_prueInvoke(method: ActionMethod.startLog);
}
@override
stopLog() {
_prueInvoke(method: ActionMethod.stopLog);
}
@override
Future<bool> startListener() {
return _invoke<bool>(
method: ActionMethod.startListener,
);
}
@override
stopListener() {
return _invoke<bool>(
method: ActionMethod.stopListener,
);
}
@override
Future<String> asyncTestDelay(String proxyName) {
final delayParams = {
"proxy-name": proxyName,
"timeout": httpTimeoutDuration.inMilliseconds,
};
return _invoke<String>(
method: ActionMethod.asyncTestDelay,
data: json.encode(delayParams),
timeout: Duration(
milliseconds: 6000,
),
onTimeout: () {
return json.encode(
Delay(
name: proxyName,
value: -1,
),
);
},
);
}
destroy() async {
final server = await serverCompleter.future;
await server.close();
await _deleteSocketFile();
}
}
final clashService = ClashService();
final clashService = system.isDesktop ? ClashService() : null;

View File

@@ -29,7 +29,6 @@ extension ColorSchemeExtension on ColorScheme {
ColorScheme toPrueBlack(bool isPrueBlack) => isPrueBlack
? copyWith(
surface: Colors.black,
background: Colors.black,
surfaceContainer: surfaceContainer.darken(0.05),
)
: this;

View File

@@ -1,33 +1,36 @@
export 'path.dart';
export 'request.dart';
export 'preferences.dart';
export 'constant.dart';
export 'proxy.dart';
export 'other.dart';
export 'num.dart';
export 'navigation.dart';
export 'window.dart';
export 'system.dart';
export 'picker.dart';
export 'android.dart';
export 'launch.dart';
export 'protocol.dart';
export 'datetime.dart';
export 'context.dart';
export 'link.dart';
export 'text.dart';
export 'color.dart';
export 'list.dart';
export 'string.dart';
export 'app_localizations.dart';
export 'color.dart';
export 'constant.dart';
export 'context.dart';
export 'datetime.dart';
export 'function.dart';
export 'package.dart';
export 'measure.dart';
export 'windows.dart';
export 'iterable.dart';
export 'scroll.dart';
export 'icons.dart';
export 'future.dart';
export 'http.dart';
export 'icons.dart';
export 'iterable.dart';
export 'keyboard.dart';
export 'launch.dart';
export 'link.dart';
export 'list.dart';
export 'lock.dart';
export 'measure.dart';
export 'navigation.dart';
export 'navigator.dart';
export 'network.dart';
export 'navigator.dart';
export 'num.dart';
export 'other.dart';
export 'package.dart';
export 'path.dart';
export 'picker.dart';
export 'preferences.dart';
export 'protocol.dart';
export 'proxy.dart';
export 'request.dart';
export 'scroll.dart';
export 'string.dart';
export 'system.dart';
export 'text.dart';
export 'tray.dart';
export 'window.dart';
export 'windows.dart';

View File

@@ -1,15 +1,20 @@
import 'dart:io';
import 'dart:math';
import 'dart:ui';
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 'system.dart';
const appName = "FlClash";
const appHelperService = "FlClashHelperService";
const coreName = "clash.meta";
const packageName = "com.follow.clash";
final unixSocketPath = "/tmp/FlClashSocket_${Random().nextInt(10000)}.sock";
const helperPort = 47890;
const helperTag = "2024125";
const httpTimeoutDuration = Duration(milliseconds: 5000);
const moreDuration = Duration(milliseconds: 100);
const animateDuration = Duration(milliseconds: 100);
@@ -21,7 +26,7 @@ const geoSiteFileName = "GeoSite.dat";
final double kHeaderHeight = system.isDesktop
? !Platform.isMacOS
? 40
: 26
: 28
: 0;
const GeoXMap defaultGeoXMap = {
"mmdb":

View File

@@ -24,8 +24,8 @@ class DAVClient {
},
);
client.setConnectTimeout(8000);
client.setSendTimeout(8000);
client.setReceiveTimeout(8000);
client.setSendTimeout(60000);
client.setReceiveTimeout(60000);
pingCompleter.complete(_ping());
}

42
lib/common/future.dart Normal file
View File

@@ -0,0 +1,42 @@
import 'dart:async';
import 'dart:ui';
extension CompleterExt<T> on Completer<T> {
safeFuture({
Duration? timeout,
VoidCallback? onLast,
FutureOr<T> Function()? onTimeout,
required String functionName,
}) {
final realTimeout = timeout ?? const Duration(seconds: 6);
Timer(realTimeout + Duration(milliseconds: 1000), () {
if (onLast != null) {
onLast();
}
});
return future.withTimeout(
timeout: realTimeout,
functionName: functionName,
onTimeout: onTimeout,
);
}
}
extension FutureExt<T> on Future<T> {
Future<T> withTimeout({
required Duration timeout,
required String functionName,
FutureOr<T> Function()? onTimeout,
}) {
return this.timeout(
timeout,
onTimeout: () async {
if (onTimeout != null) {
return onTimeout();
} else {
throw TimeoutException('$functionName timeout');
}
},
);
}
}

View File

@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/cupertino.dart';
import '../state.dart';
import 'constant.dart';
class FlClashHttpOverrides extends HttpOverrides {
@override
@@ -10,6 +11,9 @@ class FlClashHttpOverrides extends HttpOverrides {
final client = super.createHttpClient(context);
client.badCertificateCallback = (_, __, ___) => true;
client.findProxy = (url) {
if ([localhost].contains(url.host)) {
return "DIRECT";
}
debugPrint("find $url");
final appController = globalState.appController;
final port = appController.clashConfig.mixedPort;

View File

@@ -1,11 +1,11 @@
import 'dart:async';
import 'dart:io';
import 'package:fl_clash/models/models.dart' hide Process;
import 'package:fl_clash/models/models.dart';
import 'package:launch_at_startup/launch_at_startup.dart';
import 'constant.dart';
import 'system.dart';
import 'windows.dart';
class AutoLaunch {
static AutoLaunch? _instance;
@@ -26,60 +26,16 @@ class AutoLaunch {
return await launchAtStartup.isEnabled();
}
Future<bool> get windowsIsEnable async {
final res = await Process.run(
'schtasks',
['/Query', '/TN', appName, '/V', "/FO", "LIST"],
runInShell: true,
);
return res.stdout.toString().contains(Platform.resolvedExecutable);
}
Future<bool> enable() async {
if (Platform.isWindows) {
await windowsDisable();
}
return await launchAtStartup.enable();
}
windowsDisable() async {
final res = await Process.run(
'schtasks',
[
'/Delete',
'/TN',
appName,
'/F',
],
runInShell: true,
);
return res.exitCode == 0;
}
Future<bool> windowsEnable() async {
await disable();
return await windows?.registerTask(appName) ?? false;
}
Future<bool> disable() async {
return await launchAtStartup.disable();
}
updateStatus(AutoLaunchState state) async {
final isAdminAutoLaunch = state.isAdminAutoLaunch;
final isAutoLaunch = state.isAutoLaunch;
if (Platform.isWindows && isAdminAutoLaunch) {
if (await windowsIsEnable == isAutoLaunch) return;
if (isAutoLaunch) {
final isEnable = await windowsEnable();
if (!isEnable) {
enable();
}
} else {
windowsDisable();
}
return;
}
if (await isEnable == isAutoLaunch) return;
if (isAutoLaunch == true) {
enable();

View File

@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:app_links/app_links.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
typedef InstallConfigCallBack = void Function(String url);
@@ -17,7 +17,7 @@ class LinkManager {
initAppLinksListen(installConfigCallBack) async {
debugPrint("initAppLinksListen");
destroy();
subscription = _appLinks.allUriLinkStream.listen(
subscription = _appLinks.uriLinkStream.listen(
(uri) {
debugPrint('onAppLink: $uri');
if (uri.host == 'install-config') {
@@ -31,8 +31,7 @@ class LinkManager {
);
}
destroy(){
destroy() {
if (subscription != null) {
subscription?.cancel();
subscription = null;

30
lib/common/lock.dart Normal file
View File

@@ -0,0 +1,30 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
class SingleInstanceLock {
static SingleInstanceLock? _instance;
RandomAccessFile? _accessFile;
SingleInstanceLock._internal();
factory SingleInstanceLock() {
_instance ??= SingleInstanceLock._internal();
return _instance!;
}
Future<bool> acquire() async {
try {
final lockFilePath = await appPath.getLockFilePath();
final lockFile = File(lockFilePath);
await lockFile.create();
_accessFile = await lockFile.open(mode: FileMode.write);
await _accessFile?.lock();
return true;
} catch (_) {
return false;
}
}
}
final singleInstanceLock = SingleInstanceLock();

View File

@@ -7,9 +7,9 @@ import 'dart:ui';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as img;
import 'package:lpinyin/lpinyin.dart';
import 'package:zxing2/qrcode.dart';
import 'package:image/image.dart' as img;
class Other {
Color? getDelayColor(int? delay) {
@@ -19,6 +19,14 @@ class Other {
return const Color(0xFFC57F0A);
}
String get id {
final timestamp = DateTime.now().microsecondsSinceEpoch;
final random = Random();
final randomStr =
String.fromCharCodes(List.generate(8, (_) => random.nextInt(26) + 97));
return "$timestamp$randomStr";
}
String getDateStringLast2(int value) {
var valueRaw = "0$value";
return valueRaw.substring(
@@ -104,7 +112,7 @@ class Other {
String getTrayIconPath({
required Brightness brightness,
}) {
if(Platform.isMacOS){
if (Platform.isMacOS) {
return "assets/images/icon_white.png";
}
final suffix = Platform.isWindows ? "ico" : "png";

View File

@@ -13,34 +13,17 @@ class AppPath {
Completer<Directory> tempDir = Completer();
late String appDirPath;
// Future<Directory> _createDesktopCacheDir() async {
// final dir = Directory(path);
// if (await dir.exists()) {
// await dir.create(recursive: true);
// }
// return dir;
// }
AppPath._internal() {
appDirPath = join(dirname(Platform.resolvedExecutable));
getApplicationSupportDirectory().then((value) {
dataDir.complete(value);
});
getTemporaryDirectory().then((value){
tempDir.complete(value);
getTemporaryDirectory().then((value) {
tempDir.complete(value);
});
getDownloadsDirectory().then((value) {
downloadDir.complete(value);
});
// if (Platform.isAndroid) {
// getApplicationSupportDirectory().then((value) {
// cacheDir.complete(value);
// });
// } else {
// _createDesktopCacheDir().then((value) {
// cacheDir.complete(value);
// });
// }
}
factory AppPath() {
@@ -48,6 +31,23 @@ class AppPath {
return _instance!;
}
String get executableExtension {
return Platform.isWindows ? ".exe" : "";
}
String get executableDirPath {
final currentExecutablePath = Platform.resolvedExecutable;
return dirname(currentExecutablePath);
}
String get corePath {
return join(executableDirPath, "FlClashCore$executableExtension");
}
String get helperPath {
return join(executableDirPath, "$appHelperService$executableExtension");
}
Future<String> getDownloadDirPath() async {
final directory = await downloadDir.future;
return directory.path;
@@ -58,6 +58,11 @@ class AppPath {
return directory.path;
}
Future<String> getLockFilePath() async {
final directory = await dataDir.future;
return join(directory.path, "FlClash.lock");
}
Future<String> getProfilesPath() async {
final directory = await dataDir.future;
return join(directory.path, profilesDirectoryName);
@@ -69,6 +74,12 @@ class AppPath {
return join(directory, "$id.yaml");
}
Future<String?> getProvidersPath(String? id) async {
if (id == null) return null;
final directory = await getProfilesPath();
return join(directory, "providers", id);
}
Future<String> get tempPath async {
final directory = await tempDir.future;
return directory.path;

View File

@@ -1,3 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:dio/dio.dart';
@@ -79,25 +81,96 @@ class Request {
for (final source in _ipInfoSources.entries) {
try {
final response = await _dio
.get<Map<String, dynamic>>(
source.key,
cancelToken: cancelToken,
)
.timeout(
httpTimeoutDuration,
);
if (response.statusCode == 200 && response.data != null) {
return source.value(response.data!);
.get<Map<String, dynamic>>(source.key, cancelToken: cancelToken)
.timeout(httpTimeoutDuration);
if (response.statusCode != 200 || response.data == null) {
continue;
}
return source.value(response.data!);
} catch (e) {
if (cancelToken?.isCancelled == true) {
if (e is DioException && e.type == DioExceptionType.cancel) {
throw "cancelled";
}
continue;
debugPrint("checkIp error ===> $e");
}
}
return null;
}
Future<bool> pingHelper() async {
try {
final response = await _dio
.get(
"http://$localhost:$helperPort/ping",
options: Options(
responseType: ResponseType.plain,
),
)
.timeout(
const Duration(
milliseconds: 2000,
),
);
if (response.statusCode != HttpStatus.ok) {
return false;
}
return (response.data as String) == helperTag;
} catch (_) {
return false;
}
}
Future<bool> startCoreByHelper(String arg) async {
try {
final response = await _dio
.post(
"http://$localhost:$helperPort/start",
data: json.encode({
"path": appPath.corePath,
"arg": arg,
}),
options: Options(
responseType: ResponseType.plain,
),
)
.timeout(
const Duration(
milliseconds: 2000,
),
);
if (response.statusCode != HttpStatus.ok) {
return false;
}
final data = response.data as String;
return data.isEmpty;
} catch (_) {
return false;
}
}
Future<bool> stopCoreByHelper() async {
try {
final response = await _dio
.post(
"http://$localhost:$helperPort/stop",
options: Options(
responseType: ResponseType.plain,
),
)
.timeout(
const Duration(
milliseconds: 2000,
),
);
if (response.statusCode != HttpStatus.ok) {
return false;
}
final data = response.data as String;
return data.isEmpty;
} catch (_) {
return false;
}
}
}
final request = Request();

View File

@@ -1,11 +1,13 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/input.dart';
import 'package:flutter/services.dart';
import 'window.dart';
class System {
static System? _instance;
@@ -19,12 +21,6 @@ class System {
bool get isDesktop =>
Platform.isWindows || Platform.isMacOS || Platform.isLinux;
get isAdmin async {
if (!Platform.isWindows) return false;
final result = await Process.run('net', ['session'], runInShell: true);
return result.exitCode == 0;
}
Future<int> get version async {
final deviceInfo = await DeviceInfoPlugin().deviceInfo;
return switch (Platform.operatingSystem) {
@@ -35,6 +31,73 @@ class System {
};
}
Future<bool> checkIsAdmin() async {
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
if (Platform.isWindows) {
final result = await windows?.checkService();
return result == WindowsHelperServiceStatus.running;
} else if (Platform.isMacOS) {
final result = await Process.run('stat', ['-f', '%Su:%Sg %Sp', corePath]);
final output = result.stdout.trim();
if (output.startsWith('root:admin') && output.contains('rws')) {
return true;
}
return false;
} else if (Platform.isLinux) {
final result = await Process.run('stat', ['-c', '%U:%G %A', corePath]);
final output = result.stdout.trim();
if (output.startsWith('root:') && output.contains('rwx')) {
return true;
}
return false;
}
return true;
}
Future<AuthorizeCode> authorizeCore() async {
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
final isAdmin = await checkIsAdmin();
if (isAdmin) {
return AuthorizeCode.none;
}
if (Platform.isWindows) {
final result = await windows?.registerService();
if (result == true) {
return AuthorizeCode.success;
}
return AuthorizeCode.error;
} else if (Platform.isMacOS) {
final shell = 'chown root:admin $corePath; chmod +sx $corePath';
final arguments = [
"-e",
'do shell script "$shell" with administrator privileges',
];
final result = await Process.run("osascript", arguments);
if (result.exitCode != 0) {
return AuthorizeCode.error;
}
return AuthorizeCode.success;
} else if (Platform.isLinux) {
final shell = Platform.environment['SHELL'] ?? 'bash';
final password = await globalState.showCommonDialog<String>(
child: InputDialog(
title: appLocalizations.pleaseInputAdminPassword,
value: '',
),
);
final arguments = [
"-c",
'echo "$password" | sudo -S chown root:root "$corePath" && echo "$password" | sudo -S chmod +sx "$corePath"'
];
final result = await Process.run(shell, arguments);
if (result.exitCode != 0) {
return AuthorizeCode.error;
}
return AuthorizeCode.success;
}
return AuthorizeCode.error;
}
back() async {
await app?.moveTaskToBack();
await window?.hide();

193
lib/common/tray.dart Normal file
View File

@@ -0,0 +1,193 @@
import 'dart:io';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:tray_manager/tray_manager.dart';
import 'app_localizations.dart';
import 'constant.dart';
import 'other.dart';
import 'window.dart';
class Tray {
Future _updateSystemTray({
required Brightness? brightness,
bool force = false,
}) async {
if (Platform.isAndroid) {
return;
}
if (Platform.isLinux || force) {
await trayManager.destroy();
}
await trayManager.setIcon(
other.getTrayIconPath(
brightness: brightness ??
WidgetsBinding.instance.platformDispatcher.platformBrightness,
),
isTemplate: true,
);
if (!Platform.isLinux) {
await trayManager.setToolTip(
appName,
);
}
}
update({
required AppState appState,
required AppFlowingState appFlowingState,
required Config config,
required ClashConfig clashConfig,
bool focus = false,
}) async {
if (Platform.isAndroid) {
return;
}
if (!Platform.isLinux) {
await _updateSystemTray(
brightness: appState.brightness,
force: focus,
);
}
List<MenuItem> menuItems = [];
final showMenuItem = MenuItem(
label: appLocalizations.show,
onClick: (_) {
window?.show();
},
);
menuItems.add(showMenuItem);
final startMenuItem = MenuItem.checkbox(
label: appFlowingState.isStart
? appLocalizations.stop
: appLocalizations.start,
onClick: (_) async {
globalState.appController.updateStart();
},
checked: false,
);
menuItems.add(startMenuItem);
menuItems.add(MenuItem.separator());
for (final mode in Mode.values) {
menuItems.add(
MenuItem.checkbox(
label: Intl.message(mode.name),
onClick: (_) {
globalState.appController.clashConfig.mode = mode;
},
checked: mode == clashConfig.mode,
),
);
}
menuItems.add(MenuItem.separator());
if (!Platform.isWindows) {
final groups = appState.currentGroups;
for (final group in groups) {
List<MenuItem> subMenuItems = [];
for (final proxy in group.all) {
subMenuItems.add(
MenuItem.checkbox(
label: proxy.name,
checked: appState.selectedMap[group.name] == proxy.name,
onClick: (_) {
final appController = globalState.appController;
appController.config.updateCurrentSelectedMap(
group.name,
proxy.name,
);
appController.changeProxy(
groupName: group.name,
proxyName: proxy.name,
);
},
),
);
}
menuItems.add(
MenuItem.submenu(
label: group.name,
submenu: Menu(
items: subMenuItems,
),
),
);
}
if (groups.isNotEmpty) {
menuItems.add(MenuItem.separator());
}
}
if (appFlowingState.isStart) {
menuItems.add(
MenuItem.checkbox(
label: appLocalizations.tun,
onClick: (_) {
globalState.appController.updateTun();
},
checked: clashConfig.tun.enable,
),
);
menuItems.add(
MenuItem.checkbox(
label: appLocalizations.systemProxy,
onClick: (_) {
globalState.appController.updateSystemProxy();
},
checked: config.networkProps.systemProxy,
),
);
menuItems.add(MenuItem.separator());
}
final autoStartMenuItem = MenuItem.checkbox(
label: appLocalizations.autoLaunch,
onClick: (_) async {
globalState.appController.updateAutoLaunch();
},
checked: config.appSetting.autoLaunch,
);
final copyEnvVarMenuItem = MenuItem(
label: appLocalizations.copyEnvVar,
onClick: (_) async {
await _copyEnv(clashConfig.mixedPort);
},
);
menuItems.add(autoStartMenuItem);
menuItems.add(copyEnvVarMenuItem);
menuItems.add(MenuItem.separator());
final exitMenuItem = MenuItem(
label: appLocalizations.exit,
onClick: (_) async {
await globalState.appController.handleExit();
},
);
menuItems.add(exitMenuItem);
final menu = Menu(items: menuItems);
await trayManager.setContextMenu(menu);
if (Platform.isLinux) {
await _updateSystemTray(
brightness: appState.brightness,
force: focus,
);
}
}
Future<void> _copyEnv(int port) async {
final url = "http://127.0.0.1:$port";
final cmdline = Platform.isWindows
? "set \$env:all_proxy=$url"
: "export all_proxy=$url";
await Clipboard.setData(
ClipboardData(
text: cmdline,
),
);
}
}
final tray = Tray();

View File

@@ -1,19 +1,17 @@
import 'dart:io';
import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/config.dart';
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
import 'package:windows_single_instance/windows_single_instance.dart';
import 'protocol.dart';
import 'system.dart';
class Window {
init(WindowProps props, int version) async {
final acquire = await singleInstanceLock.acquire();
if (!acquire) {
exit(0);
}
if (Platform.isWindows) {
await WindowsSingleInstance.ensureSingleInstance([], "FlClash");
protocol.register("clash");
protocol.register("clashmeta");
protocol.register("flclash");

View File

@@ -1,7 +1,10 @@
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/cupertino.dart';
import 'package:path/path.dart';
class Windows {
@@ -51,12 +54,84 @@ class Windows {
calloc.free(argumentsPtr);
calloc.free(operationPtr);
if (result <= 32) {
debugPrint("[Windows] runas: $command $arguments resultCode:$result");
if (result < 42) {
return false;
}
return true;
}
_killProcess(int port) async {
final result = await Process.run('netstat', ['-ano']);
final lines = result.stdout.toString().trim().split('\n');
for (final line in lines) {
if (!line.contains(":$port") || !line.contains("LISTENING")) {
continue;
}
final parts = line.trim().split(RegExp(r'\s+'));
final pid = int.tryParse(parts.last);
if (pid != null) {
await Process.run('taskkill', ['/PID', pid.toString(), '/F']);
}
}
}
Future<WindowsHelperServiceStatus> checkService() async {
// final qcResult = await Process.run('sc', ['qc', appHelperService]);
// final qcOutput = qcResult.stdout.toString();
// if (qcResult.exitCode != 0 || !qcOutput.contains(appPath.helperPath)) {
// return WindowsHelperServiceStatus.none;
// }
final result = await Process.run('sc', ['query', appHelperService]);
if(result.exitCode != 0){
return WindowsHelperServiceStatus.none;
}
final output = result.stdout.toString();
if (output.contains("RUNNING") && await request.pingHelper()) {
return WindowsHelperServiceStatus.running;
}
return WindowsHelperServiceStatus.presence;
}
Future<bool> registerService() async {
final status = await checkService();
if (status == WindowsHelperServiceStatus.running) {
return true;
}
await _killProcess(helperPort);
final command = [
"/c",
if (status == WindowsHelperServiceStatus.presence) ...[
"sc",
"delete",
appHelperService,
"/force",
"&&",
],
"sc",
"create",
appHelperService,
'binPath= "${appPath.helperPath}"',
'start= auto',
"&&",
"sc",
"start",
appHelperService,
].join(" ");
final res = runas("cmd.exe", command);
await Future.delayed(
Duration(milliseconds: 300),
);
return res;
}
Future<bool> registerTask(String appName) async {
final taskXml = '''
<?xml version="1.0" encoding="UTF-16"?>

View File

@@ -5,19 +5,17 @@ import 'dart:isolate';
import 'dart:typed_data';
import 'package:archive/archive.dart';
import 'package:fl_clash/clash/clash.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:intl/intl.dart';
import 'package:path/path.dart';
import 'package:provider/provider.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:url_launcher/url_launcher.dart';
import 'clash/core.dart';
import 'models/models.dart';
import 'common/common.dart';
import 'models/models.dart';
class AppController {
final BuildContext context;
@@ -30,6 +28,7 @@ class AppController {
late Function addCheckIpNumDebounce;
late Function applyProfileDebounce;
late Function savePreferencesDebounce;
late Function changeProxyDebounce;
AppController(this.context) {
appState = context.read<AppState>();
@@ -45,6 +44,13 @@ class AppController {
applyProfileDebounce = debounce<Function()>(() async {
await applyProfile(isPrue: true);
});
changeProxyDebounce = debounce((String groupName, String proxyName) async {
await changeProxy(
groupName: groupName,
proxyName: proxyName,
);
await updateGroups();
});
addCheckIpNumDebounce = debounce(() {
appState.checkIpNum++;
});
@@ -53,6 +59,14 @@ class AppController {
});
}
restartCore() async {
await globalState.restartCore(
appState: appState,
clashConfig: clashConfig,
config: config,
);
}
updateStatus(bool isStart) async {
if (isStart) {
await globalState.handleStart();
@@ -62,23 +76,31 @@ class AppController {
updateRunTime,
updateTraffic,
];
if (!Platform.isAndroid) {
applyProfileDebounce();
final currentLastModified =
await config.getCurrentProfile()?.profileLastModified;
if (currentLastModified == null ||
globalState.lastProfileModified == null) {
addCheckIpNumDebounce();
return;
}
if (currentLastModified <= (globalState.lastProfileModified ?? 0)) {
addCheckIpNumDebounce();
return;
}
applyProfileDebounce();
} else {
await globalState.handleStop();
clashCore.resetTraffic();
await clashCore.resetTraffic();
appFlowingState.traffics = [];
appFlowingState.totalTraffic = Traffic();
appFlowingState.runTime = null;
await Future.delayed(
Duration(milliseconds: 300),
);
addCheckIpNumDebounce();
}
}
updateCoreVersionInfo() {
globalState.updateCoreVersionInfo(appState);
}
updateRunTime() {
final startTime = globalState.startTime;
if (startTime != null) {
@@ -92,6 +114,7 @@ class AppController {
updateTraffic() {
globalState.updateTraffic(
config: config,
appFlowingState: appFlowingState,
);
}
@@ -104,7 +127,7 @@ class AppController {
deleteProfile(String id) async {
config.deleteProfileById(id);
clashCore.clearEffect(id);
clearEffect(id);
if (config.currentProfileId == id) {
if (config.profiles.isNotEmpty) {
final updateId = config.profiles.first.id;
@@ -116,6 +139,10 @@ class AppController {
}
}
updateProviders() {
globalState.updateProviders(appState);
}
Future<void> updateProfile(Profile profile) async {
final newProfile = await profile.update();
config.setProfile(
@@ -128,6 +155,7 @@ class AppController {
if (commonScaffoldState?.mounted != true) return;
await commonScaffoldState?.loadingRun(() async {
await globalState.updateClashConfig(
appState: appState,
clashConfig: clashConfig,
config: config,
isPatch: isPatch,
@@ -211,8 +239,8 @@ class AppController {
changeProxy({
required String groupName,
required String proxyName,
}) {
globalState.changeProxy(
}) async {
await globalState.changeProxy(
config: config,
groupName: groupName,
proxyName: proxyName,
@@ -232,22 +260,16 @@ class AppController {
}
handleExit() async {
await updateStatus(false);
await proxy?.stopProxy();
await savePreferences();
clashCore.shutdown();
try {
await updateStatus(false);
await clashCore.shutdown();
await clashService?.destroy();
await proxy?.stopProxy();
await savePreferences();
} catch (_) {}
system.exit();
}
updateLogStatus() {
if (config.appSetting.openLogs) {
clashCore.startLog();
} else {
clashCore.stopLog();
appFlowingState.logs = [];
}
}
autoCheckUpdate() async {
if (!config.appSetting.autoCheckUpdate) return;
final res = await request.checkForUpdate();
@@ -302,10 +324,20 @@ class AppController {
if (!isDisclaimerAccepted) {
handleExit();
}
updateLogStatus();
if (!config.appSetting.silentLaunch) {
window?.show();
}
await globalState.initCore(
appState: appState,
clashConfig: clashConfig,
config: config,
);
await _initStatus();
autoUpdateProfiles();
autoCheckUpdate();
}
_initStatus() async {
if (Platform.isAndroid) {
globalState.updateStartTime();
}
@@ -314,8 +346,6 @@ class AppController {
} else {
await updateStatus(config.appSetting.autoRun);
}
autoUpdateProfiles();
autoCheckUpdate();
}
setDelay(Delay delay) {
@@ -523,6 +553,19 @@ class AppController {
'';
}
clearEffect(String profileId) async {
final profilePath = await appPath.getProfilePath(profileId);
final providersPath = await appPath.getProvidersPath(profileId);
return await Isolate.run(() async {
if (profilePath != null) {
await File(profilePath).delete(recursive: true);
}
if (providersPath != null) {
await File(providersPath).delete(recursive: true);
}
});
}
updateTun() {
clashConfig.tun = clashConfig.tun.copyWith(
enable: !clashConfig.tun.enable,
@@ -530,8 +573,8 @@ class AppController {
}
updateSystemProxy() {
config.desktopProps = config.desktopProps.copyWith(
systemProxy: !config.desktopProps.systemProxy,
config.networkProps = config.networkProps.copyWith(
systemProxy: !config.networkProps.systemProxy,
);
}
@@ -545,12 +588,6 @@ class AppController {
);
}
updateAdminAutoLaunch() {
config.appSetting = config.appSetting.copyWith(
adminAutoLaunch: !config.appSetting.adminAutoLaunch,
);
}
updateVisible() async {
final visible = await window?.isVisible();
if (visible != null && !visible) {
@@ -599,121 +636,14 @@ class AppController {
});
}
Future _updateSystemTray({
required Brightness? brightness,
bool force = false,
}) async {
if (Platform.isLinux || force) {
await trayManager.destroy();
}
await trayManager.setIcon(
other.getTrayIconPath(
brightness: brightness ??
WidgetsBinding.instance.platformDispatcher.platformBrightness,
),
isTemplate: true,
);
if (!Platform.isLinux) {
await trayManager.setToolTip(
appName,
);
}
}
updateTray([bool focus = false]) async {
if (!Platform.isLinux) {
await _updateSystemTray(
brightness: appState.brightness,
force: focus,
);
}
List<MenuItem> menuItems = [];
final showMenuItem = MenuItem(
label: appLocalizations.show,
onClick: (_) {
window?.show();
},
tray.update(
appState: appState,
appFlowingState: appFlowingState,
config: config,
clashConfig: clashConfig,
focus: focus,
);
menuItems.add(showMenuItem);
final startMenuItem = MenuItem.checkbox(
label: appFlowingState.isStart
? appLocalizations.stop
: appLocalizations.start,
onClick: (_) async {
globalState.appController.updateStart();
},
checked: false,
);
menuItems.add(startMenuItem);
menuItems.add(MenuItem.separator());
for (final mode in Mode.values) {
menuItems.add(
MenuItem.checkbox(
label: Intl.message(mode.name),
onClick: (_) {
globalState.appController.clashConfig.mode = mode;
},
checked: mode == clashConfig.mode,
),
);
}
menuItems.add(MenuItem.separator());
if (appFlowingState.isStart) {
menuItems.add(
MenuItem.checkbox(
label: appLocalizations.tun,
onClick: (_) {
globalState.appController.updateTun();
},
checked: clashConfig.tun.enable,
),
);
menuItems.add(
MenuItem.checkbox(
label: appLocalizations.systemProxy,
onClick: (_) {
globalState.appController.updateSystemProxy();
},
checked: config.desktopProps.systemProxy,
),
);
menuItems.add(MenuItem.separator());
}
final autoStartMenuItem = MenuItem.checkbox(
label: appLocalizations.autoLaunch,
onClick: (_) async {
globalState.appController.updateAutoLaunch();
},
checked: config.appSetting.autoLaunch,
);
menuItems.add(autoStartMenuItem);
if(Platform.isWindows){
final adminAutoStartMenuItem = MenuItem.checkbox(
label: appLocalizations.adminAutoLaunch,
onClick: (_) async {
globalState.appController.updateAdminAutoLaunch();
},
checked: config.appSetting.adminAutoLaunch,
);
menuItems.add(adminAutoStartMenuItem);
}
menuItems.add(MenuItem.separator());
final exitMenuItem = MenuItem(
label: appLocalizations.exit,
onClick: (_) async {
await globalState.appController.handleExit();
},
);
menuItems.add(exitMenuItem);
final menu = Menu(items: menuItems);
await trayManager.setContextMenu(menu);
if (Platform.isLinux) {
await _updateSystemTray(
brightness: appState.brightness,
force: focus,
);
}
}
recoveryData(

View File

@@ -173,3 +173,44 @@ enum FontFamily {
const FontFamily([this.value]);
}
enum RouteMode {
bypassPrivate,
config,
}
enum ActionMethod {
message,
initClash,
getIsInit,
forceGc,
shutdown,
validateConfig,
updateConfig,
getProxies,
changeProxy,
getTraffic,
getTotalTraffic,
resetTraffic,
asyncTestDelay,
getConnections,
closeConnections,
closeConnection,
getExternalProviders,
getExternalProvider,
updateGeoData,
updateExternalProvider,
sideLoadExternalProvider,
startLog,
stopLog,
startListener,
stopListener,
}
enum AuthorizeCode { none, success, error }
enum WindowsHelperServiceStatus {
none,
presence,
running,
}

View File

@@ -60,32 +60,6 @@ class UsageSwitch extends StatelessWidget {
}
}
class AdminAutoLaunchItem extends StatelessWidget {
const AdminAutoLaunchItem({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.appSetting.adminAutoLaunch,
builder: (_, adminAutoLaunch, __) {
return ListItem.switchItem(
title: Text(appLocalizations.adminAutoLaunch),
subtitle: Text(appLocalizations.adminAutoLaunchDesc),
delegate: SwitchDelegate(
value: adminAutoLaunch,
onChanged: (bool value) async {
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
adminAutoLaunch: value,
);
},
),
);
},
);
}
}
class ApplicationSettingFragment extends StatelessWidget {
const ApplicationSettingFragment({super.key});
@@ -134,8 +108,6 @@ class ApplicationSettingFragment extends StatelessWidget {
);
},
),
if(Platform.isWindows)
const AdminAutoLaunchItem(),
if (system.isDesktop)
Selector<Config, bool>(
selector: (_, config) => config.appSetting.silentLaunch,

View File

@@ -24,6 +24,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
title: appLocalizations.network,
isScaffold: true,
isBlur: false,
extendPageWidth: 360,
widget: const NetworkListView(),
),
),

View File

@@ -6,10 +6,11 @@ import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class VPNSwitch extends StatelessWidget {
const VPNSwitch({super.key});
class VPNItem extends StatelessWidget {
const VPNItem({super.key});
@override
Widget build(BuildContext context) {
@@ -39,8 +40,8 @@ class TUNItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.vpnProps.enable,
return Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.tun.enable,
builder: (_, enable, __) {
return ListItem.switchItem(
title: Text(appLocalizations.tun),
@@ -60,8 +61,8 @@ class TUNItem extends StatelessWidget {
}
}
class AllowBypassSwitch extends StatelessWidget {
const AllowBypassSwitch({super.key});
class AllowBypassItem extends StatelessWidget {
const AllowBypassItem({super.key});
@override
Widget build(BuildContext context) {
@@ -87,8 +88,8 @@ class AllowBypassSwitch extends StatelessWidget {
}
}
class SystemProxySwitch extends StatelessWidget {
const SystemProxySwitch({super.key});
class VpnSystemProxyItem extends StatelessWidget {
const VpnSystemProxyItem({super.key});
@override
Widget build(BuildContext context) {
@@ -114,8 +115,35 @@ class SystemProxySwitch extends StatelessWidget {
}
}
class Ipv6Switch extends StatelessWidget {
const Ipv6Switch({super.key});
class SystemProxyItem extends StatelessWidget {
const SystemProxyItem({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.networkProps.systemProxy,
builder: (_, systemProxy, __) {
return ListItem.switchItem(
title: Text(appLocalizations.systemProxy),
subtitle: Text(appLocalizations.systemProxyDesc),
delegate: SwitchDelegate(
value: systemProxy,
onChanged: (bool value) async {
final config = globalState.appController.config;
final networkProps = config.networkProps;
config.networkProps = networkProps.copyWith(
systemProxy: value,
);
},
),
);
},
);
}
}
class Ipv6Item extends StatelessWidget {
const Ipv6Item({super.key});
@override
Widget build(BuildContext context) {
@@ -176,6 +204,36 @@ class TunStackItem extends StatelessWidget {
class BypassDomainItem extends StatelessWidget {
const BypassDomainItem({super.key});
_initActions(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [
IconButton(
onPressed: () {
globalState.showMessage(
title: appLocalizations.reset,
message: TextSpan(
text: appLocalizations.resetTip,
),
onTab: () {
final config = globalState.appController.config;
config.networkProps = config.networkProps.copyWith(
bypassDomain: defaultBypassDomain,
);
Navigator.of(context).pop();
},
);
},
tooltip: appLocalizations.reset,
icon: const Icon(
Icons.replay,
),
)
];
});
}
@override
Widget build(BuildContext context) {
return ListItem.open(
@@ -183,19 +241,20 @@ class BypassDomainItem extends StatelessWidget {
subtitle: Text(appLocalizations.bypassDomainDesc),
delegate: OpenDelegate(
isBlur: false,
isScaffold: true,
title: appLocalizations.bypassDomain,
widget: Selector<Config, List<String>>(
selector: (_, config) => config.vpnProps.bypassDomain,
shouldRebuild: (prev, next) =>
!stringListEquality.equals(prev, next),
builder: (_, bypassDomain, __) {
selector: (_, config) => config.networkProps.bypassDomain,
shouldRebuild: (prev, next) => !stringListEquality.equals(prev, next),
builder: (context, bypassDomain, __) {
_initActions(context);
return ListPage(
title: appLocalizations.bypassDomain,
items: bypassDomain,
titleBuilder: (item) => Text(item),
onChange: (items){
onChange: (items) {
final config = globalState.appController.config;
config.vpnProps = config.vpnProps.copyWith(
config.networkProps = config.networkProps.copyWith(
bypassDomain: List.from(items),
);
},
@@ -208,22 +267,108 @@ class BypassDomainItem extends StatelessWidget {
}
}
class RouteModeItem extends StatelessWidget {
const RouteModeItem({super.key});
@override
Widget build(BuildContext context) {
return Selector<ClashConfig, RouteMode>(
selector: (_, clashConfig) => clashConfig.routeMode,
builder: (_, value, __) {
return ListItem<RouteMode>.options(
title: Text(appLocalizations.routeMode),
subtitle: Text(Intl.message("routeMode_${value.name}")),
delegate: OptionsDelegate<RouteMode>(
title: appLocalizations.routeMode,
options: RouteMode.values,
onChanged: (RouteMode? value) {
if (value == null) {
return;
}
final appController = globalState.appController;
appController.clashConfig.routeMode = value;
},
textBuilder: (routeMode) => Intl.message(
"routeMode_${routeMode.name}",
),
value: value,
),
);
},
);
}
}
class RouteAddressItem extends StatelessWidget {
const RouteAddressItem({super.key});
@override
Widget build(BuildContext context) {
return Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.routeMode == RouteMode.config,
builder: (_, value, child) {
if (value) {
return child!;
}
return Container();
},
child: ListItem.open(
title: Text(appLocalizations.routeAddress),
subtitle: Text(appLocalizations.routeAddressDesc),
delegate: OpenDelegate(
isBlur: false,
isScaffold: true,
title: appLocalizations.routeAddress,
widget: Selector<ClashConfig, List<String>>(
selector: (_, clashConfig) => clashConfig.includeRouteAddress,
shouldRebuild: (prev, next) =>
!stringListEquality.equals(prev, next),
builder: (context, routeAddress, __) {
return ListPage(
title: appLocalizations.routeAddress,
items: routeAddress,
titleBuilder: (item) => Text(item),
onChange: (items) {
final clashConfig = globalState.appController.clashConfig;
clashConfig.includeRouteAddress =
Set<String>.from(items).toList();
},
);
},
),
extendPageWidth: 360,
),
),
);
}
}
final networkItems = [
Platform.isAndroid ? const VPNSwitch() : const TUNItem(),
if (Platform.isAndroid) const VPNItem(),
if (Platform.isAndroid)
...generateSection(
title: "VPN",
items: [
const SystemProxySwitch(),
const AllowBypassSwitch(),
const Ipv6Switch(),
const BypassDomainItem(),
const SystemProxyItem(),
const AllowBypassItem(),
const Ipv6Item(),
],
),
if (system.isDesktop)
...generateSection(
title: appLocalizations.system,
items: [
SystemProxyItem(),
BypassDomainItem(),
],
),
...generateSection(
title: appLocalizations.options,
items: [
if (system.isDesktop) const TUNItem(),
const TunStackItem(),
const RouteModeItem(),
const RouteAddressItem(),
],
),
];

View File

@@ -27,18 +27,19 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
connectionsNotifier.value = connectionsNotifier.value
.copyWith(connections: clashCore.getConnections());
WidgetsBinding.instance.addPostFrameCallback((_) async {
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: await clashCore.getConnections(),
);
if (timer != null) {
timer?.cancel();
timer = null;
}
timer = Timer.periodic(
const Duration(seconds: 1),
(timer) {
(timer) async {
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: clashCore.getConnections(),
connections: await clashCore.getConnections(),
);
},
);
@@ -66,10 +67,10 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
width: 8,
),
IconButton(
onPressed: () {
onPressed: () async {
clashCore.closeConnections();
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: clashCore.getConnections(),
connections: await clashCore.getConnections(),
);
},
icon: const Icon(Icons.delete_sweep_outlined),
@@ -99,10 +100,11 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
);
}
_handleBlockConnection(String id) {
_handleBlockConnection(String id) async {
clashCore.closeConnection(id);
connectionsNotifier.value = connectionsNotifier.value
.copyWith(connections: clashCore.getConnections());
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: await clashCore.getConnections(),
);
}
@override
@@ -239,10 +241,10 @@ class ConnectionsSearchDelegate extends SearchDelegate {
);
}
_handleBlockConnection(String id) {
_handleBlockConnection(String id) async {
clashCore.closeConnection(id);
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: clashCore.getConnections(),
connections: await clashCore.getConnections(),
);
}

View File

@@ -2,15 +2,16 @@ import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/fragments/dashboard/intranet_ip.dart';
import 'package:fl_clash/fragments/dashboard/status_switch.dart';
import 'package:fl_clash/fragments/dashboard/status_button.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/material.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'network_detection.dart';
import 'network_speed.dart';
import 'outbound_mode.dart';
import 'start_button.dart';
import 'network_speed.dart';
import 'traffic_usage.dart';
class DashboardFragment extends StatefulWidget {
@@ -22,7 +23,7 @@ class DashboardFragment extends StatefulWidget {
class _DashboardFragmentState extends State<DashboardFragment> {
_initFab(bool isCurrent) {
if(!isCurrent){
if (!isCurrent) {
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -68,11 +69,11 @@ class _DashboardFragmentState extends State<DashboardFragment> {
if (system.isDesktop) ...[
GridItem(
crossAxisCellCount: switchCount,
child: const TUNSwitch(),
child: const TUNButton(),
),
GridItem(
crossAxisCellCount: switchCount,
child: const ProxySwitch(),
child: const SystemProxyButton(),
),
],
const GridItem(

View File

@@ -1,5 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
@@ -14,14 +16,14 @@ class IntranetIP extends StatefulWidget {
class _IntranetIPState extends State<IntranetIP> {
final ipNotifier = ValueNotifier<String?>("");
late StreamSubscription subscription;
Future<String> getNetworkType() async {
try {
List<NetworkInterface> interfaces = await NetworkInterface.list(
final interfaces = await NetworkInterface.list(
includeLoopback: false,
type: InternetAddressType.any,
);
for (var interface in interfaces) {
if (interface.name.toLowerCase().contains('wlan') ||
interface.name.toLowerCase().contains('wi-fi')) {
@@ -33,7 +35,6 @@ class _IntranetIPState extends State<IntranetIP> {
return 'Mobile Data';
}
}
return 'Unknown';
} catch (e) {
return 'Error';
@@ -41,6 +42,7 @@ class _IntranetIPState extends State<IntranetIP> {
}
Future<String?> getLocalIpAddress() async {
await Future.delayed(animateDuration);
List<NetworkInterface> interfaces = await NetworkInterface.list(
includeLoopback: false,
)
@@ -66,15 +68,14 @@ class _IntranetIPState extends State<IntranetIP> {
return null;
}
@override
void dispose() {
super.dispose();
ipNotifier.dispose();
}
@override
void initState() {
super.initState();
subscription = Connectivity().onConnectivityChanged.listen((_) async {
ipNotifier.value = null;
debugPrint("[App] Connection change");
ipNotifier.value = await getLocalIpAddress() ?? "";
});
WidgetsBinding.instance.addPostFrameCallback((_) async {
ipNotifier.value = await getLocalIpAddress() ?? "";
});
@@ -104,7 +105,9 @@ class _IntranetIPState extends State<IntranetIP> {
flex: 1,
child: TooltipText(
text: Text(
value.isNotEmpty ? value : appLocalizations.noNetwork,
value.isNotEmpty
? value
: appLocalizations.noNetwork,
style: context
.textTheme.titleLarge?.toSoftBold.toMinus,
maxLines: 1,
@@ -127,4 +130,11 @@ class _IntranetIPState extends State<IntranetIP> {
),
);
}
@override
void dispose() {
super.dispose();
subscription.cancel();
ipNotifier.dispose();
}
}

View File

@@ -9,6 +9,13 @@ import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
final networkDetectionState = ValueNotifier<NetworkDetectionState>(
const NetworkDetectionState(
isTesting: true,
ipInfo: null,
),
);
class NetworkDetection extends StatefulWidget {
const NetworkDetection({super.key});
@@ -17,12 +24,6 @@ class NetworkDetection extends StatefulWidget {
}
class _NetworkDetectionState extends State<NetworkDetection> {
final networkDetectionState = ValueNotifier<NetworkDetectionState>(
const NetworkDetectionState(
isTesting: true,
ipInfo: null,
),
);
bool? _preIsStart;
Function? _checkIpDebounce;
Timer? _setTimeoutTimer;
@@ -55,17 +56,20 @@ class _NetworkDetectionState extends State<NetworkDetection> {
);
return;
}
_setTimeoutTimer = Timer(const Duration(milliseconds: 2000), () {
_clearSetTimeoutTimer();
_setTimeoutTimer = Timer(const Duration(milliseconds: 300), () {
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: false,
ipInfo: null,
);
});
} catch (_) {
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: true,
ipInfo: null,
);
} catch (e) {
if (e.toString() == "cancelled") {
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: true,
ipInfo: null,
);
}
}
}
@@ -92,9 +96,8 @@ class _NetworkDetectionState extends State<NetworkDetection> {
}
@override
void dispose() {
dispose() {
super.dispose();
networkDetectionState.dispose();
}
String countryCodeToEmoji(String countryCode) {
@@ -156,7 +159,8 @@ class _NetworkDetectionState extends State<NetworkDetection> {
.textTheme
.titleLarge
?.copyWith(
fontFamily: FontFamily.twEmoji.value,
fontFamily:
FontFamily.twEmoji.value,
),
),
)

View File

@@ -1,45 +1,33 @@
import 'package:fl_clash/common/app_localizations.dart';
import 'package:fl_clash/common/system.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// class VPNSwitch extends StatelessWidget {
// const VPNSwitch({super.key});
//
// @override
// Widget build(BuildContext context) {
// return SwitchContainer(
// info: const Info(
// label: "VPN",
// iconData: Icons.stacked_line_chart,
// ),
// child: Selector<Config, bool>(
// selector: (_, config) => config.vpnProps.enable,
// builder: (_, enable, __) {
// return Switch(
// materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
// value: enable,
// onChanged: (value) {
// final config = globalState.appController.config;
// config.vpnProps = config.vpnProps.copyWith(
// enable: value,
// );
// },
// );
// },
// ),
// );
// }
// }
import '../config/network.dart';
class TUNSwitch extends StatelessWidget {
const TUNSwitch({super.key});
class TUNButton extends StatelessWidget {
const TUNButton({super.key});
@override
Widget build(BuildContext context) {
return SwitchContainer(
return ButtonContainer(
onPressed: () {
showSheet(
context: context,
builder: (_) {
return generateListView(generateSection(
items: [
if (system.isDesktop) const TUNItem(),
const TunStackItem(),
],
));
},
title: appLocalizations.tun,
);
},
info: Info(
label: appLocalizations.tun,
iconData: Icons.stacked_line_chart,
@@ -64,18 +52,34 @@ class TUNSwitch extends StatelessWidget {
}
}
class ProxySwitch extends StatelessWidget {
const ProxySwitch({super.key});
class SystemProxyButton extends StatelessWidget {
const SystemProxyButton({super.key});
@override
Widget build(BuildContext context) {
return SwitchContainer(
return ButtonContainer(
onPressed: () {
showSheet(
context: context,
builder: (_) {
return generateListView(
generateSection(
items: [
SystemProxyItem(),
BypassDomainItem(),
],
),
);
},
title: appLocalizations.systemProxy,
);
},
info: Info(
label: appLocalizations.systemProxy,
iconData: Icons.shuffle,
),
child: Selector<Config, bool>(
selector: (_, config) => config.desktopProps.systemProxy,
selector: (_, config) => config.networkProps.systemProxy,
builder: (_, systemProxy, __) {
return LocaleBuilder(
builder: (_) => Switch(
@@ -83,8 +87,8 @@ class ProxySwitch extends StatelessWidget {
value: systemProxy,
onChanged: (value) {
final config = globalState.appController.config;
config.desktopProps =
config.desktopProps.copyWith(systemProxy: value);
config.networkProps =
config.networkProps.copyWith(systemProxy: value);
},
),
);
@@ -94,20 +98,22 @@ class ProxySwitch extends StatelessWidget {
}
}
class SwitchContainer extends StatelessWidget {
class ButtonContainer extends StatelessWidget {
final Info info;
final Widget child;
final VoidCallback onPressed;
const SwitchContainer({
const ButtonContainer({
super.key,
required this.info,
required this.child,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return CommonCard(
onPressed: () {},
onPressed: onPressed,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,

View File

@@ -1,9 +1,9 @@
import 'dart:ui';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/fragments/profiles/edit_profile.dart';
import 'package:fl_clash/models/models.dart';
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';
@@ -127,8 +127,8 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
Widget build(BuildContext context) {
return ActiveBuilder(
label: "profiles",
builder: (isCurrent,child){
if(isCurrent){
builder: (isCurrent, child) {
if (isCurrent) {
_initScaffold();
}
return child!;
@@ -246,44 +246,16 @@ class ProfileItem extends StatelessWidget {
);
}
List<Widget> _buildUserInfo(BuildContext context, UserInfo userInfo) {
final use = userInfo.upload + userInfo.download;
final total = userInfo.total;
if (total == 0) {
return [];
}
final useShow = TrafficValue(value: use).show;
final totalShow = TrafficValue(value: total).show;
final progress = total == 0 ? 0.0 : use / total;
final expireShow = userInfo.expire == 0
? appLocalizations.infiniteTime
: DateTime.fromMillisecondsSinceEpoch(userInfo.expire * 1000).show;
return [
LinearProgressIndicator(
minHeight: 6,
value: progress,
backgroundColor: context.colorScheme.primary.toSoft(),
),
const SizedBox(
height: 8,
),
Text(
"$useShow / $totalShow · $expireShow",
style: context.textTheme.labelMedium?.toLight,
),
const SizedBox(
height: 4,
),
];
}
List<Widget> _buildUrlProfileInfo(BuildContext context) {
final userInfo = profile.userInfo;
final subscriptionInfo = profile.subscriptionInfo;
return [
const SizedBox(
height: 8,
),
if (userInfo != null) ..._buildUserInfo(context, userInfo),
if (subscriptionInfo != null)
SubscriptionInfoView(
subscriptionInfo: subscriptionInfo,
),
Text(
profile.lastUpdateDate?.lastUpdateTimeDesc ?? "",
style: context.textTheme.labelMedium?.toLight,

View File

@@ -115,11 +115,10 @@ class ProxyCard extends StatelessWidget {
groupName,
nextProxyName,
);
appController.changeProxy(
groupName: groupName,
proxyName: nextProxyName,
);
await appController.updateGroupDebounce();
await appController.changeProxyDebounce([
groupName,
nextProxyName,
]);
return;
}
globalState.showSnackBar(

View File

@@ -51,7 +51,7 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
}
_headerStateNotifier.value = _headerStateNotifier.value.copyWith(
currentIndex: currentIndex,
offset: headerOffset,
offset: max(headerOffset, 0),
);
}
@@ -299,6 +299,9 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
headerState.currentIndex > state.groupNames.length - 1
? 0
: headerState.currentIndex;
if (index < 0) {
return Container();
}
return Stack(
children: [
Positioned(
@@ -417,9 +420,9 @@ class _ListHeaderState extends State<ListHeader>
final iconMapEntryList =
config.proxiesStyle.iconMap.entries.toList();
final index = iconMapEntryList.indexWhere((item) {
try{
try {
return RegExp(item.key).hasMatch(groupName);
}catch(_){
} catch (_) {
return false;
}
});
@@ -468,7 +471,7 @@ class _ListHeaderState extends State<ListHeader>
Widget build(BuildContext context) {
return CommonCard(
key: widget.key,
radius: 24,
radius: 18,
type: CommonCardType.filled,
child: Container(
padding: const EdgeInsets.symmetric(

View File

@@ -4,7 +4,7 @@ import 'dart:io';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/app.dart';
import 'package:fl_clash/models/ffi.dart';
import 'package:fl_clash/models/core.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
@@ -27,6 +27,7 @@ class _ProvidersState extends State<Providers> {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(
(_) {
globalState.appController.updateProviders();
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [
@@ -55,7 +56,7 @@ class _ProvidersState extends State<Providers> {
providerName: provider.name,
);
appState.setProvider(
clashCore.getExternalProvider(provider.name),
await clashCore.getExternalProvider(provider.name),
);
},
);
@@ -121,7 +122,7 @@ class ProviderItem extends StatelessWidget {
if (message.isNotEmpty) throw message;
});
appState.setProvider(
clashCore.getExternalProvider(provider.name),
await clashCore.getExternalProvider(provider.name),
);
});
await globalState.appController.updateGroupDebounce();
@@ -132,8 +133,8 @@ class ProviderItem extends StatelessWidget {
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);
if (bytes == null || provider.path == null) return;
final file = await File(provider.path!).create(recursive: true);
await file.writeAsBytes(bytes);
final providerName = provider.name;
var message = await clashCore.sideLoadExternalProvider(
@@ -142,7 +143,7 @@ class ProviderItem extends StatelessWidget {
);
if (message.isNotEmpty) throw message;
appState.setProvider(
clashCore.getExternalProvider(provider.name),
await clashCore.getExternalProvider(provider.name),
);
if (message.isNotEmpty) throw message;
});
@@ -150,8 +151,7 @@ class ProviderItem extends StatelessWidget {
}
String _buildProviderDesc() {
final baseInfo =
"${provider.type}(${provider.vehicleType}) · ${provider.updateAt.lastUpdateTimeDesc}";
final baseInfo = provider.updateAt.lastUpdateTimeDesc;
final count = provider.count;
return switch (count == 0) {
true => baseInfo,
@@ -176,10 +176,13 @@ class ProviderItem extends StatelessWidget {
Text(
_buildProviderDesc(),
),
Text(
provider.path,
style: context.textTheme.bodyMedium?.toLight,
const SizedBox(
height: 4,
),
if (provider.subscriptionInfo != null)
SubscriptionInfoView(
subscriptionInfo: provider.subscriptionInfo,
),
const SizedBox(
height: 8,
),
@@ -200,6 +203,9 @@ class ProviderItem extends StatelessWidget {
),
],
),
const SizedBox(
height: 4,
),
],
),
trailing: SizedBox(

View File

@@ -1,4 +1,5 @@
import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
@@ -10,6 +11,8 @@ import 'package:provider/provider.dart';
import 'card.dart';
import 'common.dart';
List<Proxy> currentProxies = [];
typedef GroupNameKeyMap = Map<String, GlobalObjectKey<ProxyGroupViewState>>;
class ProxiesTabFragment extends StatefulWidget {
@@ -28,7 +31,7 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
@override
void dispose() {
super.dispose();
_tabController?.dispose();
_destroyTabController();
}
scrollToGroupSelected() {
@@ -106,6 +109,36 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
);
}
_tabControllerListener([int? index]) {
final appController = globalState.appController;
final currentGroups = appController.appState.currentGroups;
if (_tabController?.index == null) {
return;
}
final currentGroup = currentGroups[index ?? _tabController!.index];
currentProxies = currentGroup.all;
appController.config.updateCurrentGroupName(
currentGroup.name,
);
}
_destroyTabController() {
_tabController?.removeListener(_tabControllerListener);
_tabController?.dispose();
_tabController = null;
}
_updateTabController(int length, int index) {
final realIndex = index == -1 ? 0 : index;
_tabController ??= TabController(
length: length,
initialIndex: realIndex,
vsync: this,
);
_tabControllerListener(realIndex);
_tabController?.addListener(_tabControllerListener);
}
@override
Widget build(BuildContext context) {
return Selector2<AppState, Config, ProxiesSelectorState>(
@@ -119,8 +152,7 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
},
shouldRebuild: (prev, next) {
if (!stringListEquality.equals(prev.groupNames, next.groupNames)) {
_tabController?.dispose();
_tabController = null;
_destroyTabController();
return true;
}
return false;
@@ -129,12 +161,8 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
final index = state.groupNames.indexWhere(
(item) => item == state.currentGroupName,
);
_tabController ??= TabController(
length: state.groupNames.length,
initialIndex: index == -1 ? 0 : index,
vsync: this,
);
GroupNameKeyMap keyMap = {};
_updateTabController(state.groupNames.length, index);
final GroupNameKeyMap keyMap = {};
final children = state.groupNames.map((groupName) {
keyMap[groupName] = GlobalObjectKey(groupName);
return KeepScope(
@@ -167,16 +195,6 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
left: 16,
right: 16 + (value ? 16 : 0),
),
onTap: (index) {
final appController = globalState.appController;
final currentGroups =
appController.appState.currentGroups;
if (currentGroups.length > index) {
appController.config.updateCurrentGroupName(
currentGroups[index].name,
);
}
},
dividerColor: Colors.transparent,
isScrollable: true,
tabAlignment: TabAlignment.start,
@@ -243,14 +261,13 @@ class ProxyGroupView extends StatefulWidget {
class ProxyGroupViewState extends State<ProxyGroupView> {
var isLock = false;
final _controller = ScrollController();
List<Proxy> _lastProxies = [];
String get groupName => widget.groupName;
_delayTest(List<Proxy> proxies) async {
_delayTest() async {
if (isLock) return;
isLock = true;
await delayTest(proxies);
await delayTest(currentProxies);
isLock = false;
}
@@ -269,7 +286,7 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
16 +
getScrollToSelectedOffset(
groupName: groupName,
proxies: _lastProxies,
proxies: currentProxies,
),
_controller.position.maxScrollExtent,
),
@@ -278,7 +295,7 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
);
}
initFab(bool isCurrent, List<Proxy> proxies) {
initFab(bool isCurrent) {
if (!isCurrent) {
return;
}
@@ -287,9 +304,7 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.floatingActionButton = DelayTestButton(
onClick: () async {
await _delayTest(
proxies,
);
await _delayTest();
},
);
});
@@ -319,11 +334,10 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
final sortedProxies = globalState.appController.getSortProxies(
proxies,
);
_lastProxies = sortedProxies;
return ActiveBuilder(
label: "proxies",
builder: (isCurrent, child) {
initFab(isCurrent, proxies);
initFab(isCurrent);
return child!;
},
child: Align(
@@ -381,7 +395,9 @@ class _DelayTestButtonState extends State<DelayTestButton>
_healthcheck() async {
_controller.forward();
await widget.onClick();
_controller.reverse();
if (mounted) {
_controller.reverse();
}
}
@override

View File

@@ -168,6 +168,7 @@
"ipv6Desc": "When turned on it will be able to receive IPv6 traffic",
"app": "App",
"general": "General",
"vpnSystemProxyDesc": "Attach HTTP proxy to VpnService",
"systemProxyDesc": "Attach HTTP proxy to VpnService",
"unifiedDelay": "Unified delay",
"unifiedDelayDesc": "Remove extra delays such as handshaking",
@@ -323,5 +324,13 @@
"adminAutoLaunchDesc": "Boot up by using admin mode",
"fontFamily": "FontFamily",
"systemFont": "System font",
"toggle": "Toggle"
"toggle": "Toggle",
"system": "System",
"routeMode": "Route mode",
"routeMode_bypassPrivate": "Bypass private route address",
"routeMode_config": "Use config",
"routeAddress": "Route address",
"routeAddressDesc": "Config listen route address",
"pleaseInputAdminPassword": "Please enter the admin password",
"copyEnvVar": "Copying environment variables"
}

View File

@@ -168,7 +168,8 @@
"ipv6Desc": "开启后将可以接收IPv6流量",
"app": "应用",
"general": "基础",
"systemProxyDesc": "为VpnService附加HTTP代理",
"vpnSystemProxyDesc": "为VpnService附加HTTP代理",
"systemProxyDesc": "设置系统代理",
"unifiedDelay": "统一延迟",
"unifiedDelayDesc": "去除握手等额外延迟",
"tcpConcurrent": "TCP并发",
@@ -323,5 +324,13 @@
"adminAutoLaunchDesc": "使用管理员模式开机自启动",
"fontFamily": "字体",
"systemFont": "系统字体",
"toggle": "切换"
}
"toggle": "切换",
"system": "系统",
"routeMode": "路由模式",
"routeMode_bypassPrivate": "绕过私有路由地址",
"routeMode_config": "使用配置",
"routeAddress": "路由地址",
"routeAddressDesc": "配置监听路由地址",
"pleaseInputAdminPassword": "请输入管理员密码",
"copyEnvVar": "复制环境变量"
}

View File

@@ -121,6 +121,8 @@ class MessageLookup extends MessageLookupByLibrary {
"View current connections data"),
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity"),
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage(
"Copying environment variables"),
"core": MessageLookupByLibrary.simpleMessage("Core"),
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
"country": MessageLookupByLibrary.simpleMessage("Country"),
@@ -326,6 +328,8 @@ class MessageLookup extends MessageLookupByLibrary {
"paste": MessageLookupByLibrary.simpleMessage("Paste"),
"pleaseBindWebDAV":
MessageLookupByLibrary.simpleMessage("Please bind WebDAV"),
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
"Please enter the admin password"),
"pleaseUploadFile":
MessageLookupByLibrary.simpleMessage("Please upload file"),
"pleaseUploadValidQrcode": MessageLookupByLibrary.simpleMessage(
@@ -398,6 +402,13 @@ class MessageLookup extends MessageLookupByLibrary {
"respectRules": MessageLookupByLibrary.simpleMessage("Respect rules"),
"respectRulesDesc": MessageLookupByLibrary.simpleMessage(
"DNS connection following rules, need to configure proxy-server-nameserver"),
"routeAddress": MessageLookupByLibrary.simpleMessage("Route address"),
"routeAddressDesc":
MessageLookupByLibrary.simpleMessage("Config listen route address"),
"routeMode": MessageLookupByLibrary.simpleMessage("Route mode"),
"routeMode_bypassPrivate": MessageLookupByLibrary.simpleMessage(
"Bypass private route address"),
"routeMode_config": MessageLookupByLibrary.simpleMessage("Use config"),
"rule": MessageLookupByLibrary.simpleMessage("Rule"),
"ruleProviders": MessageLookupByLibrary.simpleMessage("Rule providers"),
"save": MessageLookupByLibrary.simpleMessage("Save"),
@@ -426,6 +437,7 @@ class MessageLookup extends MessageLookupByLibrary {
"style": MessageLookupByLibrary.simpleMessage("Style"),
"submit": MessageLookupByLibrary.simpleMessage("Submit"),
"sync": MessageLookupByLibrary.simpleMessage("Sync"),
"system": MessageLookupByLibrary.simpleMessage("System"),
"systemFont": MessageLookupByLibrary.simpleMessage("System font"),
"systemProxy": MessageLookupByLibrary.simpleMessage("System proxy"),
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(
@@ -475,6 +487,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Modify VPN related settings"),
"vpnEnableDesc": MessageLookupByLibrary.simpleMessage(
"Auto routes all system traffic through VpnService"),
"vpnSystemProxyDesc": MessageLookupByLibrary.simpleMessage(
"Attach HTTP proxy to VpnService"),
"vpnTip": MessageLookupByLibrary.simpleMessage(
"Changes take effect after restarting the VPN"),
"webDAVConfiguration":

View File

@@ -98,6 +98,7 @@ class MessageLookup extends MessageLookupByLibrary {
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
"copy": MessageLookupByLibrary.simpleMessage("复制"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage("复制环境变量"),
"core": MessageLookupByLibrary.simpleMessage("内核"),
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
"country": MessageLookupByLibrary.simpleMessage("区域"),
@@ -259,6 +260,8 @@ class MessageLookup extends MessageLookupByLibrary {
"passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"),
"paste": MessageLookupByLibrary.simpleMessage("粘贴"),
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage("请绑定WebDAV"),
"pleaseInputAdminPassword":
MessageLookupByLibrary.simpleMessage("请输入管理员密码"),
"pleaseUploadFile": MessageLookupByLibrary.simpleMessage("请上传文件"),
"pleaseUploadValidQrcode":
MessageLookupByLibrary.simpleMessage("请上传有效的二维码"),
@@ -314,6 +317,12 @@ class MessageLookup extends MessageLookupByLibrary {
"respectRules": MessageLookupByLibrary.simpleMessage("遵守规则"),
"respectRulesDesc": MessageLookupByLibrary.simpleMessage(
"DNS连接跟随rules,需配置proxy-server-nameserver"),
"routeAddress": MessageLookupByLibrary.simpleMessage("路由地址"),
"routeAddressDesc": MessageLookupByLibrary.simpleMessage("配置监听路由地址"),
"routeMode": MessageLookupByLibrary.simpleMessage("路由模式"),
"routeMode_bypassPrivate":
MessageLookupByLibrary.simpleMessage("绕过私有路由地址"),
"routeMode_config": MessageLookupByLibrary.simpleMessage("使用配置"),
"rule": MessageLookupByLibrary.simpleMessage("规则"),
"ruleProviders": MessageLookupByLibrary.simpleMessage("规则提供者"),
"save": MessageLookupByLibrary.simpleMessage("保存"),
@@ -340,10 +349,10 @@ class MessageLookup extends MessageLookupByLibrary {
"style": MessageLookupByLibrary.simpleMessage("风格"),
"submit": MessageLookupByLibrary.simpleMessage("提交"),
"sync": MessageLookupByLibrary.simpleMessage("同步"),
"system": MessageLookupByLibrary.simpleMessage("系统"),
"systemFont": MessageLookupByLibrary.simpleMessage("系统字体"),
"systemProxy": MessageLookupByLibrary.simpleMessage("系统代理"),
"systemProxyDesc":
MessageLookupByLibrary.simpleMessage("为VpnService附加HTTP代理"),
"systemProxyDesc": MessageLookupByLibrary.simpleMessage("设置系统代理"),
"tab": MessageLookupByLibrary.simpleMessage("标签页"),
"tabAnimation": MessageLookupByLibrary.simpleMessage("选项卡动画"),
"tabAnimationDesc":
@@ -381,6 +390,8 @@ class MessageLookup extends MessageLookupByLibrary {
"vpnDesc": MessageLookupByLibrary.simpleMessage("修改VPN相关设置"),
"vpnEnableDesc":
MessageLookupByLibrary.simpleMessage("通过VpnService自动路由系统所有流量"),
"vpnSystemProxyDesc":
MessageLookupByLibrary.simpleMessage("为VpnService附加HTTP代理"),
"vpnTip": MessageLookupByLibrary.simpleMessage("重启VPN后改变生效"),
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV配置"),
"whitelistMode": MessageLookupByLibrary.simpleMessage("白名单模式"),

View File

@@ -1740,6 +1740,16 @@ class AppLocalizations {
);
}
/// `Attach HTTP proxy to VpnService`
String get vpnSystemProxyDesc {
return Intl.message(
'Attach HTTP proxy to VpnService',
name: 'vpnSystemProxyDesc',
desc: '',
args: [],
);
}
/// `Attach HTTP proxy to VpnService`
String get systemProxyDesc {
return Intl.message(
@@ -3299,6 +3309,86 @@ class AppLocalizations {
args: [],
);
}
/// `System`
String get system {
return Intl.message(
'System',
name: 'system',
desc: '',
args: [],
);
}
/// `Route mode`
String get routeMode {
return Intl.message(
'Route mode',
name: 'routeMode',
desc: '',
args: [],
);
}
/// `Bypass private route address`
String get routeMode_bypassPrivate {
return Intl.message(
'Bypass private route address',
name: 'routeMode_bypassPrivate',
desc: '',
args: [],
);
}
/// `Use config`
String get routeMode_config {
return Intl.message(
'Use config',
name: 'routeMode_config',
desc: '',
args: [],
);
}
/// `Route address`
String get routeAddress {
return Intl.message(
'Route address',
name: 'routeAddress',
desc: '',
args: [],
);
}
/// `Config listen route address`
String get routeAddressDesc {
return Intl.message(
'Config listen route address',
name: 'routeAddressDesc',
desc: '',
args: [],
);
}
/// `Please enter the admin password`
String get pleaseInputAdminPassword {
return Intl.message(
'Please enter the admin password',
name: 'pleaseInputAdminPassword',
desc: '',
args: [],
);
}
/// `Copying environment variables`
String get copyEnvVar {
return Intl.message(
'Copying environment variables',
name: 'copyEnvVar',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -8,17 +8,22 @@ import 'package:fl_clash/plugins/vpn.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'application.dart';
import 'common/common.dart';
import 'l10n/l10n.dart';
import 'models/models.dart';
import 'common/common.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
clashCore.initMessage();
clashLib?.initMessage();
globalState.packageInfo = await PackageInfo.fromPlatform();
final version = await system.version;
final config = await preferences.getConfig() ?? Config();
await AppLocalizations.load(
other.getLocaleForString(config.appSetting.locale) ??
WidgetsBinding.instance.platformDispatcher.locale,
);
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
await android?.init();
await window?.init(config.windowProps, version);
@@ -27,12 +32,14 @@ Future<void> main() async {
version: version,
selectedMap: config.currentSelectedMap,
);
final appFlowingState = AppFlowingState();
appState.navigationItems = navigation.getItems(
openLogs: config.appSetting.openLogs,
hasProxies: false,
);
await globalState.init(
tray.update(
appState: appState,
appFlowingState: appFlowingState,
config: config,
clashConfig: clashConfig,
);
@@ -40,6 +47,7 @@ Future<void> main() async {
runAppWithPreferences(
const Application(),
appState: appState,
appFlowingState: appFlowingState,
config: config,
clashConfig: clashConfig,
);
@@ -57,39 +65,66 @@ Future<void> vpnService() async {
other.getLocaleForString(config.appSetting.locale) ??
WidgetsBinding.instance.platformDispatcher.locale,
);
final appState = AppState(
mode: clashConfig.mode,
selectedMap: config.currentSelectedMap,
version: version,
);
await globalState.init(
appState: appState,
config: config,
clashConfig: clashConfig,
);
await app?.tip(appLocalizations.startVpn);
globalState
.updateClashConfig(
appState: appState,
clashConfig: clashConfig,
config: config,
isPatch: false,
)
.then(
(_) async {
await globalState.handleStart();
tile?.addListener(
TileListenerWithVpn(
onStop: () async {
await app?.tip(appLocalizations.stopVpn);
await globalState.handleStop();
clashCore.shutdown();
exit(0);
},
),
);
globalState.updateTraffic(config: config);
globalState.updateFunctionLists = [
() {
globalState.updateTraffic(config: config);
}
];
},
);
vpn?.setServiceMessageHandler(
ServiceMessageHandler(
onProtect: (Fd fd) async {
await vpn?.setProtect(fd.value);
clashCore.setFdMap(fd.id);
clashLib?.setFdMap(fd.id);
},
onProcess: (Process process) async {
onProcess: (ProcessData process) async {
final packageName = await vpn?.resolverProcess(process);
clashCore.setProcessMap(
clashLib?.setProcessMap(
ProcessMapItem(
id: process.id,
value: packageName ?? "",
),
);
},
onStarted: (String runTime) async {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
},
onLoaded: (String groupName) {
final currentSelectedMap = config.currentSelectedMap;
final proxyName = currentSelectedMap[groupName];
@@ -102,43 +137,20 @@ Future<void> vpnService() async {
},
),
);
await app?.tip(appLocalizations.startVpn);
await globalState.handleStart();
tile?.addListener(
TileListenerWithVpn(
onStop: () async {
await app?.tip(appLocalizations.stopVpn);
await globalState.handleStop();
clashCore.shutdown();
exit(0);
},
),
);
globalState.updateTraffic();
globalState.updateFunctionLists = [
() {
globalState.updateTraffic();
}
];
}
@immutable
class ServiceMessageHandler with ServiceMessageListener {
final Function(Fd fd) _onProtect;
final Function(Process process) _onProcess;
final Function(String runTime) _onStarted;
final Function(ProcessData process) _onProcess;
final Function(String providerName) _onLoaded;
const ServiceMessageHandler({
required Function(Fd fd) onProtect,
required Function(Process process) onProcess,
required Function(String runTime) onStarted,
required Function(ProcessData process) onProcess,
required Function(String providerName) onLoaded,
}) : _onProtect = onProtect,
_onProcess = onProcess,
_onStarted = onStarted,
_onLoaded = onLoaded;
@override
@@ -147,15 +159,10 @@ class ServiceMessageHandler with ServiceMessageListener {
}
@override
onProcess(Process process) {
onProcess(ProcessData process) {
_onProcess(process);
}
@override
onStarted(String runTime) {
_onStarted(runTime);
}
@override
onLoaded(String providerName) {
_onLoaded(providerName);

View File

@@ -1,3 +1,4 @@
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:flutter/material.dart';
@@ -23,6 +24,28 @@ class _AndroidContainerState extends State<AndroidManager> {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
}
Widget _updateCoreState(Widget child) {
return Selector2<Config, ClashConfig, CoreState>(
selector: (_, config, clashConfig) => CoreState(
enable: config.vpnProps.enable,
accessControl: config.isAccessControl ? config.accessControl : null,
ipv6: config.vpnProps.ipv6,
allowBypass: config.vpnProps.allowBypass,
bypassDomain: config.networkProps.bypassDomain,
systemProxy: config.vpnProps.systemProxy,
onlyProxy: config.appSetting.onlyProxy,
currentProfileName:
config.currentProfile?.label ?? config.currentProfileId ?? "",
routeAddress: clashConfig.routeAddress,
),
builder: (__, state, child) {
clashLib?.setState(state);
return child!;
},
child: child,
);
}
Widget _excludeContainer(Widget child) {
return Selector<Config, bool>(
selector: (_, config) => config.appSetting.hidden,
@@ -36,6 +59,10 @@ class _AndroidContainerState extends State<AndroidManager> {
@override
Widget build(BuildContext context) {
return _excludeContainer(widget.child);
return _updateCoreState(
_excludeContainer(
widget.child,
),
);
}
}

View File

@@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';

View File

@@ -20,7 +20,6 @@ class ClashManager extends StatefulWidget {
}
class _ClashContainerState extends State<ClashManager> with AppMessageListener {
Function? updateClashConfigDebounce;
Function? updateDelayDebounce;
Widget _updateContainer(Widget child) {
@@ -47,10 +46,7 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
),
shouldRebuild: (prev, next) {
if (prev != next) {
updateClashConfigDebounce ??= debounce<Function()>(() async {
await globalState.appController.updateClashConfig();
});
updateClashConfigDebounce!();
globalState.appController.updateClashConfigDebounce();
}
return prev != next;
},
@@ -61,40 +57,20 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
);
}
Widget _updateCoreState(Widget child) {
return Selector2<Config, ClashConfig, CoreState>(
selector: (_, config, clashConfig) => CoreState(
enable: config.vpnProps.enable,
accessControl: config.isAccessControl ? config.accessControl : null,
ipv6: config.vpnProps.ipv6,
allowBypass: config.vpnProps.allowBypass,
bypassDomain: config.vpnProps.bypassDomain,
systemProxy: config.vpnProps.systemProxy,
onlyProxy: config.appSetting.onlyProxy,
currentProfileName:
config.currentProfile?.label ?? config.currentProfileId ?? "",
),
builder: (__, state, child) {
clashCore.setState(state);
return child!;
},
child: child,
);
}
_changeProfile() async {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final appController = globalState.appController;
appController.appState.delayMap = {};
await appController.applyProfile();
});
}
Widget _changeProfileContainer(Widget child) {
return Selector<Config, String?>(
selector: (_, config) => config.currentProfileId,
shouldRebuild: (prev, next) {
if (prev != next) {
WidgetsBinding.instance.addPostFrameCallback((_) {
final appController = globalState.appController;
appController.appState.delayMap = {};
appController.applyProfile();
});
}
return prev != next;
},
builder: (__, state, child) {
_changeProfile();
return child!;
},
child: child,
@@ -104,10 +80,8 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
@override
Widget build(BuildContext context) {
return _changeProfileContainer(
_updateCoreState(
_updateContainer(
widget.child,
),
_updateContainer(
widget.child,
),
);
}
@@ -161,7 +135,7 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
Future<void> onLoaded(String providerName) async {
final appController = globalState.appController;
appController.appState.setProvider(
clashCore.getExternalProvider(
await clashCore.getExternalProvider(
providerName,
),
);

View File

@@ -8,13 +8,13 @@ class ProxyManager extends StatelessWidget {
const ProxyManager({super.key, required this.child});
_updateProxy(ProxyState proxyState) {
_updateProxy(ProxyState proxyState) async {
final isStart = proxyState.isStart;
final systemProxy = proxyState.systemProxy;
final port = proxyState.port;
if (isStart && systemProxy) {
proxy?.startProxy(port);
}else{
proxy?.startProxy(port, proxyState.bassDomain);
} else {
proxy?.stopProxy();
}
}
@@ -24,8 +24,9 @@ class ProxyManager extends StatelessWidget {
return Selector3<AppFlowingState, Config, ClashConfig, ProxyState>(
selector: (_, appFlowingState, config, clashConfig) => ProxyState(
isStart: appFlowingState.isStart,
systemProxy: config.desktopProps.systemProxy,
systemProxy: config.networkProps.systemProxy,
port: clashConfig.mixedPort,
bassDomain: config.networkProps.bypassDomain,
),
builder: (_, state, child) {
_updateProxy(state);

View File

@@ -1,4 +1,3 @@
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/tile.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
@@ -16,8 +15,6 @@ class TileManager extends StatefulWidget {
}
class _TileContainerState extends State<TileManager> with TileListener {
@override
Widget build(BuildContext context) {
return widget.child;

View File

@@ -17,8 +17,7 @@ class TrayManager extends StatefulWidget {
State<TrayManager> createState() => _TrayContainerState();
}
class _TrayContainerState extends State<TrayManager>
with TrayListener {
class _TrayContainerState extends State<TrayManager> with TrayListener {
@override
void initState() {
super.initState();
@@ -31,19 +30,23 @@ class _TrayContainerState extends State<TrayManager>
selector: (_, appState, appFlowingState, config, clashConfig) =>
TrayState(
mode: clashConfig.mode,
adminAutoLaunch: config.appSetting.adminAutoLaunch,
autoLaunch: config.appSetting.autoLaunch,
isStart: appFlowingState.isStart,
locale: config.appSetting.locale,
systemProxy: config.desktopProps.systemProxy,
systemProxy: config.networkProps.systemProxy,
tunEnable: clashConfig.tun.enable,
brightness: appState.brightness,
port: clashConfig.mixedPort,
groups: appState.groups,
map: appState.selectedMap,
),
shouldRebuild: (prev, next) {
if (prev != next) {
globalState.appController.updateTray();
}
return prev != next;
},
builder: (_, state, child) {
globalState.appController.updateTray();
return child!;
},
child: widget.child,

View File

@@ -28,7 +28,6 @@ class _WindowContainerState extends State<WindowManager>
return Selector<Config, AutoLaunchState>(
selector: (_, config) => AutoLaunchState(
isAutoLaunch: config.appSetting.autoLaunch,
isAdminAutoLaunch: config.appSetting.adminAutoLaunch,
),
builder: (_, state, child) {
updateLaunchDebounce ??= debounce((AutoLaunchState state) {
@@ -59,15 +58,15 @@ class _WindowContainerState extends State<WindowManager>
super.onWindowClose();
}
@override
Future<void> onShouldTerminate() async {
await globalState.appController.handleExit();
super.onShouldTerminate();
}
@override
Future<void> onWindowMoved() async {
super.onWindowMoved();
final offset = await windowManager.getPosition();
final config = globalState.appController.config;
config.windowProps = config.windowProps.copyWith(
top: offset.dy,
left: offset.dx,
);
}
@override
@@ -88,8 +87,9 @@ class _WindowContainerState extends State<WindowManager>
}
@override
void onTaskbarCreated() {
Future<void> onTaskbarCreated() async {
globalState.appController.updateTray(true);
await globalState.appController.restartCore();
super.onTaskbarCreated();
}

View File

@@ -1,8 +1,9 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
import 'common.dart';
import 'ffi.dart';
import 'core.dart';
import 'profile.dart';
typedef DelayMap = Map<String, int?>;

View File

@@ -7,9 +7,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import '../enum/enum.dart';
part 'generated/clash_config.g.dart';
part 'generated/clash_config.freezed.dart';
part 'generated/clash_config.g.dart';
const defaultTun = Tun();
@@ -126,6 +125,91 @@ typedef HostsMap = Map<String, String>;
const defaultMixedPort = 7890;
const defaultKeepAliveInterval = 30;
const defaultBypassPrivateRouteAddress = [
"1.0.0.0/8",
"2.0.0.0/7",
"4.0.0.0/6",
"8.0.0.0/7",
"11.0.0.0/8",
"12.0.0.0/6",
"16.0.0.0/4",
"32.0.0.0/3",
"64.0.0.0/3",
"96.0.0.0/4",
"112.0.0.0/5",
"120.0.0.0/6",
"124.0.0.0/7",
"126.0.0.0/8",
"128.0.0.0/3",
"160.0.0.0/5",
"168.0.0.0/8",
"169.0.0.0/9",
"169.128.0.0/10",
"169.192.0.0/11",
"169.224.0.0/12",
"169.240.0.0/13",
"169.248.0.0/14",
"169.252.0.0/15",
"169.255.0.0/16",
"170.0.0.0/7",
"172.0.0.0/12",
"172.32.0.0/11",
"172.64.0.0/10",
"172.128.0.0/9",
"173.0.0.0/8",
"174.0.0.0/7",
"176.0.0.0/4",
"192.0.0.0/9",
"192.128.0.0/11",
"192.160.0.0/13",
"192.169.0.0/16",
"192.170.0.0/15",
"192.172.0.0/14",
"192.176.0.0/12",
"192.192.0.0/10",
"193.0.0.0/8",
"194.0.0.0/7",
"196.0.0.0/6",
"200.0.0.0/5",
"208.0.0.0/4",
"240.0.0.0/5",
"248.0.0.0/6",
"252.0.0.0/7",
"254.0.0.0/8",
"255.0.0.0/9",
"255.128.0.0/10",
"255.192.0.0/11",
"255.224.0.0/12",
"255.240.0.0/13",
"255.248.0.0/14",
"255.252.0.0/15",
"255.254.0.0/16",
"255.255.0.0/17",
"255.255.128.0/18",
"255.255.192.0/19",
"255.255.224.0/20",
"255.255.240.0/21",
"255.255.248.0/22",
"255.255.252.0/23",
"255.255.254.0/24",
"255.255.255.0/25",
"255.255.255.128/26",
"255.255.255.192/27",
"255.255.255.224/28",
"255.255.255.240/29",
"255.255.255.248/30",
"255.255.255.252/31",
"255.255.255.254/32",
"::/1",
"8000::/2",
"c000::/3",
"e000::/4",
"f000::/5",
"f800::/6",
"fe00::/9",
"fec0::/10"
];
@JsonSerializable()
class ClashConfig extends ChangeNotifier {
int _mixedPort;
@@ -145,6 +229,8 @@ class ClashConfig extends ChangeNotifier {
List<String> _rules;
String? _globalRealUa;
HostsMap _hosts;
List<String> _includeRouteAddress;
RouteMode _routeMode;
ClashConfig()
: _mixedPort = defaultMixedPort,
@@ -161,6 +247,8 @@ class ClashConfig extends ChangeNotifier {
_keepAliveInterval = defaultKeepAliveInterval,
_dns = defaultDns,
_geoXUrl = defaultGeoXMap,
_routeMode = RouteMode.config,
_includeRouteAddress = [],
_rules = [],
_hosts = {};
@@ -343,6 +431,34 @@ class ClashConfig extends ChangeNotifier {
}
}
@JsonKey(name: "route-address", includeFromJson: false, includeToJson: true)
List<String> get routeAddress {
return switch (_routeMode == RouteMode.config) {
true => _includeRouteAddress,
false => defaultBypassPrivateRouteAddress,
};
}
@JsonKey(name: "include-route-address", defaultValue: [])
List<String> get includeRouteAddress => _includeRouteAddress;
set includeRouteAddress(List<String> value) {
if (!stringListEquality.equals(value, _includeRouteAddress)) {
_includeRouteAddress = value;
notifyListeners();
}
}
@JsonKey(name: "route-mode", defaultValue: RouteMode.config)
RouteMode get routeMode => _routeMode;
set routeMode(RouteMode value) {
if (value != _routeMode) {
_routeMode = value;
notifyListeners();
}
}
update([ClashConfig? clashConfig]) {
if (clashConfig != null) {
_mixedPort = clashConfig._mixedPort;
@@ -360,10 +476,34 @@ class ClashConfig extends ChangeNotifier {
_geodataLoader = clashConfig._geodataLoader;
_dns = clashConfig._dns;
_rules = clashConfig._rules;
_routeMode = clashConfig._routeMode;
_includeRouteAddress = clashConfig._includeRouteAddress;
}
notifyListeners();
}
ClashConfig copyWith() {
return ClashConfig()
..mixedPort = _mixedPort
..mode = _mode
..ipv6 = _ipv6
..findProcessMode = _findProcessMode
..allowLan = _allowLan
..tcpConcurrent = _tcpConcurrent
..logLevel = _logLevel
..tun = tun
..unifiedDelay = _unifiedDelay
..geodataLoader = _geodataLoader
..externalController = _externalController
..keepAliveInterval = _keepAliveInterval
..dns = _dns
..geoXUrl = _geoXUrl
..routeMode = _routeMode
..includeRouteAddress = _includeRouteAddress
..rules = _rules
..hosts = _hosts;
}
Map<String, dynamic> toJson() {
return _$ClashConfigToJson(this);
}

View File

@@ -1,4 +1,5 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
@@ -6,9 +7,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'models.dart';
part 'generated/config.g.dart';
part 'generated/config.freezed.dart';
part 'generated/config.g.dart';
final defaultAppSetting = const AppSetting().copyWith(
isAnimateToPage: system.isDesktop ? false : true,
@@ -20,7 +20,6 @@ class AppSetting with _$AppSetting {
String? locale,
@Default(false) bool onlyProxy,
@Default(false) bool autoLaunch,
@Default(false) bool adminAutoLaunch,
@Default(false) bool silentLaunch,
@Default(false) bool autoRun,
@Default(false) bool openLogs,
@@ -38,9 +37,8 @@ class AppSetting with _$AppSetting {
_$AppSettingFromJson(json);
factory AppSetting.realFromJson(Map<String, Object?>? json) {
final appSetting = json == null
? defaultAppSetting
: AppSetting.fromJson(json);
final appSetting =
json == null ? defaultAppSetting : AppSetting.fromJson(json);
return appSetting.copyWith(
isAnimateToPage: system.isDesktop ? false : appSetting.isAnimateToPage,
);
@@ -110,7 +108,6 @@ class VpnProps with _$VpnProps {
@Default(true) bool systemProxy,
@Default(false) bool ipv6,
@Default(true) bool allowBypass,
@Default(defaultBypassDomain) List<String> bypassDomain,
}) = _VpnProps;
factory VpnProps.fromJson(Map<String, Object?>? json) =>
@@ -118,13 +115,14 @@ class VpnProps with _$VpnProps {
}
@freezed
class DesktopProps with _$DesktopProps {
const factory DesktopProps({
class NetworkProps with _$NetworkProps {
const factory NetworkProps({
@Default(true) bool systemProxy,
}) = _DesktopProps;
@Default(defaultBypassDomain) List<String> bypassDomain,
}) = _NetworkProps;
factory DesktopProps.fromJson(Map<String, Object?>? json) =>
json == null ? const DesktopProps() : _$DesktopPropsFromJson(json);
factory NetworkProps.fromJson(Map<String, Object?>? json) =>
json == null ? const NetworkProps() : _$NetworkPropsFromJson(json);
}
const defaultProxiesStyle = ProxiesStyle();
@@ -188,7 +186,7 @@ class Config extends ChangeNotifier {
WindowProps _windowProps;
ThemeProps _themeProps;
VpnProps _vpnProps;
DesktopProps _desktopProps;
NetworkProps _networkProps;
bool _overrideDns;
List<HotKeyAction> _hotKeyActions;
ProxiesStyle _proxiesStyle;
@@ -199,7 +197,7 @@ class Config extends ChangeNotifier {
_accessControl = const AccessControl(),
_windowProps = const WindowProps(),
_vpnProps = defaultVpnProps,
_desktopProps = const DesktopProps(),
_networkProps = const NetworkProps(),
_overrideDns = false,
_appSetting = defaultAppSetting,
_hotKeyActions = [],
@@ -384,11 +382,11 @@ class Config extends ChangeNotifier {
}
}
DesktopProps get desktopProps => _desktopProps;
NetworkProps get networkProps => _networkProps;
set desktopProps(DesktopProps value) {
if (_desktopProps != value) {
_desktopProps = value;
set networkProps(NetworkProps value) {
if (_networkProps != value) {
_networkProps = value;
notifyListeners();
}
}
@@ -471,7 +469,7 @@ class Config extends ChangeNotifier {
_proxiesStyle = config._proxiesStyle;
_vpnProps = config._vpnProps;
_overrideDns = config._overrideDns;
_desktopProps = config._desktopProps;
_networkProps = config._networkProps;
_hotKeyActions = config._hotKeyActions;
}
notifyListeners();
@@ -487,6 +485,6 @@ class Config extends ChangeNotifier {
@override
String toString() {
return 'Config{_appSetting: $_appSetting, _profiles: $_profiles, _currentProfileId: $_currentProfileId, _isAccessControl: $_isAccessControl, _accessControl: $_accessControl, _dav: $_dav, _windowProps: $_windowProps, _themeProps: $_themeProps, _vpnProps: $_vpnProps, _desktopProps: $_desktopProps, _overrideDns: $_overrideDns, _hotKeyActions: $_hotKeyActions, _proxiesStyle: $_proxiesStyle}';
return 'Config{_appSetting: $_appSetting, _profiles: $_profiles, _currentProfileId: $_currentProfileId, _isAccessControl: $_isAccessControl, _accessControl: $_accessControl, _dav: $_dav, _windowProps: $_windowProps, _themeProps: $_themeProps, _vpnProps: $_vpnProps, _networkProps: $_networkProps, _overrideDns: $_overrideDns, _hotKeyActions: $_hotKeyActions, _proxiesStyle: $_proxiesStyle}';
}
}

View File

@@ -1,12 +1,35 @@
// ignore_for_file: invalid_annotation_target
import 'dart:convert';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'generated/ffi.g.dart';
part 'generated/core.freezed.dart';
part 'generated/core.g.dart';
part 'generated/ffi.freezed.dart';
abstract mixin class AppMessageListener {
void onLog(Log log) {}
void onDelay(Delay delay) {}
void onRequest(Connection connection) {}
void onStarted(String runTime) {}
void onLoaded(String providerName) {}
}
abstract mixin class ServiceMessageListener {
onProtect(Fd fd) {}
onProcess(ProcessData process) {}
onStarted(String runTime) {}
onLoaded(String providerName) {}
}
@freezed
class CoreState with _$CoreState {
@@ -17,6 +40,7 @@ class CoreState with _$CoreState {
required bool allowBypass,
required bool systemProxy,
required List<String> bypassDomain,
required List<String> routeAddress,
required bool ipv6,
required bool onlyProxy,
}) = _CoreState;
@@ -36,6 +60,7 @@ class AndroidVpnOptions with _$AndroidVpnOptions {
required List<String> bypassDomain,
required String ipv4Address,
required String ipv6Address,
required List<String> routeAddress,
required String dnsServerAddress,
}) = _AndroidVpnOptions;
@@ -123,14 +148,14 @@ class Now with _$Now {
}
@freezed
class Process with _$Process {
const factory Process({
class ProcessData with _$ProcessData {
const factory ProcessData({
required int id,
required Metadata metadata,
}) = _Process;
}) = _ProcessData;
factory Process.fromJson(Map<String, Object?> json) =>
_$ProcessFromJson(json);
factory ProcessData.fromJson(Map<String, Object?> json) =>
_$ProcessDataFromJson(json);
}
@freezed
@@ -154,13 +179,38 @@ class ProcessMapItem with _$ProcessMapItem {
_$ProcessMapItemFromJson(json);
}
@freezed
class ProviderSubscriptionInfo with _$ProviderSubscriptionInfo {
const factory ProviderSubscriptionInfo({
@JsonKey(name: "UPLOAD") @Default(0) int upload,
@JsonKey(name: "DOWNLOAD") @Default(0) int download,
@JsonKey(name: "TOTAL") @Default(0) int total,
@JsonKey(name: "EXPIRE") @Default(0) int expire,
}) = _ProviderSubscriptionInfo;
factory ProviderSubscriptionInfo.fromJson(Map<String, Object?> json) =>
_$ProviderSubscriptionInfoFromJson(json);
}
SubscriptionInfo? subscriptionInfoFormCore(Map<String, Object?>? json) {
if (json == null) return null;
return SubscriptionInfo(
upload: (json['Upload'] as num?)?.toInt() ?? 0,
download: (json['Download'] as num?)?.toInt() ?? 0,
total: (json['Total'] as num?)?.toInt() ?? 0,
expire: (json['Expire'] as num?)?.toInt() ?? 0,
);
}
@freezed
class ExternalProvider with _$ExternalProvider {
const factory ExternalProvider({
required String name,
required String type,
required String path,
String? path,
required int count,
@JsonKey(name: "subscription-info", fromJson: subscriptionInfoFormCore)
SubscriptionInfo? subscriptionInfo,
@Default(false) bool isUpdating,
@JsonKey(name: "vehicle-type") required String vehicleType,
@JsonKey(name: "update-at") required DateTime updateAt,
@@ -186,24 +236,19 @@ class TunProps with _$TunProps {
_$TunPropsFromJson(json);
}
abstract mixin class AppMessageListener {
void onLog(Log log) {}
@freezed
class Action with _$Action {
const factory Action({
required ActionMethod method,
required dynamic data,
required String id,
}) = _Action;
void onDelay(Delay delay) {}
void onRequest(Connection connection) {}
void onStarted(String runTime) {}
void onLoaded(String providerName) {}
factory Action.fromJson(Map<String, Object?> json) => _$ActionFromJson(json);
}
abstract mixin class ServiceMessageListener {
onProtect(Fd fd) {}
onProcess(Process process) {}
onStarted(String runTime) {}
onLoaded(String providerName) {}
extension ActionExt on Action {
String get toJson {
return json.encode(this);
}
}

View File

@@ -26,8 +26,12 @@ mixin _$Tun {
@JsonKey(name: "dns-hijack")
List<String> get dnsHijack => throw _privateConstructorUsedError;
/// Serializes this Tun to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Tun
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$TunCopyWith<Tun> get copyWith => throw _privateConstructorUsedError;
}
@@ -52,6 +56,8 @@ class _$TunCopyWithImpl<$Res, $Val extends Tun> implements $TunCopyWith<$Res> {
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Tun
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -100,6 +106,8 @@ class __$$TunImplCopyWithImpl<$Res> extends _$TunCopyWithImpl<$Res, _$TunImpl>
__$$TunImplCopyWithImpl(_$TunImpl _value, $Res Function(_$TunImpl) _then)
: super(_value, _then);
/// Create a copy of Tun
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -178,12 +186,14 @@ class _$TunImpl implements _Tun {
.equals(other._dnsHijack, _dnsHijack));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, enable, device, stack,
const DeepCollectionEquality().hash(_dnsHijack));
@JsonKey(ignore: true)
/// Create a copy of Tun
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$TunImplCopyWith<_$TunImpl> get copyWith =>
@@ -215,8 +225,11 @@ abstract class _Tun implements Tun {
@override
@JsonKey(name: "dns-hijack")
List<String> get dnsHijack;
/// Create a copy of Tun
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$TunImplCopyWith<_$TunImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -234,8 +247,12 @@ mixin _$FallbackFilter {
List<String> get ipcidr => throw _privateConstructorUsedError;
List<String> get domain => throw _privateConstructorUsedError;
/// Serializes this FallbackFilter to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of FallbackFilter
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$FallbackFilterCopyWith<FallbackFilter> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -264,6 +281,8 @@ class _$FallbackFilterCopyWithImpl<$Res, $Val extends FallbackFilter>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of FallbackFilter
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -322,6 +341,8 @@ class __$$FallbackFilterImplCopyWithImpl<$Res>
_$FallbackFilterImpl _value, $Res Function(_$FallbackFilterImpl) _then)
: super(_value, _then);
/// Create a copy of FallbackFilter
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -427,7 +448,7 @@ class _$FallbackFilterImpl implements _FallbackFilter {
const DeepCollectionEquality().equals(other._domain, _domain));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
@@ -437,7 +458,9 @@ class _$FallbackFilterImpl implements _FallbackFilter {
const DeepCollectionEquality().hash(_ipcidr),
const DeepCollectionEquality().hash(_domain));
@JsonKey(ignore: true)
/// Create a copy of FallbackFilter
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$FallbackFilterImplCopyWith<_$FallbackFilterImpl> get copyWith =>
@@ -474,8 +497,11 @@ abstract class _FallbackFilter implements FallbackFilter {
List<String> get ipcidr;
@override
List<String> get domain;
/// Create a copy of FallbackFilter
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$FallbackFilterImplCopyWith<_$FallbackFilterImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -514,8 +540,12 @@ mixin _$Dns {
@JsonKey(name: "fallback-filter")
FallbackFilter get fallbackFilter => throw _privateConstructorUsedError;
/// Serializes this Dns to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Dns
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$DnsCopyWith<Dns> get copyWith => throw _privateConstructorUsedError;
}
@@ -554,6 +584,8 @@ class _$DnsCopyWithImpl<$Res, $Val extends Dns> implements $DnsCopyWith<$Res> {
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Dns
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -637,6 +669,8 @@ class _$DnsCopyWithImpl<$Res, $Val extends Dns> implements $DnsCopyWith<$Res> {
) as $Val);
}
/// Create a copy of Dns
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$FallbackFilterCopyWith<$Res> get fallbackFilter {
@@ -680,6 +714,8 @@ class __$$DnsImplCopyWithImpl<$Res> extends _$DnsCopyWithImpl<$Res, _$DnsImpl>
__$$DnsImplCopyWithImpl(_$DnsImpl _value, $Res Function(_$DnsImpl) _then)
: super(_value, _then);
/// Create a copy of Dns
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -932,7 +968,7 @@ class _$DnsImpl implements _Dns {
other.fallbackFilter == fallbackFilter));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
@@ -952,7 +988,9 @@ class _$DnsImpl implements _Dns {
const DeepCollectionEquality().hash(_proxyServerNameserver),
fallbackFilter);
@JsonKey(ignore: true)
/// Create a copy of Dns
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$DnsImplCopyWith<_$DnsImpl> get copyWith =>
@@ -1030,8 +1068,11 @@ abstract class _Dns implements Dns {
@override
@JsonKey(name: "fallback-filter")
FallbackFilter get fallbackFilter;
/// Create a copy of Dns
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$DnsImplCopyWith<_$DnsImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -41,7 +41,13 @@ ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig()
..hosts = (json['hosts'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
) ??
{};
{}
..includeRouteAddress = (json['include-route-address'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[]
..routeMode = $enumDecodeNullable(_$RouteModeEnumMap, json['route-mode']) ??
RouteMode.config;
Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) =>
<String, dynamic>{
@@ -63,6 +69,9 @@ Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) =>
'global-real-ua': instance.globalRealUa,
'geox-url': instance.geoXUrl,
'hosts': instance.hosts,
'route-address': instance.routeAddress,
'include-route-address': instance.includeRouteAddress,
'route-mode': _$RouteModeEnumMap[instance.routeMode]!,
};
const _$ModeEnumMap = {
@@ -84,6 +93,11 @@ const _$LogLevelEnumMap = {
LogLevel.silent: 'silent',
};
const _$RouteModeEnumMap = {
RouteMode.bypassPrivate: 'bypassPrivate',
RouteMode.config: 'config',
};
_$TunImpl _$$TunImplFromJson(Map<String, dynamic> json) => _$TunImpl(
enable: json['enable'] as bool? ?? false,
device: json['device'] as String? ?? appName,

View File

@@ -24,7 +24,9 @@ mixin _$NavigationItem {
String? get path => throw _privateConstructorUsedError;
List<NavigationItemMode> get modes => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of NavigationItem
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$NavigationItemCopyWith<NavigationItem> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -55,6 +57,8 @@ class _$NavigationItemCopyWithImpl<$Res, $Val extends NavigationItem>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of NavigationItem
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -125,6 +129,8 @@ class __$$NavigationItemImplCopyWithImpl<$Res>
_$NavigationItemImpl _value, $Res Function(_$NavigationItemImpl) _then)
: super(_value, _then);
/// Create a copy of NavigationItem
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -232,7 +238,9 @@ class _$NavigationItemImpl implements _NavigationItem {
int get hashCode => Object.hash(runtimeType, icon, label, description,
fragment, keep, path, const DeepCollectionEquality().hash(_modes));
@JsonKey(ignore: true)
/// Create a copy of NavigationItem
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$NavigationItemImplCopyWith<_$NavigationItemImpl> get copyWith =>
@@ -264,8 +272,11 @@ abstract class _NavigationItem implements NavigationItem {
String? get path;
@override
List<NavigationItemMode> get modes;
/// Create a copy of NavigationItem
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$NavigationItemImplCopyWith<_$NavigationItemImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -281,8 +292,12 @@ mixin _$Package {
bool get isSystem => throw _privateConstructorUsedError;
int get firstInstallTime => throw _privateConstructorUsedError;
/// Serializes this Package to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Package
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PackageCopyWith<Package> get copyWith => throw _privateConstructorUsedError;
}
@@ -305,6 +320,8 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Package
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -353,6 +370,8 @@ class __$$PackageImplCopyWithImpl<$Res>
_$PackageImpl _value, $Res Function(_$PackageImpl) _then)
: super(_value, _then);
/// Create a copy of Package
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -422,12 +441,14 @@ class _$PackageImpl implements _Package {
other.firstInstallTime == firstInstallTime));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, packageName, label, isSystem, firstInstallTime);
@JsonKey(ignore: true)
/// Create a copy of Package
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PackageImplCopyWith<_$PackageImpl> get copyWith =>
@@ -458,8 +479,11 @@ abstract class _Package implements Package {
bool get isSystem;
@override
int get firstInstallTime;
/// Create a copy of Package
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PackageImplCopyWith<_$PackageImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -480,8 +504,12 @@ mixin _$Metadata {
String get process => throw _privateConstructorUsedError;
String get remoteDestination => throw _privateConstructorUsedError;
/// Serializes this Metadata to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Metadata
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$MetadataCopyWith<Metadata> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -513,6 +541,8 @@ class _$MetadataCopyWithImpl<$Res, $Val extends Metadata>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Metadata
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -595,6 +625,8 @@ class __$$MetadataImplCopyWithImpl<$Res>
_$MetadataImpl _value, $Res Function(_$MetadataImpl) _then)
: super(_value, _then);
/// Create a copy of Metadata
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -711,7 +743,7 @@ class _$MetadataImpl implements _Metadata {
other.remoteDestination == remoteDestination));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
@@ -725,7 +757,9 @@ class _$MetadataImpl implements _Metadata {
process,
remoteDestination);
@JsonKey(ignore: true)
/// Create a copy of Metadata
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$MetadataImplCopyWith<_$MetadataImpl> get copyWith =>
@@ -772,8 +806,11 @@ abstract class _Metadata implements Metadata {
String get process;
@override
String get remoteDestination;
/// Create a copy of Metadata
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$MetadataImplCopyWith<_$MetadataImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -791,8 +828,12 @@ mixin _$Connection {
Metadata get metadata => throw _privateConstructorUsedError;
List<String> get chains => throw _privateConstructorUsedError;
/// Serializes this Connection to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Connection
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ConnectionCopyWith<Connection> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -824,6 +865,8 @@ class _$ConnectionCopyWithImpl<$Res, $Val extends Connection>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Connection
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -862,6 +905,8 @@ class _$ConnectionCopyWithImpl<$Res, $Val extends Connection>
) as $Val);
}
/// Create a copy of Connection
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$MetadataCopyWith<$Res> get metadata {
@@ -899,6 +944,8 @@ class __$$ConnectionImplCopyWithImpl<$Res>
_$ConnectionImpl _value, $Res Function(_$ConnectionImpl) _then)
: super(_value, _then);
/// Create a copy of Connection
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -991,12 +1038,14 @@ class _$ConnectionImpl implements _Connection {
const DeepCollectionEquality().equals(other._chains, _chains));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, id, upload, download, start,
metadata, const DeepCollectionEquality().hash(_chains));
@JsonKey(ignore: true)
/// Create a copy of Connection
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ConnectionImplCopyWith<_$ConnectionImpl> get copyWith =>
@@ -1034,8 +1083,11 @@ abstract class _Connection implements Connection {
Metadata get metadata;
@override
List<String> get chains;
/// Create a copy of Connection
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ConnectionImplCopyWith<_$ConnectionImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1049,8 +1101,12 @@ mixin _$LogsAndKeywords {
List<Log> get logs => throw _privateConstructorUsedError;
List<String> get keywords => throw _privateConstructorUsedError;
/// Serializes this LogsAndKeywords to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of LogsAndKeywords
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$LogsAndKeywordsCopyWith<LogsAndKeywords> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1074,6 +1130,8 @@ class _$LogsAndKeywordsCopyWithImpl<$Res, $Val extends LogsAndKeywords>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of LogsAndKeywords
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1112,6 +1170,8 @@ class __$$LogsAndKeywordsImplCopyWithImpl<$Res>
_$LogsAndKeywordsImpl _value, $Res Function(_$LogsAndKeywordsImpl) _then)
: super(_value, _then);
/// Create a copy of LogsAndKeywords
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1174,14 +1234,16 @@ class _$LogsAndKeywordsImpl implements _LogsAndKeywords {
const DeepCollectionEquality().equals(other._keywords, _keywords));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_logs),
const DeepCollectionEquality().hash(_keywords));
@JsonKey(ignore: true)
/// Create a copy of LogsAndKeywords
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LogsAndKeywordsImplCopyWith<_$LogsAndKeywordsImpl> get copyWith =>
@@ -1208,8 +1270,11 @@ abstract class _LogsAndKeywords implements LogsAndKeywords {
List<Log> get logs;
@override
List<String> get keywords;
/// Create a copy of LogsAndKeywords
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LogsAndKeywordsImplCopyWith<_$LogsAndKeywordsImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1224,8 +1289,12 @@ mixin _$ConnectionsAndKeywords {
List<Connection> get connections => throw _privateConstructorUsedError;
List<String> get keywords => throw _privateConstructorUsedError;
/// Serializes this ConnectionsAndKeywords to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of ConnectionsAndKeywords
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ConnectionsAndKeywordsCopyWith<ConnectionsAndKeywords> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1250,6 +1319,8 @@ class _$ConnectionsAndKeywordsCopyWithImpl<$Res,
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ConnectionsAndKeywords
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1291,6 +1362,8 @@ class __$$ConnectionsAndKeywordsImplCopyWithImpl<$Res>
$Res Function(_$ConnectionsAndKeywordsImpl) _then)
: super(_value, _then);
/// Create a copy of ConnectionsAndKeywords
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1355,14 +1428,16 @@ class _$ConnectionsAndKeywordsImpl implements _ConnectionsAndKeywords {
const DeepCollectionEquality().equals(other._keywords, _keywords));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_connections),
const DeepCollectionEquality().hash(_keywords));
@JsonKey(ignore: true)
/// Create a copy of ConnectionsAndKeywords
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ConnectionsAndKeywordsImplCopyWith<_$ConnectionsAndKeywordsImpl>
@@ -1389,8 +1464,11 @@ abstract class _ConnectionsAndKeywords implements ConnectionsAndKeywords {
List<Connection> get connections;
@override
List<String> get keywords;
/// Create a copy of ConnectionsAndKeywords
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ConnectionsAndKeywordsImplCopyWith<_$ConnectionsAndKeywordsImpl>
get copyWith => throw _privateConstructorUsedError;
}
@@ -1406,8 +1484,12 @@ mixin _$DAV {
String get password => throw _privateConstructorUsedError;
String get fileName => throw _privateConstructorUsedError;
/// Serializes this DAV to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of DAV
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$DAVCopyWith<DAV> get copyWith => throw _privateConstructorUsedError;
}
@@ -1428,6 +1510,8 @@ class _$DAVCopyWithImpl<$Res, $Val extends DAV> implements $DAVCopyWith<$Res> {
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of DAV
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1472,6 +1556,8 @@ class __$$DAVImplCopyWithImpl<$Res> extends _$DAVCopyWithImpl<$Res, _$DAVImpl>
__$$DAVImplCopyWithImpl(_$DAVImpl _value, $Res Function(_$DAVImpl) _then)
: super(_value, _then);
/// Create a copy of DAV
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1541,11 +1627,13 @@ class _$DAVImpl implements _DAV {
other.fileName == fileName));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, uri, user, password, fileName);
@JsonKey(ignore: true)
/// Create a copy of DAV
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$DAVImplCopyWith<_$DAVImpl> get copyWith =>
@@ -1576,8 +1664,11 @@ abstract class _DAV implements DAV {
String get password;
@override
String get fileName;
/// Create a copy of DAV
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$DAVImplCopyWith<_$DAVImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1587,7 +1678,9 @@ mixin _$FileInfo {
int get size => throw _privateConstructorUsedError;
DateTime get lastModified => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of FileInfo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$FileInfoCopyWith<FileInfo> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1610,6 +1703,8 @@ class _$FileInfoCopyWithImpl<$Res, $Val extends FileInfo>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of FileInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1648,6 +1743,8 @@ class __$$FileInfoImplCopyWithImpl<$Res>
_$FileInfoImpl _value, $Res Function(_$FileInfoImpl) _then)
: super(_value, _then);
/// Create a copy of FileInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1695,7 +1792,9 @@ class _$FileInfoImpl implements _FileInfo {
@override
int get hashCode => Object.hash(runtimeType, size, lastModified);
@JsonKey(ignore: true)
/// Create a copy of FileInfo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$FileInfoImplCopyWith<_$FileInfoImpl> get copyWith =>
@@ -1711,8 +1810,11 @@ abstract class _FileInfo implements FileInfo {
int get size;
@override
DateTime get lastModified;
/// Create a copy of FileInfo
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$FileInfoImplCopyWith<_$FileInfoImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1726,8 +1828,12 @@ mixin _$VersionInfo {
String get clashName => throw _privateConstructorUsedError;
String get version => throw _privateConstructorUsedError;
/// Serializes this VersionInfo to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of VersionInfo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$VersionInfoCopyWith<VersionInfo> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1751,6 +1857,8 @@ class _$VersionInfoCopyWithImpl<$Res, $Val extends VersionInfo>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of VersionInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1789,6 +1897,8 @@ class __$$VersionInfoImplCopyWithImpl<$Res>
_$VersionInfoImpl _value, $Res Function(_$VersionInfoImpl) _then)
: super(_value, _then);
/// Create a copy of VersionInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1838,11 +1948,13 @@ class _$VersionInfoImpl implements _VersionInfo {
(identical(other.version, version) || other.version == version));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, clashName, version);
@JsonKey(ignore: true)
/// Create a copy of VersionInfo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$VersionInfoImplCopyWith<_$VersionInfoImpl> get copyWith =>
@@ -1867,8 +1979,11 @@ abstract class _VersionInfo implements VersionInfo {
String get clashName;
@override
String get version;
/// Create a copy of VersionInfo
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$VersionInfoImplCopyWith<_$VersionInfoImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1886,8 +2001,12 @@ mixin _$Group {
String get icon => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
/// Serializes this Group to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Group
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$GroupCopyWith<Group> get copyWith => throw _privateConstructorUsedError;
}
@@ -1915,6 +2034,8 @@ class _$GroupCopyWithImpl<$Res, $Val extends Group>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Group
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1978,6 +2099,8 @@ class __$$GroupImplCopyWithImpl<$Res>
_$GroupImpl _value, $Res Function(_$GroupImpl) _then)
: super(_value, _then);
/// Create a copy of Group
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -2071,12 +2194,14 @@ class _$GroupImpl implements _Group {
(identical(other.name, name) || other.name == name));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, type,
const DeepCollectionEquality().hash(_all), now, hidden, icon, name);
@JsonKey(ignore: true)
/// Create a copy of Group
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$GroupImplCopyWith<_$GroupImpl> get copyWith =>
@@ -2113,8 +2238,11 @@ abstract class _Group implements Group {
String get icon;
@override
String get name;
/// Create a copy of Group
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$GroupImplCopyWith<_$GroupImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -2129,8 +2257,12 @@ mixin _$Proxy {
String get type => throw _privateConstructorUsedError;
String? get now => throw _privateConstructorUsedError;
/// Serializes this Proxy to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Proxy
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProxyCopyWith<Proxy> get copyWith => throw _privateConstructorUsedError;
}
@@ -2152,6 +2284,8 @@ class _$ProxyCopyWithImpl<$Res, $Val extends Proxy>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Proxy
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -2194,6 +2328,8 @@ class __$$ProxyImplCopyWithImpl<$Res>
_$ProxyImpl _value, $Res Function(_$ProxyImpl) _then)
: super(_value, _then);
/// Create a copy of Proxy
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -2248,11 +2384,13 @@ class _$ProxyImpl implements _Proxy {
(identical(other.now, now) || other.now == now));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, name, type, now);
@JsonKey(ignore: true)
/// Create a copy of Proxy
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProxyImplCopyWith<_$ProxyImpl> get copyWith =>
@@ -2280,8 +2418,11 @@ abstract class _Proxy implements Proxy {
String get type;
@override
String? get now;
/// Create a copy of Proxy
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProxyImplCopyWith<_$ProxyImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -2296,8 +2437,12 @@ mixin _$HotKeyAction {
int? get key => throw _privateConstructorUsedError;
Set<KeyboardModifier> get modifiers => throw _privateConstructorUsedError;
/// Serializes this HotKeyAction to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of HotKeyAction
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$HotKeyActionCopyWith<HotKeyAction> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -2321,6 +2466,8 @@ class _$HotKeyActionCopyWithImpl<$Res, $Val extends HotKeyAction>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of HotKeyAction
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -2364,6 +2511,8 @@ class __$$HotKeyActionImplCopyWithImpl<$Res>
_$HotKeyActionImpl _value, $Res Function(_$HotKeyActionImpl) _then)
: super(_value, _then);
/// Create a copy of HotKeyAction
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -2429,12 +2578,14 @@ class _$HotKeyActionImpl implements _HotKeyAction {
.equals(other._modifiers, _modifiers));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, action, key,
const DeepCollectionEquality().hash(_modifiers));
@JsonKey(ignore: true)
/// Create a copy of HotKeyAction
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$HotKeyActionImplCopyWith<_$HotKeyActionImpl> get copyWith =>
@@ -2463,8 +2614,11 @@ abstract class _HotKeyAction implements HotKeyAction {
int? get key;
@override
Set<KeyboardModifier> get modifiers;
/// Create a copy of HotKeyAction
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$HotKeyActionImplCopyWith<_$HotKeyActionImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -2475,7 +2629,9 @@ mixin _$Field {
String get value => throw _privateConstructorUsedError;
Validator? get validator => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Field
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$FieldCopyWith<Field> get copyWith => throw _privateConstructorUsedError;
}
@@ -2497,6 +2653,8 @@ class _$FieldCopyWithImpl<$Res, $Val extends Field>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Field
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -2539,6 +2697,8 @@ class __$$FieldImplCopyWithImpl<$Res>
_$FieldImpl _value, $Res Function(_$FieldImpl) _then)
: super(_value, _then);
/// Create a copy of Field
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -2594,7 +2754,9 @@ class _$FieldImpl implements _Field {
@override
int get hashCode => Object.hash(runtimeType, label, value, validator);
@JsonKey(ignore: true)
/// Create a copy of Field
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$FieldImplCopyWith<_$FieldImpl> get copyWith =>
@@ -2613,8 +2775,11 @@ abstract class _Field implements Field {
String get value;
@override
Validator? get validator;
/// Create a copy of Field
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$FieldImplCopyWith<_$FieldImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -23,7 +23,6 @@ mixin _$AppSetting {
String? get locale => throw _privateConstructorUsedError;
bool get onlyProxy => throw _privateConstructorUsedError;
bool get autoLaunch => throw _privateConstructorUsedError;
bool get adminAutoLaunch => throw _privateConstructorUsedError;
bool get silentLaunch => throw _privateConstructorUsedError;
bool get autoRun => throw _privateConstructorUsedError;
bool get openLogs => throw _privateConstructorUsedError;
@@ -36,8 +35,12 @@ mixin _$AppSetting {
bool get minimizeOnExit => throw _privateConstructorUsedError;
bool get hidden => throw _privateConstructorUsedError;
/// Serializes this AppSetting to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of AppSetting
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$AppSettingCopyWith<AppSetting> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -52,7 +55,6 @@ abstract class $AppSettingCopyWith<$Res> {
{String? locale,
bool onlyProxy,
bool autoLaunch,
bool adminAutoLaunch,
bool silentLaunch,
bool autoRun,
bool openLogs,
@@ -76,13 +78,14 @@ class _$AppSettingCopyWithImpl<$Res, $Val extends AppSetting>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of AppSetting
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? locale = freezed,
Object? onlyProxy = null,
Object? autoLaunch = null,
Object? adminAutoLaunch = null,
Object? silentLaunch = null,
Object? autoRun = null,
Object? openLogs = null,
@@ -108,10 +111,6 @@ class _$AppSettingCopyWithImpl<$Res, $Val extends AppSetting>
? _value.autoLaunch
: autoLaunch // ignore: cast_nullable_to_non_nullable
as bool,
adminAutoLaunch: null == adminAutoLaunch
? _value.adminAutoLaunch
: adminAutoLaunch // ignore: cast_nullable_to_non_nullable
as bool,
silentLaunch: null == silentLaunch
? _value.silentLaunch
: silentLaunch // ignore: cast_nullable_to_non_nullable
@@ -172,7 +171,6 @@ abstract class _$$AppSettingImplCopyWith<$Res>
{String? locale,
bool onlyProxy,
bool autoLaunch,
bool adminAutoLaunch,
bool silentLaunch,
bool autoRun,
bool openLogs,
@@ -194,13 +192,14 @@ class __$$AppSettingImplCopyWithImpl<$Res>
_$AppSettingImpl _value, $Res Function(_$AppSettingImpl) _then)
: super(_value, _then);
/// Create a copy of AppSetting
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? locale = freezed,
Object? onlyProxy = null,
Object? autoLaunch = null,
Object? adminAutoLaunch = null,
Object? silentLaunch = null,
Object? autoRun = null,
Object? openLogs = null,
@@ -226,10 +225,6 @@ class __$$AppSettingImplCopyWithImpl<$Res>
? _value.autoLaunch
: autoLaunch // ignore: cast_nullable_to_non_nullable
as bool,
adminAutoLaunch: null == adminAutoLaunch
? _value.adminAutoLaunch
: adminAutoLaunch // ignore: cast_nullable_to_non_nullable
as bool,
silentLaunch: null == silentLaunch
? _value.silentLaunch
: silentLaunch // ignore: cast_nullable_to_non_nullable
@@ -285,7 +280,6 @@ class _$AppSettingImpl implements _AppSetting {
{this.locale,
this.onlyProxy = false,
this.autoLaunch = false,
this.adminAutoLaunch = false,
this.silentLaunch = false,
this.autoRun = false,
this.openLogs = false,
@@ -311,9 +305,6 @@ class _$AppSettingImpl implements _AppSetting {
final bool autoLaunch;
@override
@JsonKey()
final bool adminAutoLaunch;
@override
@JsonKey()
final bool silentLaunch;
@override
@JsonKey()
@@ -348,7 +339,7 @@ class _$AppSettingImpl implements _AppSetting {
@override
String toString() {
return 'AppSetting(locale: $locale, onlyProxy: $onlyProxy, autoLaunch: $autoLaunch, adminAutoLaunch: $adminAutoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, minimizeOnExit: $minimizeOnExit, hidden: $hidden)';
return 'AppSetting(locale: $locale, onlyProxy: $onlyProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, minimizeOnExit: $minimizeOnExit, hidden: $hidden)';
}
@override
@@ -361,8 +352,6 @@ class _$AppSettingImpl implements _AppSetting {
other.onlyProxy == onlyProxy) &&
(identical(other.autoLaunch, autoLaunch) ||
other.autoLaunch == autoLaunch) &&
(identical(other.adminAutoLaunch, adminAutoLaunch) ||
other.adminAutoLaunch == adminAutoLaunch) &&
(identical(other.silentLaunch, silentLaunch) ||
other.silentLaunch == silentLaunch) &&
(identical(other.autoRun, autoRun) || other.autoRun == autoRun) &&
@@ -384,14 +373,13 @@ class _$AppSettingImpl implements _AppSetting {
(identical(other.hidden, hidden) || other.hidden == hidden));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
locale,
onlyProxy,
autoLaunch,
adminAutoLaunch,
silentLaunch,
autoRun,
openLogs,
@@ -404,7 +392,9 @@ class _$AppSettingImpl implements _AppSetting {
minimizeOnExit,
hidden);
@JsonKey(ignore: true)
/// Create a copy of AppSetting
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$AppSettingImplCopyWith<_$AppSettingImpl> get copyWith =>
@@ -423,7 +413,6 @@ abstract class _AppSetting implements AppSetting {
{final String? locale,
final bool onlyProxy,
final bool autoLaunch,
final bool adminAutoLaunch,
final bool silentLaunch,
final bool autoRun,
final bool openLogs,
@@ -446,8 +435,6 @@ abstract class _AppSetting implements AppSetting {
@override
bool get autoLaunch;
@override
bool get adminAutoLaunch;
@override
bool get silentLaunch;
@override
bool get autoRun;
@@ -469,8 +456,11 @@ abstract class _AppSetting implements AppSetting {
bool get minimizeOnExit;
@override
bool get hidden;
/// Create a copy of AppSetting
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$AppSettingImplCopyWith<_$AppSettingImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -487,8 +477,12 @@ mixin _$AccessControl {
AccessSortType get sort => throw _privateConstructorUsedError;
bool get isFilterSystemApp => throw _privateConstructorUsedError;
/// Serializes this AccessControl to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of AccessControl
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$AccessControlCopyWith<AccessControl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -517,6 +511,8 @@ class _$AccessControlCopyWithImpl<$Res, $Val extends AccessControl>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of AccessControl
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -575,6 +571,8 @@ class __$$AccessControlImplCopyWithImpl<$Res>
_$AccessControlImpl _value, $Res Function(_$AccessControlImpl) _then)
: super(_value, _then);
/// Create a copy of AccessControl
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -672,7 +670,7 @@ class _$AccessControlImpl implements _AccessControl {
other.isFilterSystemApp == isFilterSystemApp));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
@@ -682,7 +680,9 @@ class _$AccessControlImpl implements _AccessControl {
sort,
isFilterSystemApp);
@JsonKey(ignore: true)
/// Create a copy of AccessControl
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$AccessControlImplCopyWith<_$AccessControlImpl> get copyWith =>
@@ -717,8 +717,11 @@ abstract class _AccessControl implements AccessControl {
AccessSortType get sort;
@override
bool get isFilterSystemApp;
/// Create a copy of AccessControl
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$AccessControlImplCopyWith<_$AccessControlImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -734,8 +737,12 @@ mixin _$WindowProps {
double? get top => throw _privateConstructorUsedError;
double? get left => throw _privateConstructorUsedError;
/// Serializes this WindowProps to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of WindowProps
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$WindowPropsCopyWith<WindowProps> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -759,6 +766,8 @@ class _$WindowPropsCopyWithImpl<$Res, $Val extends WindowProps>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of WindowProps
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -807,6 +816,8 @@ class __$$WindowPropsImplCopyWithImpl<$Res>
_$WindowPropsImpl _value, $Res Function(_$WindowPropsImpl) _then)
: super(_value, _then);
/// Create a copy of WindowProps
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -872,11 +883,13 @@ class _$WindowPropsImpl implements _WindowProps {
(identical(other.left, left) || other.left == left));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, width, height, top, left);
@JsonKey(ignore: true)
/// Create a copy of WindowProps
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$WindowPropsImplCopyWith<_$WindowPropsImpl> get copyWith =>
@@ -908,8 +921,11 @@ abstract class _WindowProps implements WindowProps {
double? get top;
@override
double? get left;
/// Create a copy of WindowProps
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$WindowPropsImplCopyWith<_$WindowPropsImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -924,10 +940,13 @@ mixin _$VpnProps {
bool get systemProxy => throw _privateConstructorUsedError;
bool get ipv6 => throw _privateConstructorUsedError;
bool get allowBypass => throw _privateConstructorUsedError;
List<String> get bypassDomain => throw _privateConstructorUsedError;
/// Serializes this VpnProps to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of VpnProps
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$VpnPropsCopyWith<VpnProps> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -937,12 +956,7 @@ abstract class $VpnPropsCopyWith<$Res> {
factory $VpnPropsCopyWith(VpnProps value, $Res Function(VpnProps) then) =
_$VpnPropsCopyWithImpl<$Res, VpnProps>;
@useResult
$Res call(
{bool enable,
bool systemProxy,
bool ipv6,
bool allowBypass,
List<String> bypassDomain});
$Res call({bool enable, bool systemProxy, bool ipv6, bool allowBypass});
}
/// @nodoc
@@ -955,6 +969,8 @@ class _$VpnPropsCopyWithImpl<$Res, $Val extends VpnProps>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of VpnProps
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -962,7 +978,6 @@ class _$VpnPropsCopyWithImpl<$Res, $Val extends VpnProps>
Object? systemProxy = null,
Object? ipv6 = null,
Object? allowBypass = null,
Object? bypassDomain = null,
}) {
return _then(_value.copyWith(
enable: null == enable
@@ -981,10 +996,6 @@ class _$VpnPropsCopyWithImpl<$Res, $Val extends VpnProps>
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
as bool,
bypassDomain: null == bypassDomain
? _value.bypassDomain
: bypassDomain // ignore: cast_nullable_to_non_nullable
as List<String>,
) as $Val);
}
}
@@ -997,12 +1008,7 @@ abstract class _$$VpnPropsImplCopyWith<$Res>
__$$VpnPropsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{bool enable,
bool systemProxy,
bool ipv6,
bool allowBypass,
List<String> bypassDomain});
$Res call({bool enable, bool systemProxy, bool ipv6, bool allowBypass});
}
/// @nodoc
@@ -1013,6 +1019,8 @@ class __$$VpnPropsImplCopyWithImpl<$Res>
_$VpnPropsImpl _value, $Res Function(_$VpnPropsImpl) _then)
: super(_value, _then);
/// Create a copy of VpnProps
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1020,7 +1028,6 @@ class __$$VpnPropsImplCopyWithImpl<$Res>
Object? systemProxy = null,
Object? ipv6 = null,
Object? allowBypass = null,
Object? bypassDomain = null,
}) {
return _then(_$VpnPropsImpl(
enable: null == enable
@@ -1039,10 +1046,6 @@ class __$$VpnPropsImplCopyWithImpl<$Res>
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
as bool,
bypassDomain: null == bypassDomain
? _value._bypassDomain
: bypassDomain // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
@@ -1054,9 +1057,7 @@ class _$VpnPropsImpl implements _VpnProps {
{this.enable = true,
this.systemProxy = true,
this.ipv6 = false,
this.allowBypass = true,
final List<String> bypassDomain = defaultBypassDomain})
: _bypassDomain = bypassDomain;
this.allowBypass = true});
factory _$VpnPropsImpl.fromJson(Map<String, dynamic> json) =>
_$$VpnPropsImplFromJson(json);
@@ -1073,18 +1074,10 @@ class _$VpnPropsImpl implements _VpnProps {
@override
@JsonKey()
final bool allowBypass;
final List<String> _bypassDomain;
@override
@JsonKey()
List<String> get bypassDomain {
if (_bypassDomain is EqualUnmodifiableListView) return _bypassDomain;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_bypassDomain);
}
@override
String toString() {
return 'VpnProps(enable: $enable, systemProxy: $systemProxy, ipv6: $ipv6, allowBypass: $allowBypass, bypassDomain: $bypassDomain)';
return 'VpnProps(enable: $enable, systemProxy: $systemProxy, ipv6: $ipv6, allowBypass: $allowBypass)';
}
@override
@@ -1097,17 +1090,17 @@ class _$VpnPropsImpl implements _VpnProps {
other.systemProxy == systemProxy) &&
(identical(other.ipv6, ipv6) || other.ipv6 == ipv6) &&
(identical(other.allowBypass, allowBypass) ||
other.allowBypass == allowBypass) &&
const DeepCollectionEquality()
.equals(other._bypassDomain, _bypassDomain));
other.allowBypass == allowBypass));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, enable, systemProxy, ipv6,
allowBypass, const DeepCollectionEquality().hash(_bypassDomain));
int get hashCode =>
Object.hash(runtimeType, enable, systemProxy, ipv6, allowBypass);
@JsonKey(ignore: true)
/// Create a copy of VpnProps
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$VpnPropsImplCopyWith<_$VpnPropsImpl> get copyWith =>
@@ -1126,8 +1119,7 @@ abstract class _VpnProps implements VpnProps {
{final bool enable,
final bool systemProxy,
final bool ipv6,
final bool allowBypass,
final List<String> bypassDomain}) = _$VpnPropsImpl;
final bool allowBypass}) = _$VpnPropsImpl;
factory _VpnProps.fromJson(Map<String, dynamic> json) =
_$VpnPropsImpl.fromJson;
@@ -1140,149 +1132,192 @@ abstract class _VpnProps implements VpnProps {
bool get ipv6;
@override
bool get allowBypass;
/// Create a copy of VpnProps
/// with the given fields replaced by the non-null parameter values.
@override
List<String> get bypassDomain;
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$VpnPropsImplCopyWith<_$VpnPropsImpl> get copyWith =>
throw _privateConstructorUsedError;
}
DesktopProps _$DesktopPropsFromJson(Map<String, dynamic> json) {
return _DesktopProps.fromJson(json);
NetworkProps _$NetworkPropsFromJson(Map<String, dynamic> json) {
return _NetworkProps.fromJson(json);
}
/// @nodoc
mixin _$DesktopProps {
mixin _$NetworkProps {
bool get systemProxy => throw _privateConstructorUsedError;
List<String> get bypassDomain => throw _privateConstructorUsedError;
/// Serializes this NetworkProps to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$DesktopPropsCopyWith<DesktopProps> get copyWith =>
/// Create a copy of NetworkProps
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$NetworkPropsCopyWith<NetworkProps> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $DesktopPropsCopyWith<$Res> {
factory $DesktopPropsCopyWith(
DesktopProps value, $Res Function(DesktopProps) then) =
_$DesktopPropsCopyWithImpl<$Res, DesktopProps>;
abstract class $NetworkPropsCopyWith<$Res> {
factory $NetworkPropsCopyWith(
NetworkProps value, $Res Function(NetworkProps) then) =
_$NetworkPropsCopyWithImpl<$Res, NetworkProps>;
@useResult
$Res call({bool systemProxy});
$Res call({bool systemProxy, List<String> bypassDomain});
}
/// @nodoc
class _$DesktopPropsCopyWithImpl<$Res, $Val extends DesktopProps>
implements $DesktopPropsCopyWith<$Res> {
_$DesktopPropsCopyWithImpl(this._value, this._then);
class _$NetworkPropsCopyWithImpl<$Res, $Val extends NetworkProps>
implements $NetworkPropsCopyWith<$Res> {
_$NetworkPropsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of NetworkProps
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? systemProxy = null,
Object? bypassDomain = null,
}) {
return _then(_value.copyWith(
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
bypassDomain: null == bypassDomain
? _value.bypassDomain
: bypassDomain // ignore: cast_nullable_to_non_nullable
as List<String>,
) as $Val);
}
}
/// @nodoc
abstract class _$$DesktopPropsImplCopyWith<$Res>
implements $DesktopPropsCopyWith<$Res> {
factory _$$DesktopPropsImplCopyWith(
_$DesktopPropsImpl value, $Res Function(_$DesktopPropsImpl) then) =
__$$DesktopPropsImplCopyWithImpl<$Res>;
abstract class _$$NetworkPropsImplCopyWith<$Res>
implements $NetworkPropsCopyWith<$Res> {
factory _$$NetworkPropsImplCopyWith(
_$NetworkPropsImpl value, $Res Function(_$NetworkPropsImpl) then) =
__$$NetworkPropsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool systemProxy});
$Res call({bool systemProxy, List<String> bypassDomain});
}
/// @nodoc
class __$$DesktopPropsImplCopyWithImpl<$Res>
extends _$DesktopPropsCopyWithImpl<$Res, _$DesktopPropsImpl>
implements _$$DesktopPropsImplCopyWith<$Res> {
__$$DesktopPropsImplCopyWithImpl(
_$DesktopPropsImpl _value, $Res Function(_$DesktopPropsImpl) _then)
class __$$NetworkPropsImplCopyWithImpl<$Res>
extends _$NetworkPropsCopyWithImpl<$Res, _$NetworkPropsImpl>
implements _$$NetworkPropsImplCopyWith<$Res> {
__$$NetworkPropsImplCopyWithImpl(
_$NetworkPropsImpl _value, $Res Function(_$NetworkPropsImpl) _then)
: super(_value, _then);
/// Create a copy of NetworkProps
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? systemProxy = null,
Object? bypassDomain = null,
}) {
return _then(_$DesktopPropsImpl(
return _then(_$NetworkPropsImpl(
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
bypassDomain: null == bypassDomain
? _value._bypassDomain
: bypassDomain // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$DesktopPropsImpl implements _DesktopProps {
const _$DesktopPropsImpl({this.systemProxy = true});
class _$NetworkPropsImpl implements _NetworkProps {
const _$NetworkPropsImpl(
{this.systemProxy = true,
final List<String> bypassDomain = defaultBypassDomain})
: _bypassDomain = bypassDomain;
factory _$DesktopPropsImpl.fromJson(Map<String, dynamic> json) =>
_$$DesktopPropsImplFromJson(json);
factory _$NetworkPropsImpl.fromJson(Map<String, dynamic> json) =>
_$$NetworkPropsImplFromJson(json);
@override
@JsonKey()
final bool systemProxy;
final List<String> _bypassDomain;
@override
@JsonKey()
List<String> get bypassDomain {
if (_bypassDomain is EqualUnmodifiableListView) return _bypassDomain;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_bypassDomain);
}
@override
String toString() {
return 'DesktopProps(systemProxy: $systemProxy)';
return 'NetworkProps(systemProxy: $systemProxy, bypassDomain: $bypassDomain)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$DesktopPropsImpl &&
other is _$NetworkPropsImpl &&
(identical(other.systemProxy, systemProxy) ||
other.systemProxy == systemProxy));
other.systemProxy == systemProxy) &&
const DeepCollectionEquality()
.equals(other._bypassDomain, _bypassDomain));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, systemProxy);
int get hashCode => Object.hash(runtimeType, systemProxy,
const DeepCollectionEquality().hash(_bypassDomain));
@JsonKey(ignore: true)
/// Create a copy of NetworkProps
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$DesktopPropsImplCopyWith<_$DesktopPropsImpl> get copyWith =>
__$$DesktopPropsImplCopyWithImpl<_$DesktopPropsImpl>(this, _$identity);
_$$NetworkPropsImplCopyWith<_$NetworkPropsImpl> get copyWith =>
__$$NetworkPropsImplCopyWithImpl<_$NetworkPropsImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$DesktopPropsImplToJson(
return _$$NetworkPropsImplToJson(
this,
);
}
}
abstract class _DesktopProps implements DesktopProps {
const factory _DesktopProps({final bool systemProxy}) = _$DesktopPropsImpl;
abstract class _NetworkProps implements NetworkProps {
const factory _NetworkProps(
{final bool systemProxy,
final List<String> bypassDomain}) = _$NetworkPropsImpl;
factory _DesktopProps.fromJson(Map<String, dynamic> json) =
_$DesktopPropsImpl.fromJson;
factory _NetworkProps.fromJson(Map<String, dynamic> json) =
_$NetworkPropsImpl.fromJson;
@override
bool get systemProxy;
@override
@JsonKey(ignore: true)
_$$DesktopPropsImplCopyWith<_$DesktopPropsImpl> get copyWith =>
List<String> get bypassDomain;
/// Create a copy of NetworkProps
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$NetworkPropsImplCopyWith<_$NetworkPropsImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1299,8 +1334,12 @@ mixin _$ProxiesStyle {
ProxyCardType get cardType => throw _privateConstructorUsedError;
Map<String, String> get iconMap => throw _privateConstructorUsedError;
/// Serializes this ProxiesStyle to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of ProxiesStyle
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProxiesStyleCopyWith<ProxiesStyle> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1330,6 +1369,8 @@ class _$ProxiesStyleCopyWithImpl<$Res, $Val extends ProxiesStyle>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProxiesStyle
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1394,6 +1435,8 @@ class __$$ProxiesStyleImplCopyWithImpl<$Res>
_$ProxiesStyleImpl _value, $Res Function(_$ProxiesStyleImpl) _then)
: super(_value, _then);
/// Create a copy of ProxiesStyle
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1493,12 +1536,14 @@ class _$ProxiesStyleImpl implements _ProxiesStyle {
const DeepCollectionEquality().equals(other._iconMap, _iconMap));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, type, sortType, layout,
iconStyle, cardType, const DeepCollectionEquality().hash(_iconMap));
@JsonKey(ignore: true)
/// Create a copy of ProxiesStyle
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProxiesStyleImplCopyWith<_$ProxiesStyleImpl> get copyWith =>
@@ -1536,8 +1581,11 @@ abstract class _ProxiesStyle implements ProxiesStyle {
ProxyCardType get cardType;
@override
Map<String, String> get iconMap;
/// Create a copy of ProxiesStyle
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProxiesStyleImplCopyWith<_$ProxiesStyleImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1553,8 +1601,12 @@ mixin _$ThemeProps {
bool get prueBlack => throw _privateConstructorUsedError;
FontFamily get fontFamily => throw _privateConstructorUsedError;
/// Serializes this ThemeProps to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of ThemeProps
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ThemePropsCopyWith<ThemeProps> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1582,6 +1634,8 @@ class _$ThemePropsCopyWithImpl<$Res, $Val extends ThemeProps>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ThemeProps
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1634,6 +1688,8 @@ class __$$ThemePropsImplCopyWithImpl<$Res>
_$ThemePropsImpl _value, $Res Function(_$ThemePropsImpl) _then)
: super(_value, _then);
/// Create a copy of ThemeProps
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -1707,12 +1763,14 @@ class _$ThemePropsImpl implements _ThemeProps {
other.fontFamily == fontFamily));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, primaryColor, themeMode, prueBlack, fontFamily);
@JsonKey(ignore: true)
/// Create a copy of ThemeProps
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ThemePropsImplCopyWith<_$ThemePropsImpl> get copyWith =>
@@ -1744,8 +1802,11 @@ abstract class _ThemeProps implements ThemeProps {
bool get prueBlack;
@override
FontFamily get fontFamily;
/// Create a copy of ThemeProps
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ThemePropsImplCopyWith<_$ThemePropsImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -23,8 +23,8 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
..windowProps =
WindowProps.fromJson(json['windowProps'] as Map<String, dynamic>?)
..vpnProps = VpnProps.fromJson(json['vpnProps'] as Map<String, dynamic>?)
..desktopProps =
DesktopProps.fromJson(json['desktopProps'] as Map<String, dynamic>?)
..networkProps =
NetworkProps.fromJson(json['networkProps'] as Map<String, dynamic>?)
..overrideDns = json['overrideDns'] as bool? ?? false
..hotKeyActions = (json['hotKeyActions'] as List<dynamic>?)
?.map((e) => HotKeyAction.fromJson(e as Map<String, dynamic>))
@@ -44,7 +44,7 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'dav': instance.dav,
'windowProps': instance.windowProps,
'vpnProps': instance.vpnProps,
'desktopProps': instance.desktopProps,
'networkProps': instance.networkProps,
'overrideDns': instance.overrideDns,
'hotKeyActions': instance.hotKeyActions,
'proxiesStyle': instance.proxiesStyle,
@@ -56,7 +56,6 @@ _$AppSettingImpl _$$AppSettingImplFromJson(Map<String, dynamic> json) =>
locale: json['locale'] as String?,
onlyProxy: json['onlyProxy'] as bool? ?? false,
autoLaunch: json['autoLaunch'] as bool? ?? false,
adminAutoLaunch: json['adminAutoLaunch'] as bool? ?? false,
silentLaunch: json['silentLaunch'] as bool? ?? false,
autoRun: json['autoRun'] as bool? ?? false,
openLogs: json['openLogs'] as bool? ?? false,
@@ -75,7 +74,6 @@ Map<String, dynamic> _$$AppSettingImplToJson(_$AppSettingImpl instance) =>
'locale': instance.locale,
'onlyProxy': instance.onlyProxy,
'autoLaunch': instance.autoLaunch,
'adminAutoLaunch': instance.adminAutoLaunch,
'silentLaunch': instance.silentLaunch,
'autoRun': instance.autoRun,
'openLogs': instance.openLogs,
@@ -148,10 +146,6 @@ _$VpnPropsImpl _$$VpnPropsImplFromJson(Map<String, dynamic> json) =>
systemProxy: json['systemProxy'] as bool? ?? true,
ipv6: json['ipv6'] as bool? ?? false,
allowBypass: json['allowBypass'] as bool? ?? true,
bypassDomain: (json['bypassDomain'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
defaultBypassDomain,
);
Map<String, dynamic> _$$VpnPropsImplToJson(_$VpnPropsImpl instance) =>
@@ -160,17 +154,21 @@ Map<String, dynamic> _$$VpnPropsImplToJson(_$VpnPropsImpl instance) =>
'systemProxy': instance.systemProxy,
'ipv6': instance.ipv6,
'allowBypass': instance.allowBypass,
'bypassDomain': instance.bypassDomain,
};
_$DesktopPropsImpl _$$DesktopPropsImplFromJson(Map<String, dynamic> json) =>
_$DesktopPropsImpl(
_$NetworkPropsImpl _$$NetworkPropsImplFromJson(Map<String, dynamic> json) =>
_$NetworkPropsImpl(
systemProxy: json['systemProxy'] as bool? ?? true,
bypassDomain: (json['bypassDomain'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
defaultBypassDomain,
);
Map<String, dynamic> _$$DesktopPropsImplToJson(_$DesktopPropsImpl instance) =>
Map<String, dynamic> _$$NetworkPropsImplToJson(_$NetworkPropsImpl instance) =>
<String, dynamic>{
'systemProxy': instance.systemProxy,
'bypassDomain': instance.bypassDomain,
};
_$ProxiesStyleImpl _$$ProxiesStyleImplFromJson(Map<String, dynamic> json) =>

View File

@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of '../ffi.dart';
part of '../core.dart';
// **************************************************************************
// JsonSerializableGenerator
@@ -19,6 +19,9 @@ _$CoreStateImpl _$$CoreStateImplFromJson(Map<String, dynamic> json) =>
bypassDomain: (json['bypassDomain'] as List<dynamic>)
.map((e) => e as String)
.toList(),
routeAddress: (json['routeAddress'] as List<dynamic>)
.map((e) => e as String)
.toList(),
ipv6: json['ipv6'] as bool,
onlyProxy: json['onlyProxy'] as bool,
);
@@ -31,6 +34,7 @@ Map<String, dynamic> _$$CoreStateImplToJson(_$CoreStateImpl instance) =>
'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy,
'bypassDomain': instance.bypassDomain,
'routeAddress': instance.routeAddress,
'ipv6': instance.ipv6,
'onlyProxy': instance.onlyProxy,
};
@@ -51,6 +55,9 @@ _$AndroidVpnOptionsImpl _$$AndroidVpnOptionsImplFromJson(
.toList(),
ipv4Address: json['ipv4Address'] as String,
ipv6Address: json['ipv6Address'] as String,
routeAddress: (json['routeAddress'] as List<dynamic>)
.map((e) => e as String)
.toList(),
dnsServerAddress: json['dnsServerAddress'] as String,
);
@@ -65,6 +72,7 @@ Map<String, dynamic> _$$AndroidVpnOptionsImplToJson(
'bypassDomain': instance.bypassDomain,
'ipv4Address': instance.ipv4Address,
'ipv6Address': instance.ipv6Address,
'routeAddress': instance.routeAddress,
'dnsServerAddress': instance.dnsServerAddress,
};
@@ -180,13 +188,13 @@ Map<String, dynamic> _$$NowImplToJson(_$NowImpl instance) => <String, dynamic>{
'value': instance.value,
};
_$ProcessImpl _$$ProcessImplFromJson(Map<String, dynamic> json) =>
_$ProcessImpl(
_$ProcessDataImpl _$$ProcessDataImplFromJson(Map<String, dynamic> json) =>
_$ProcessDataImpl(
id: (json['id'] as num).toInt(),
metadata: Metadata.fromJson(json['metadata'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$ProcessImplToJson(_$ProcessImpl instance) =>
Map<String, dynamic> _$$ProcessDataImplToJson(_$ProcessDataImpl instance) =>
<String, dynamic>{
'id': instance.id,
'metadata': instance.metadata,
@@ -215,13 +223,33 @@ Map<String, dynamic> _$$ProcessMapItemImplToJson(
'value': instance.value,
};
_$ProviderSubscriptionInfoImpl _$$ProviderSubscriptionInfoImplFromJson(
Map<String, dynamic> json) =>
_$ProviderSubscriptionInfoImpl(
upload: (json['UPLOAD'] as num?)?.toInt() ?? 0,
download: (json['DOWNLOAD'] as num?)?.toInt() ?? 0,
total: (json['TOTAL'] as num?)?.toInt() ?? 0,
expire: (json['EXPIRE'] as num?)?.toInt() ?? 0,
);
Map<String, dynamic> _$$ProviderSubscriptionInfoImplToJson(
_$ProviderSubscriptionInfoImpl instance) =>
<String, dynamic>{
'UPLOAD': instance.upload,
'DOWNLOAD': instance.download,
'TOTAL': instance.total,
'EXPIRE': instance.expire,
};
_$ExternalProviderImpl _$$ExternalProviderImplFromJson(
Map<String, dynamic> json) =>
_$ExternalProviderImpl(
name: json['name'] as String,
type: json['type'] as String,
path: json['path'] as String,
path: json['path'] as String?,
count: (json['count'] as num).toInt(),
subscriptionInfo: subscriptionInfoFormCore(
json['subscription-info'] as Map<String, Object?>?),
isUpdating: json['isUpdating'] as bool? ?? false,
vehicleType: json['vehicle-type'] as String,
updateAt: DateTime.parse(json['update-at'] as String),
@@ -234,6 +262,7 @@ Map<String, dynamic> _$$ExternalProviderImplToJson(
'type': instance.type,
'path': instance.path,
'count': instance.count,
'subscription-info': instance.subscriptionInfo,
'isUpdating': instance.isUpdating,
'vehicle-type': instance.vehicleType,
'update-at': instance.updateAt.toIso8601String(),
@@ -260,3 +289,44 @@ Map<String, dynamic> _$$TunPropsImplToJson(_$TunPropsImpl instance) =>
'dns': instance.dns,
'dns6': instance.dns6,
};
_$ActionImpl _$$ActionImplFromJson(Map<String, dynamic> json) => _$ActionImpl(
method: $enumDecode(_$ActionMethodEnumMap, json['method']),
data: json['data'],
id: json['id'] as String,
);
Map<String, dynamic> _$$ActionImplToJson(_$ActionImpl instance) =>
<String, dynamic>{
'method': _$ActionMethodEnumMap[instance.method]!,
'data': instance.data,
'id': instance.id,
};
const _$ActionMethodEnumMap = {
ActionMethod.message: 'message',
ActionMethod.initClash: 'initClash',
ActionMethod.getIsInit: 'getIsInit',
ActionMethod.forceGc: 'forceGc',
ActionMethod.shutdown: 'shutdown',
ActionMethod.validateConfig: 'validateConfig',
ActionMethod.updateConfig: 'updateConfig',
ActionMethod.getProxies: 'getProxies',
ActionMethod.changeProxy: 'changeProxy',
ActionMethod.getTraffic: 'getTraffic',
ActionMethod.getTotalTraffic: 'getTotalTraffic',
ActionMethod.resetTraffic: 'resetTraffic',
ActionMethod.asyncTestDelay: 'asyncTestDelay',
ActionMethod.getConnections: 'getConnections',
ActionMethod.closeConnections: 'closeConnections',
ActionMethod.closeConnection: 'closeConnection',
ActionMethod.getExternalProviders: 'getExternalProviders',
ActionMethod.getExternalProvider: 'getExternalProvider',
ActionMethod.updateGeoData: 'updateGeoData',
ActionMethod.updateExternalProvider: 'updateExternalProvider',
ActionMethod.sideLoadExternalProvider: 'sideLoadExternalProvider',
ActionMethod.startLog: 'startLog',
ActionMethod.stopLog: 'stopLog',
ActionMethod.startListener: 'startListener',
ActionMethod.stopListener: 'stopListener',
};

View File

@@ -14,41 +14,48 @@ T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
UserInfo _$UserInfoFromJson(Map<String, dynamic> json) {
return _UserInfo.fromJson(json);
SubscriptionInfo _$SubscriptionInfoFromJson(Map<String, dynamic> json) {
return _SubscriptionInfo.fromJson(json);
}
/// @nodoc
mixin _$UserInfo {
mixin _$SubscriptionInfo {
int get upload => throw _privateConstructorUsedError;
int get download => throw _privateConstructorUsedError;
int get total => throw _privateConstructorUsedError;
int get expire => throw _privateConstructorUsedError;
/// Serializes this SubscriptionInfo to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$UserInfoCopyWith<UserInfo> get copyWith =>
/// Create a copy of SubscriptionInfo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SubscriptionInfoCopyWith<SubscriptionInfo> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $UserInfoCopyWith<$Res> {
factory $UserInfoCopyWith(UserInfo value, $Res Function(UserInfo) then) =
_$UserInfoCopyWithImpl<$Res, UserInfo>;
abstract class $SubscriptionInfoCopyWith<$Res> {
factory $SubscriptionInfoCopyWith(
SubscriptionInfo value, $Res Function(SubscriptionInfo) then) =
_$SubscriptionInfoCopyWithImpl<$Res, SubscriptionInfo>;
@useResult
$Res call({int upload, int download, int total, int expire});
}
/// @nodoc
class _$UserInfoCopyWithImpl<$Res, $Val extends UserInfo>
implements $UserInfoCopyWith<$Res> {
_$UserInfoCopyWithImpl(this._value, this._then);
class _$SubscriptionInfoCopyWithImpl<$Res, $Val extends SubscriptionInfo>
implements $SubscriptionInfoCopyWith<$Res> {
_$SubscriptionInfoCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SubscriptionInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -79,24 +86,26 @@ class _$UserInfoCopyWithImpl<$Res, $Val extends UserInfo>
}
/// @nodoc
abstract class _$$UserInfoImplCopyWith<$Res>
implements $UserInfoCopyWith<$Res> {
factory _$$UserInfoImplCopyWith(
_$UserInfoImpl value, $Res Function(_$UserInfoImpl) then) =
__$$UserInfoImplCopyWithImpl<$Res>;
abstract class _$$SubscriptionInfoImplCopyWith<$Res>
implements $SubscriptionInfoCopyWith<$Res> {
factory _$$SubscriptionInfoImplCopyWith(_$SubscriptionInfoImpl value,
$Res Function(_$SubscriptionInfoImpl) then) =
__$$SubscriptionInfoImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({int upload, int download, int total, int expire});
}
/// @nodoc
class __$$UserInfoImplCopyWithImpl<$Res>
extends _$UserInfoCopyWithImpl<$Res, _$UserInfoImpl>
implements _$$UserInfoImplCopyWith<$Res> {
__$$UserInfoImplCopyWithImpl(
_$UserInfoImpl _value, $Res Function(_$UserInfoImpl) _then)
class __$$SubscriptionInfoImplCopyWithImpl<$Res>
extends _$SubscriptionInfoCopyWithImpl<$Res, _$SubscriptionInfoImpl>
implements _$$SubscriptionInfoImplCopyWith<$Res> {
__$$SubscriptionInfoImplCopyWithImpl(_$SubscriptionInfoImpl _value,
$Res Function(_$SubscriptionInfoImpl) _then)
: super(_value, _then);
/// Create a copy of SubscriptionInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -105,7 +114,7 @@ class __$$UserInfoImplCopyWithImpl<$Res>
Object? total = null,
Object? expire = null,
}) {
return _then(_$UserInfoImpl(
return _then(_$SubscriptionInfoImpl(
upload: null == upload
? _value.upload
: upload // ignore: cast_nullable_to_non_nullable
@@ -128,12 +137,12 @@ class __$$UserInfoImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$UserInfoImpl implements _UserInfo {
const _$UserInfoImpl(
class _$SubscriptionInfoImpl implements _SubscriptionInfo {
const _$SubscriptionInfoImpl(
{this.upload = 0, this.download = 0, this.total = 0, this.expire = 0});
factory _$UserInfoImpl.fromJson(Map<String, dynamic> json) =>
_$$UserInfoImplFromJson(json);
factory _$SubscriptionInfoImpl.fromJson(Map<String, dynamic> json) =>
_$$SubscriptionInfoImplFromJson(json);
@override
@JsonKey()
@@ -150,14 +159,14 @@ class _$UserInfoImpl implements _UserInfo {
@override
String toString() {
return 'UserInfo(upload: $upload, download: $download, total: $total, expire: $expire)';
return 'SubscriptionInfo(upload: $upload, download: $download, total: $total, expire: $expire)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$UserInfoImpl &&
other is _$SubscriptionInfoImpl &&
(identical(other.upload, upload) || other.upload == upload) &&
(identical(other.download, download) ||
other.download == download) &&
@@ -165,33 +174,36 @@ class _$UserInfoImpl implements _UserInfo {
(identical(other.expire, expire) || other.expire == expire));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, upload, download, total, expire);
@JsonKey(ignore: true)
/// Create a copy of SubscriptionInfo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$UserInfoImplCopyWith<_$UserInfoImpl> get copyWith =>
__$$UserInfoImplCopyWithImpl<_$UserInfoImpl>(this, _$identity);
_$$SubscriptionInfoImplCopyWith<_$SubscriptionInfoImpl> get copyWith =>
__$$SubscriptionInfoImplCopyWithImpl<_$SubscriptionInfoImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$UserInfoImplToJson(
return _$$SubscriptionInfoImplToJson(
this,
);
}
}
abstract class _UserInfo implements UserInfo {
const factory _UserInfo(
abstract class _SubscriptionInfo implements SubscriptionInfo {
const factory _SubscriptionInfo(
{final int upload,
final int download,
final int total,
final int expire}) = _$UserInfoImpl;
final int expire}) = _$SubscriptionInfoImpl;
factory _UserInfo.fromJson(Map<String, dynamic> json) =
_$UserInfoImpl.fromJson;
factory _SubscriptionInfo.fromJson(Map<String, dynamic> json) =
_$SubscriptionInfoImpl.fromJson;
@override
int get upload;
@@ -201,9 +213,12 @@ abstract class _UserInfo implements UserInfo {
int get total;
@override
int get expire;
/// Create a copy of SubscriptionInfo
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
_$$UserInfoImplCopyWith<_$UserInfoImpl> get copyWith =>
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SubscriptionInfoImplCopyWith<_$SubscriptionInfoImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -219,15 +234,19 @@ mixin _$Profile {
String get url => throw _privateConstructorUsedError;
DateTime? get lastUpdateDate => throw _privateConstructorUsedError;
Duration get autoUpdateDuration => throw _privateConstructorUsedError;
UserInfo? get userInfo => throw _privateConstructorUsedError;
SubscriptionInfo? get subscriptionInfo => throw _privateConstructorUsedError;
bool get autoUpdate => throw _privateConstructorUsedError;
Map<String, String> get selectedMap => throw _privateConstructorUsedError;
Set<String> get unfoldSet => throw _privateConstructorUsedError;
@JsonKey(includeToJson: false, includeFromJson: false)
bool get isUpdating => throw _privateConstructorUsedError;
/// Serializes this Profile to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Profile
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProfileCopyWith<Profile> get copyWith => throw _privateConstructorUsedError;
}
@@ -243,13 +262,13 @@ abstract class $ProfileCopyWith<$Res> {
String url,
DateTime? lastUpdateDate,
Duration autoUpdateDuration,
UserInfo? userInfo,
SubscriptionInfo? subscriptionInfo,
bool autoUpdate,
Map<String, String> selectedMap,
Set<String> unfoldSet,
@JsonKey(includeToJson: false, includeFromJson: false) bool isUpdating});
$UserInfoCopyWith<$Res>? get userInfo;
$SubscriptionInfoCopyWith<$Res>? get subscriptionInfo;
}
/// @nodoc
@@ -262,6 +281,8 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Profile
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -271,7 +292,7 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
Object? url = null,
Object? lastUpdateDate = freezed,
Object? autoUpdateDuration = null,
Object? userInfo = freezed,
Object? subscriptionInfo = freezed,
Object? autoUpdate = null,
Object? selectedMap = null,
Object? unfoldSet = null,
@@ -302,10 +323,10 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
? _value.autoUpdateDuration
: autoUpdateDuration // ignore: cast_nullable_to_non_nullable
as Duration,
userInfo: freezed == userInfo
? _value.userInfo
: userInfo // ignore: cast_nullable_to_non_nullable
as UserInfo?,
subscriptionInfo: freezed == subscriptionInfo
? _value.subscriptionInfo
: subscriptionInfo // ignore: cast_nullable_to_non_nullable
as SubscriptionInfo?,
autoUpdate: null == autoUpdate
? _value.autoUpdate
: autoUpdate // ignore: cast_nullable_to_non_nullable
@@ -325,15 +346,17 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
) as $Val);
}
/// Create a copy of Profile
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$UserInfoCopyWith<$Res>? get userInfo {
if (_value.userInfo == null) {
$SubscriptionInfoCopyWith<$Res>? get subscriptionInfo {
if (_value.subscriptionInfo == null) {
return null;
}
return $UserInfoCopyWith<$Res>(_value.userInfo!, (value) {
return _then(_value.copyWith(userInfo: value) as $Val);
return $SubscriptionInfoCopyWith<$Res>(_value.subscriptionInfo!, (value) {
return _then(_value.copyWith(subscriptionInfo: value) as $Val);
});
}
}
@@ -352,14 +375,14 @@ abstract class _$$ProfileImplCopyWith<$Res> implements $ProfileCopyWith<$Res> {
String url,
DateTime? lastUpdateDate,
Duration autoUpdateDuration,
UserInfo? userInfo,
SubscriptionInfo? subscriptionInfo,
bool autoUpdate,
Map<String, String> selectedMap,
Set<String> unfoldSet,
@JsonKey(includeToJson: false, includeFromJson: false) bool isUpdating});
@override
$UserInfoCopyWith<$Res>? get userInfo;
$SubscriptionInfoCopyWith<$Res>? get subscriptionInfo;
}
/// @nodoc
@@ -370,6 +393,8 @@ class __$$ProfileImplCopyWithImpl<$Res>
_$ProfileImpl _value, $Res Function(_$ProfileImpl) _then)
: super(_value, _then);
/// Create a copy of Profile
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -379,7 +404,7 @@ class __$$ProfileImplCopyWithImpl<$Res>
Object? url = null,
Object? lastUpdateDate = freezed,
Object? autoUpdateDuration = null,
Object? userInfo = freezed,
Object? subscriptionInfo = freezed,
Object? autoUpdate = null,
Object? selectedMap = null,
Object? unfoldSet = null,
@@ -410,10 +435,10 @@ class __$$ProfileImplCopyWithImpl<$Res>
? _value.autoUpdateDuration
: autoUpdateDuration // ignore: cast_nullable_to_non_nullable
as Duration,
userInfo: freezed == userInfo
? _value.userInfo
: userInfo // ignore: cast_nullable_to_non_nullable
as UserInfo?,
subscriptionInfo: freezed == subscriptionInfo
? _value.subscriptionInfo
: subscriptionInfo // ignore: cast_nullable_to_non_nullable
as SubscriptionInfo?,
autoUpdate: null == autoUpdate
? _value.autoUpdate
: autoUpdate // ignore: cast_nullable_to_non_nullable
@@ -444,7 +469,7 @@ class _$ProfileImpl implements _Profile {
this.url = "",
this.lastUpdateDate,
required this.autoUpdateDuration,
this.userInfo,
this.subscriptionInfo,
this.autoUpdate = true,
final Map<String, String> selectedMap = const {},
final Set<String> unfoldSet = const {},
@@ -470,7 +495,7 @@ class _$ProfileImpl implements _Profile {
@override
final Duration autoUpdateDuration;
@override
final UserInfo? userInfo;
final SubscriptionInfo? subscriptionInfo;
@override
@JsonKey()
final bool autoUpdate;
@@ -498,7 +523,7 @@ class _$ProfileImpl implements _Profile {
@override
String toString() {
return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap, unfoldSet: $unfoldSet, isUpdating: $isUpdating)';
return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, subscriptionInfo: $subscriptionInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap, unfoldSet: $unfoldSet, isUpdating: $isUpdating)';
}
@override
@@ -515,8 +540,8 @@ class _$ProfileImpl implements _Profile {
other.lastUpdateDate == lastUpdateDate) &&
(identical(other.autoUpdateDuration, autoUpdateDuration) ||
other.autoUpdateDuration == autoUpdateDuration) &&
(identical(other.userInfo, userInfo) ||
other.userInfo == userInfo) &&
(identical(other.subscriptionInfo, subscriptionInfo) ||
other.subscriptionInfo == subscriptionInfo) &&
(identical(other.autoUpdate, autoUpdate) ||
other.autoUpdate == autoUpdate) &&
const DeepCollectionEquality()
@@ -527,7 +552,7 @@ class _$ProfileImpl implements _Profile {
other.isUpdating == isUpdating));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
@@ -537,13 +562,15 @@ class _$ProfileImpl implements _Profile {
url,
lastUpdateDate,
autoUpdateDuration,
userInfo,
subscriptionInfo,
autoUpdate,
const DeepCollectionEquality().hash(_selectedMap),
const DeepCollectionEquality().hash(_unfoldSet),
isUpdating);
@JsonKey(ignore: true)
/// Create a copy of Profile
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProfileImplCopyWith<_$ProfileImpl> get copyWith =>
@@ -565,7 +592,7 @@ abstract class _Profile implements Profile {
final String url,
final DateTime? lastUpdateDate,
required final Duration autoUpdateDuration,
final UserInfo? userInfo,
final SubscriptionInfo? subscriptionInfo,
final bool autoUpdate,
final Map<String, String> selectedMap,
final Set<String> unfoldSet,
@@ -587,7 +614,7 @@ abstract class _Profile implements Profile {
@override
Duration get autoUpdateDuration;
@override
UserInfo? get userInfo;
SubscriptionInfo? get subscriptionInfo;
@override
bool get autoUpdate;
@override
@@ -597,8 +624,11 @@ abstract class _Profile implements Profile {
@override
@JsonKey(includeToJson: false, includeFromJson: false)
bool get isUpdating;
/// Create a copy of Profile
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProfileImplCopyWith<_$ProfileImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -6,15 +6,17 @@ part of '../profile.dart';
// JsonSerializableGenerator
// **************************************************************************
_$UserInfoImpl _$$UserInfoImplFromJson(Map<String, dynamic> json) =>
_$UserInfoImpl(
_$SubscriptionInfoImpl _$$SubscriptionInfoImplFromJson(
Map<String, dynamic> json) =>
_$SubscriptionInfoImpl(
upload: (json['upload'] as num?)?.toInt() ?? 0,
download: (json['download'] as num?)?.toInt() ?? 0,
total: (json['total'] as num?)?.toInt() ?? 0,
expire: (json['expire'] as num?)?.toInt() ?? 0,
);
Map<String, dynamic> _$$UserInfoImplToJson(_$UserInfoImpl instance) =>
Map<String, dynamic> _$$SubscriptionInfoImplToJson(
_$SubscriptionInfoImpl instance) =>
<String, dynamic>{
'upload': instance.upload,
'download': instance.download,
@@ -33,9 +35,10 @@ _$ProfileImpl _$$ProfileImplFromJson(Map<String, dynamic> json) =>
: DateTime.parse(json['lastUpdateDate'] as String),
autoUpdateDuration:
Duration(microseconds: (json['autoUpdateDuration'] as num).toInt()),
userInfo: json['userInfo'] == null
subscriptionInfo: json['subscriptionInfo'] == null
? null
: UserInfo.fromJson(json['userInfo'] as Map<String, dynamic>),
: SubscriptionInfo.fromJson(
json['subscriptionInfo'] as Map<String, dynamic>),
autoUpdate: json['autoUpdate'] as bool? ?? true,
selectedMap: (json['selectedMap'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
@@ -55,7 +58,7 @@ Map<String, dynamic> _$$ProfileImplToJson(_$ProfileImpl instance) =>
'url': instance.url,
'lastUpdateDate': instance.lastUpdateDate?.toIso8601String(),
'autoUpdateDuration': instance.autoUpdateDuration.inMicroseconds,
'userInfo': instance.userInfo,
'subscriptionInfo': instance.subscriptionInfo,
'autoUpdate': instance.autoUpdate,
'selectedMap': instance.selectedMap,
'unfoldSet': instance.unfoldSet.toList(),

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
export 'app.dart';
export 'clash_config.dart';
export 'common.dart';
export 'config.dart';
export 'core.dart';
export 'profile.dart';
export 'ffi.dart';
export 'selector.dart';
export 'common.dart';

View File

@@ -4,37 +4,36 @@ import 'dart:io';
import 'dart:typed_data';
import 'package:fl_clash/clash/core.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'generated/profile.g.dart';
part 'generated/profile.freezed.dart';
part 'generated/profile.g.dart';
typedef SelectedMap = Map<String, String>;
@freezed
class UserInfo with _$UserInfo {
const factory UserInfo({
class SubscriptionInfo with _$SubscriptionInfo {
const factory SubscriptionInfo({
@Default(0) int upload,
@Default(0) int download,
@Default(0) int total,
@Default(0) int expire,
}) = _UserInfo;
}) = _SubscriptionInfo;
factory UserInfo.fromJson(Map<String, Object?> json) =>
_$UserInfoFromJson(json);
factory SubscriptionInfo.fromJson(Map<String, Object?> json) =>
_$SubscriptionInfoFromJson(json);
factory UserInfo.formHString(String? info) {
if (info == null) return const UserInfo();
factory SubscriptionInfo.formHString(String? info) {
if (info == null) return const SubscriptionInfo();
final list = info.split(";");
Map<String, int?> map = {};
for (final i in list) {
final keyValue = i.trim().split("=");
map[keyValue[0]] = int.tryParse(keyValue[1]);
}
return UserInfo(
return SubscriptionInfo(
upload: map["upload"] ?? 0,
download: map["download"] ?? 0,
total: map["total"] ?? 0,
@@ -52,7 +51,7 @@ class Profile with _$Profile {
@Default("") String url,
DateTime? lastUpdateDate,
required Duration autoUpdateDuration,
UserInfo? userInfo,
SubscriptionInfo? subscriptionInfo,
@Default(true) bool autoUpdate,
@Default({}) SelectedMap selectedMap,
@Default({}) Set<String> unfoldSet,
@@ -97,13 +96,28 @@ extension ProfileExtension on Profile {
return await File(profilePath!).exists();
}
Future<File> getFile() async {
final path = await appPath.getProfilePath(id);
final file = File(path!);
final isExists = await file.exists();
if (!isExists) {
await file.create(recursive: true);
}
return file;
}
Future<int> get profileLastModified async {
final file = await getFile();
return (await file.lastModified()).microsecondsSinceEpoch;
}
Future<Profile> update() async {
final response = await request.getFileResponseForUrl(url);
final disposition = response.headers.value("content-disposition");
final userinfo = response.headers.value('subscription-userinfo');
return await copyWith(
label: label ?? other.getFileNameForDisposition(disposition) ?? id,
userInfo: UserInfo.formHString(userinfo),
subscriptionInfo: SubscriptionInfo.formHString(userinfo),
).saveFile(response.data);
}
@@ -112,12 +126,7 @@ extension ProfileExtension on Profile {
if (message.isNotEmpty) {
throw message;
}
final path = await appPath.getProfilePath(id);
final file = File(path!);
final isExists = await file.exists();
if (!isExists) {
await file.create(recursive: true);
}
final file = await getFile();
await file.writeAsBytes(bytes);
return copyWith(lastUpdateDate: DateTime.now());
}
@@ -127,12 +136,7 @@ extension ProfileExtension on Profile {
if (message.isNotEmpty) {
throw message;
}
final path = await appPath.getProfilePath(id);
final file = File(path!);
final isExists = await file.exists();
if (!isExists) {
await file.create(recursive: true);
}
final file = await getFile();
await file.writeAsString(value);
return copyWith(lastUpdateDate: DateTime.now());
}

View File

@@ -63,13 +63,15 @@ class ApplicationSelectorState with _$ApplicationSelectorState {
class TrayState with _$TrayState {
const factory TrayState({
required Mode mode,
required int port,
required bool autoLaunch,
required bool adminAutoLaunch,
required bool systemProxy,
required bool tunEnable,
required bool isStart,
required String? locale,
required Brightness? brightness,
required List<Group> groups,
required SelectedMap map,
}) = _TrayState;
}
@@ -197,7 +199,6 @@ class ProxiesActionsState with _$ProxiesActionsState {
class AutoLaunchState with _$AutoLaunchState {
const factory AutoLaunchState({
required bool isAutoLaunch,
required bool isAdminAutoLaunch,
}) = _AutoLaunchState;
}
@@ -206,6 +207,7 @@ class ProxyState with _$ProxyState {
const factory ProxyState({
required bool isStart,
required bool systemProxy,
required List<String> bassDomain,
required int port,
}) = _ProxyState;
}
@@ -218,8 +220,6 @@ class HttpOverridesState with _$HttpOverridesState {
}) = _HttpOverridesState;
}
@freezed
class ClashConfigState with _$ClashConfigState {
const factory ClashConfigState({
@@ -251,4 +251,4 @@ class VPNState with _$VPNState {
required TunStack stack,
required VpnProps vpnProps,
}) = _VPNState;
}
}

View File

@@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
@@ -26,7 +27,7 @@ class Vpn {
clashCore.requestGc();
case "dnsChanged":
final dns = call.arguments as String;
clashCore.updateDns(dns);
clashLib?.updateDns(dns);
default:
throw MissingPluginException();
}
@@ -39,7 +40,7 @@ class Vpn {
}
Future<bool?> startVpn() async {
final options = clashCore.getAndroidVpnOptions();
final options = clashLib?.getAndroidVpnOptions();
return await methodChannel.invokeMethod<bool>("start", {
'data': json.encode(options),
});
@@ -53,7 +54,7 @@ class Vpn {
return await methodChannel.invokeMethod<bool?>("setProtect", {'fd': fd});
}
Future<String?> resolverProcess(Process process) async {
Future<String?> resolverProcess(ProcessData process) async {
return await methodChannel.invokeMethod<String>("resolverProcess", {
"data": json.encode(process),
});
@@ -78,7 +79,7 @@ class Vpn {
receiver!.listen((message) {
_handleServiceMessage(message);
});
clashCore.startTun(fd, receiver!.sendPort.nativePort);
clashLib?.startTun(fd, receiver!.sendPort.nativePort);
}
setServiceMessageHandler(ServiceMessageListener serviceMessageListener) {
@@ -91,7 +92,7 @@ class Vpn {
case ServiceMessageType.protect:
_serviceMessageHandler?.onProtect(Fd.fromJson(m.data));
case ServiceMessageType.process:
_serviceMessageHandler?.onProcess(Process.fromJson(m.data));
_serviceMessageHandler?.onProcess(ProcessData.fromJson(m.data));
case ServiceMessageType.started:
_serviceMessageHandler?.onStarted(m.data);
case ServiceMessageType.loaded:

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