Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ece8a48181 | ||
|
|
4b32a096dd | ||
|
|
04d6a928eb | ||
|
|
22e71ec6e1 | ||
|
|
6f1b07e26b | ||
|
|
1e3ab1c717 | ||
|
|
3a43dc2fe9 | ||
|
|
c94f64cf78 | ||
|
|
25764fc4d3 | ||
|
|
649dbb9886 | ||
|
|
e1dd616d74 | ||
|
|
c91d78b63f | ||
|
|
82767325e5 | ||
|
|
3f0f7f051b | ||
|
|
15c64327db | ||
|
|
09e35eb5dd | ||
|
|
77c4935106 | ||
|
|
e6da643186 | ||
|
|
61bd4e4549 | ||
|
|
a5f4d12748 | ||
|
|
9dc67f31a3 | ||
|
|
846ec9728f | ||
|
|
6e5f1b8e5f | ||
|
|
aca4a3e979 | ||
|
|
3783c3c650 | ||
|
|
01aed99002 | ||
|
|
9aec1e75e4 | ||
|
|
5dfb95a22d | ||
|
|
0f8cfa20b2 | ||
|
|
3933fd9d9e | ||
|
|
b7400f2ce8 | ||
|
|
d0ec580932 | ||
|
|
6bc0be9876 | ||
|
|
38221bcd10 | ||
|
|
bee2f8aa4f | ||
|
|
6cfcaa4edc | ||
|
|
3fdc69edd5 | ||
|
|
ed7ade0d72 | ||
|
|
621ddefc65 |
20
.github/workflows/build.yaml
vendored
20
.github/workflows/build.yaml
vendored
@@ -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 ]
|
||||
|
||||
63
.github/workflows/change.yaml
vendored
63
.github/workflows/change.yaml
vendored
@@ -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 }}
|
||||
58
CHANGELOG.md
58
CHANGELOG.md
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Submodule core/Clash.Meta updated: 317fd5ece0...f7c61f885c
32
core/action.go
Normal file
32
core/action.go
Normal 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
|
||||
}
|
||||
317
core/common.go
317
core/common.go
@@ -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
110
core/constant.go
Normal 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"
|
||||
)
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build cgo
|
||||
|
||||
package dart_bridge
|
||||
|
||||
/*
|
||||
|
||||
7
core/dart-bridge/lib_common.go
Normal file
7
core/dart-bridge/lib_common.go
Normal file
@@ -0,0 +1,7 @@
|
||||
//go:build !cgo
|
||||
|
||||
package dart_bridge
|
||||
|
||||
func SendToPort(port int64, msg string) bool {
|
||||
return false
|
||||
}
|
||||
20
core/dns.go
20
core/dns.go
@@ -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()
|
||||
}()
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
module core
|
||||
|
||||
go 1.21.0
|
||||
go 1.21
|
||||
|
||||
replace github.com/metacubex/mihomo => ./Clash.Meta
|
||||
|
||||
|
||||
457
core/hub.go
457
core/hub.go
@@ -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
182
core/lib.go
Normal 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
270
core/lib_android.go
Normal 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()
|
||||
}()
|
||||
}
|
||||
38
core/log.go
38
core/log.go
@@ -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
|
||||
}
|
||||
}
|
||||
11
core/main.go
11
core/main.go
@@ -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
8
core/main_cgo.go
Normal file
@@ -0,0 +1,8 @@
|
||||
//go:build cgo
|
||||
|
||||
package main
|
||||
|
||||
import "C"
|
||||
|
||||
func main() {
|
||||
}
|
||||
@@ -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
47
core/message_cgo.go
Normal 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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build android
|
||||
//go:build android && cgo
|
||||
|
||||
package platform
|
||||
|
||||
|
||||
@@ -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
162
core/server.go
Normal 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), ¶ms)
|
||||
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), ¶ms)
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
157
core/tun.go
157
core/tun.go
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export 'core.dart';
|
||||
export 'lib.dart';
|
||||
export 'message.dart';
|
||||
export 'service.dart';
|
||||
export 'message.dart';
|
||||
@@ -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
59
lib/clash/interface.dart
Normal 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
367
lib/clash/lib.dart
Normal 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;
|
||||
@@ -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._();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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
42
lib/common/future.dart
Normal 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');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
30
lib/common/lock.dart
Normal 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();
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
193
lib/common/tray.dart
Normal 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();
|
||||
@@ -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");
|
||||
|
||||
@@ -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"?>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -24,6 +24,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
title: appLocalizations.network,
|
||||
isScaffold: true,
|
||||
isBlur: false,
|
||||
extendPageWidth: 360,
|
||||
widget: const NetworkListView(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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(),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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": "复制环境变量"
|
||||
}
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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("白名单模式"),
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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?>;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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',
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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';
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user