Compare commits

..

1 Commits

Author SHA1 Message Date
chen08209
ddc07e9f29 Add android separates the core process
Support core status check and force restart

Optimize proxies page and access page

Update flutter and pub dependencies

Optimize more details
2025-09-17 17:12:11 +08:00
74 changed files with 844 additions and 1334 deletions

View File

@@ -27,9 +27,9 @@ jobs:
- platform: macos
os: macos-latest
arch: arm64
# - platform: windows
# os: windows-11-arm
# arch: arm64
- platform: windows
os: windows-11-arm
arch: arm64
- platform: linux
os: ubuntu-24.04-arm
arch: arm64

16
.gitignore vendored
View File

@@ -45,19 +45,11 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
/android/**/.cxx
/android/**/build
/android/common/**/.**/
/android/common/local.*
/android/core/**/includes/
/android/core/**/cmake-build-*/
/android/core/**/jniLibs/
#FlClash
#libclash
/libclash/
/android/app/src/main/jniLibs/
/services/helper/target
/macos/**/Package.resolved
devtools_options.yaml
#jniLibs
/android/app/src/main/jniLibs/

View File

@@ -1,29 +1,3 @@
## v0.8.89
- Fix some issues
- Optimize Windows service mode
- Update core
- Update changelog
## v0.8.88
- Add android separates the core process
- Support core status check and force restart
- Optimize proxies page and access page
- Update flutter and pub dependencies
- Update go version
- Optimize more details
- Update changelog
## v0.8.87
- Optimize desktop view

View File

@@ -2,7 +2,6 @@ package com.follow.clash
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import com.follow.clash.common.GlobalState
import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ServicePlugin
import com.follow.clash.plugins.TilePlugin
@@ -32,9 +31,6 @@ class MainActivity : FlutterActivity(),
}
override fun onDestroy() {
GlobalState.launch {
Service.setEventListener(null)
}
State.flutterEngine = null
super.onDestroy()
}

View File

@@ -3,7 +3,6 @@ package com.follow.clash
import com.follow.clash.common.ServiceDelegate
import com.follow.clash.common.formatString
import com.follow.clash.common.intent
import com.follow.clash.service.IAckInterface
import com.follow.clash.service.ICallbackInterface
import com.follow.clash.service.IEventInterface
import com.follow.clash.service.IRemoteInterface
@@ -45,11 +44,8 @@ object Service {
return delegate.useService {
it.invokeAction(
data, object : ICallbackInterface.Stub() {
override fun onResult(
result: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
) {
override fun onResult(result: ByteArray?, isSuccess: Boolean) {
res.add(result ?: byteArrayOf())
ack?.onAck()
if (isSuccess) {
cb(res.formatString())
}
@@ -59,29 +55,23 @@ object Service {
}
suspend fun setEventListener(
cb: ((result: String?) -> Unit)?
cb: (result: String?) -> Unit
): Result<Unit> {
val results = HashMap<String, MutableList<ByteArray>>()
return delegate.useService {
it.setEventListener(
when (cb != null) {
true -> object : IEventInterface.Stub() {
override fun onEvent(
id: String, data: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
) {
if (results[id] == null) {
results[id] = mutableListOf()
}
results[id]?.add(data ?: byteArrayOf())
ack?.onAck()
if (isSuccess) {
cb(results[id]?.formatString())
results.remove(id)
}
it.setEventListener(object : IEventInterface.Stub() {
override fun onEvent(
id: String, data: ByteArray?, isSuccess: Boolean
) {
if (results[id] == null) {
results[id] = mutableListOf()
}
results[id]?.add(data ?: byteArrayOf())
if (isSuccess) {
cb(results[id]?.formatString())
results.remove(id)
}
}
false -> null
})
}
}
@@ -104,11 +94,11 @@ object Service {
private suspend fun awaitIResultInterface(
block: (IResultInterface) -> Unit
): Long = suspendCancellableCoroutine { continuation ->
): Unit = suspendCancellableCoroutine { continuation ->
val callback = object : IResultInterface.Stub() {
override fun onResult(time: Long) {
override fun onResult() {
if (continuation.isActive) {
continuation.resume(time)
continuation.resume(Unit)
}
}
}
@@ -123,25 +113,19 @@ object Service {
}
suspend fun startService(options: VpnOptions, runTime: Long): Long {
return delegate.useService {
suspend fun startService(options: VpnOptions) {
delegate.useService {
awaitIResultInterface { callback ->
it.startService(options, runTime, callback)
it.startService(options, callback)
}
}.getOrNull() ?: 0L
}
}
suspend fun stopService(): Long {
return delegate.useService {
suspend fun stopService() {
delegate.useService {
awaitIResultInterface { callback ->
it.stopService(callback)
}
}.getOrNull() ?: 0L
}
suspend fun getRunTime(): Long {
return delegate.useService {
it.runTime
}.getOrNull() ?: 0L
}
}
}

View File

@@ -52,18 +52,6 @@ object State {
action?.invoke()
}
suspend fun handleSyncState() {
runLock.withLock {
Service.bind()
runTime = Service.getRunTime()
val runState = when (runTime == 0L) {
true -> RunState.STOP
false -> RunState.START
}
runStateFlow.tryEmit(runState)
}
}
suspend fun handleStartServiceAction() {
tilePlugin?.handleStart()
if (flutterEngine != null) {
@@ -92,7 +80,6 @@ object State {
suspend fun destroyServiceEngine() {
runLock.withLock {
GlobalState.log("Destroy service engine")
withContext(Dispatchers.Main) {
runCatching {
serviceFlutterEngine?.destroy()
@@ -104,12 +91,7 @@ object State {
suspend fun startServiceWithEngine() {
runLock.withLock {
if (runStateFlow.value == RunState.PENDING || runStateFlow.value == RunState.START) {
return
}
GlobalState.log("Create service engine")
withContext(Dispatchers.Main) {
serviceFlutterEngine?.destroy()
serviceFlutterEngine = FlutterEngine(GlobalState.application)
serviceFlutterEngine?.plugins?.add(ServicePlugin())
serviceFlutterEngine?.plugins?.add(AppPlugin())
@@ -137,7 +119,8 @@ object State {
return@launch
}
appPlugin?.prepare(options.enable) {
runTime = Service.startService(options, runTime)
Service.startService(options)
runTime = System.currentTimeMillis()
runStateFlow.tryEmit(RunState.START)
}
}
@@ -152,8 +135,9 @@ object State {
return@launch
}
runStateFlow.tryEmit(RunState.PENDING)
runTime = Service.stopService()
Service.stopService()
runStateFlow.tryEmit(RunState.STOP)
runTime = 0
}
destroyServiceEngine()
}

View File

@@ -31,7 +31,6 @@ class TileService : TileService() {
scope?.cancel()
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
scope?.launch {
State.handleSyncState()
State.runStateFlow.collect {
updateTile(it)
}
@@ -45,7 +44,8 @@ class TileService : TileService() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startActivityAndCollapse(pendingIntent)
} else {
@Suppress("DEPRECATION") startActivityAndCollapse(intent)
@Suppress("DEPRECATION")
startActivityAndCollapse(intent)
}
}

View File

@@ -38,7 +38,7 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) {
"init" -> {
handleInit(call, result)
handleInit(result)
}
"shutdown" -> {
@@ -131,16 +131,11 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
}
fun handleInit(call: MethodCall, result: MethodChannel.Result) {
fun handleInit(result: MethodChannel.Result) {
Service.bind()
launch {
val needSetEventListener = call.arguments<Boolean>() ?: false
when (needSetEventListener) {
true -> Service.setEventListener {
handleSendEvent(it)
}
false -> Service.setEventListener(null)
Service.setEventListener {
handleSendEvent(it)
}.onSuccess {
result.success("")
}.onFailure {
@@ -152,9 +147,6 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
private fun handleGetRunTime(result: MethodChannel.Result) {
launch {
State.handleSyncState()
result.success(State.runTime)
}
return result.success(State.runTime)
}
}

View File

@@ -220,6 +220,7 @@ val Long.formatBytes: String
fun String.chunkedForAidl(charset: Charset = Charsets.UTF_8): List<ByteArray> {
val allBytes = toByteArray(charset)
val total = allBytes.size
val maxBytes = when {
total <= 100 * 1024 -> total
total <= 1024 * 1024 -> 64 * 1024

View File

@@ -11,7 +11,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import java.util.concurrent.atomic.AtomicBoolean
@@ -45,10 +44,7 @@ class ServiceDelegate<T>(
job = null
_serviceState.value = null
job = launch {
runCatching {
GlobalState.application.bindServiceFlow<IBinder>(intent)
.collect { handleBind(it) }
}
GlobalState.application.bindServiceFlow<IBinder>(intent).collect { handleBind(it) }
}
}
}
@@ -60,9 +56,7 @@ class ServiceDelegate<T>(
withTimeout(timeoutMillis) {
val state = serviceState.filterNotNull().first()
state.first?.let {
withContext(Dispatchers.Default) {
block(it)
}
block(it)
} ?: throw Exception(state.second)
}
}

View File

@@ -41,13 +41,9 @@ Java_com_follow_clash_core_Core_invokeAction(JNIEnv *env, jobject thiz, jstring
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_setEventListener(JNIEnv *env, jobject thiz, jobject cb) {
if (cb != nullptr) {
const auto interface = new_global(cb);
setEventListener(interface);
} else {
setEventListener(nullptr);
}
Java_com_follow_clash_core_Core_setMessageCallback(JNIEnv *env, jobject thiz, jobject cb) {
const auto interface = new_global(cb);
setMessageCallback(interface);
}
extern "C"
@@ -173,7 +169,7 @@ Java_com_follow_clash_core_Core_updateDNS(JNIEnv *env, jobject thiz, jstring dns
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_setEventListener(JNIEnv *env, jobject thiz, jobject cb) {
Java_com_follow_clash_core_Core_setMessageCallback(JNIEnv *env, jobject thiz, jobject cb) {
}
extern "C"

View File

@@ -84,22 +84,18 @@ data object Core {
)
}
private external fun setEventListener(cb: InvokeInterface?)
private external fun setMessageCallback(cb: InvokeInterface)
fun callSetEventListener(
cb: ((result: String?) -> Unit)?
fun setMessageCallback(
cb: (result: String?) -> Unit
) {
when (cb != null) {
true -> setEventListener(
object : InvokeInterface {
override fun onResult(result: String?) {
cb(result)
}
},
)
false -> setEventListener(null)
}
setMessageCallback(
object : InvokeInterface {
override fun onResult(result: String?) {
cb(result)
}
},
)
}
external fun stopTun()

View File

@@ -25,7 +25,7 @@
android:process=":remote">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="proxy" />
android:value="service" />
</service>
<service

View File

@@ -1,8 +0,0 @@
// IAckInterface.aidl
package com.follow.clash.service;
import com.follow.clash.service.IAckInterface;
interface IAckInterface {
oneway void onAck();
}

View File

@@ -1,8 +1,6 @@
// ICallbackInterface.aidl
package com.follow.clash.service;
import com.follow.clash.service.IAckInterface;
interface ICallbackInterface {
oneway void onResult(in byte[] data,in boolean isSuccess, in IAckInterface ack);
void onResult(in byte[] data,in boolean isSuccess);
}

View File

@@ -1,8 +1,6 @@
// IEventInterface.aidl
package com.follow.clash.service;
import com.follow.clash.service.IAckInterface;
interface IEventInterface {
oneway void onEvent(in String id, in byte[] data,in boolean isSuccess, in IAckInterface ack);
void onEvent(in String id, in byte[] data,in boolean isSuccess);
}

View File

@@ -10,9 +10,8 @@ import com.follow.clash.service.models.NotificationParams;
interface IRemoteInterface {
void invokeAction(in String data, in ICallbackInterface callback);
void updateNotificationParams(in NotificationParams params);
void startService(in VpnOptions options, in long runTime, in IResultInterface result);
void startService(in VpnOptions options, in IResultInterface result);
void stopService(in IResultInterface result);
void setEventListener(in IEventInterface event);
void setCrashlytics(in boolean enable);
long getRunTime();
}

View File

@@ -2,5 +2,5 @@
package com.follow.clash.service;
interface IResultInterface {
oneway void onResult(in long runTime);
void onResult();
}

View File

@@ -8,22 +8,23 @@ import com.follow.clash.common.ServiceDelegate
import com.follow.clash.common.chunkedForAidl
import com.follow.clash.common.intent
import com.follow.clash.core.Core
import com.follow.clash.service.State.delegate
import com.follow.clash.service.State.intent
import com.follow.clash.service.State.runLock
import com.follow.clash.service.models.NotificationParams
import com.follow.clash.service.models.VpnOptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.UUID
import kotlin.coroutines.resume
class RemoteService : Service(),
CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
private var delegate: ServiceDelegate<IBaseService>? = null
private var intent: Intent? = null
val runLock = Mutex()
private fun handleStopService(result: IResultInterface) {
launch {
runLock.withLock {
@@ -31,8 +32,7 @@ class RemoteService : Service(),
service.stop()
delegate?.unbind()
}
State.runTime = 0
result.onResult(0)
result.onResult()
}
}
}
@@ -43,7 +43,7 @@ class RemoteService : Service(),
delegate = null
}
private fun handleStartService(runTime: Long, result: IResultInterface) {
private fun handleStartService(result: IResultInterface) {
launch {
runLock.withLock {
val nextIntent = when (State.options?.enable == true) {
@@ -65,11 +65,7 @@ class RemoteService : Service(),
delegate?.useService { service ->
service.start()
}
State.runTime = when (runTime != 0L) {
true -> runTime
false -> System.currentTimeMillis()
}
result.onResult(State.runTime)
result.onResult()
}
}
}
@@ -77,22 +73,11 @@ class RemoteService : Service(),
private val binder = object : IRemoteInterface.Stub() {
override fun invokeAction(data: String, callback: ICallbackInterface) {
Core.invokeAction(data) {
launch {
runCatching {
val chunks = it?.chunkedForAidl() ?: listOf()
for ((index, chunk) in chunks.withIndex()) {
suspendCancellableCoroutine { cont ->
callback.onResult(
chunk,
index == chunks.lastIndex,
object : IAckInterface.Stub() {
override fun onAck() {
cont.resume(Unit)
}
},
)
}
}
runCatching {
val chunks = it?.chunkedForAidl() ?: listOf()
val totalSize = chunks.size
chunks.forEachIndexed { index, chunk ->
callback.onResult(chunk, totalSize - 1 == index)
}
}
}
@@ -102,65 +87,37 @@ class RemoteService : Service(),
State.notificationParamsFlow.tryEmit(params)
}
override fun startService(
options: VpnOptions,
runtime: Long,
result: IResultInterface,
) {
State.options = options
handleStartService(runtime, result)
handleStartService(result)
}
override fun stopService(result: IResultInterface) {
handleStopService(result)
}
override fun setEventListener(eventListener: IEventInterface?) {
GlobalState.log("RemoveEventListener ${eventListener == null}")
when (eventListener != null) {
true -> Core.callSetEventListener {
launch {
runCatching {
val id = UUID.randomUUID().toString()
val chunks = it?.chunkedForAidl() ?: listOf()
for ((index, chunk) in chunks.withIndex()) {
suspendCancellableCoroutine { cont ->
eventListener.onEvent(
id,
chunk,
index == chunks.lastIndex,
object : IAckInterface.Stub() {
override fun onAck() {
cont.resume(Unit)
}
},
)
}
}
}
override fun setEventListener(event: IEventInterface) {
Core.setMessageCallback {
runCatching {
val id = UUID.randomUUID().toString()
val chunks = it?.chunkedForAidl() ?: listOf()
val totalSize = chunks.size
chunks.forEachIndexed { index, chunk ->
event.onEvent(id, chunk, totalSize - 1 == index)
}
}
false -> Core.callSetEventListener(null)
}
}
override fun setCrashlytics(enable: Boolean) {
GlobalState.setCrashlytics(enable)
}
override fun getRunTime(): Long {
return State.runTime
}
}
override fun onBind(intent: Intent?): IBinder {
return binder
}
override fun onDestroy() {
GlobalState.log("Remote service destroy")
super.onDestroy()
}
}

View File

@@ -1,22 +1,12 @@
package com.follow.clash.service
import android.content.Intent
import com.follow.clash.common.ServiceDelegate
import com.follow.clash.service.models.NotificationParams
import com.follow.clash.service.models.VpnOptions
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.sync.Mutex
object State {
var options: VpnOptions? = null
var notificationParamsFlow: MutableStateFlow<NotificationParams?> = MutableStateFlow(
NotificationParams()
)
val runLock = Mutex()
var runTime: Long = 0L
var delegate: ServiceDelegate<IBaseService>? = null
var intent: Intent? = null
}

View File

@@ -213,7 +213,6 @@ class VpnService : SystemVpnService(), IBaseService,
allowBypass()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && options.systemProxy) {
GlobalState.log("Open http proxy")
setHttpProxy(
ProxyInfo.buildDirectProxy(
"127.0.0.1", options.port, options.bypassDomain

View File

@@ -1,6 +1,5 @@
package com.follow.clash.service.models
import com.follow.clash.common.GlobalState
import com.follow.clash.common.formatBytes
import com.follow.clash.core.Core
import com.google.gson.Gson
@@ -18,8 +17,7 @@ fun Core.getSpeedTrafficText(onlyStatisticsProxy: Boolean): String {
val res = getTraffic(onlyStatisticsProxy)
val traffic = Gson().fromJson(res, Traffic::class.java)
return traffic.speedText
} catch (e: Exception) {
GlobalState.log(e.message + "")
} catch (_: Exception) {
return ""
}
}

View File

@@ -47,6 +47,9 @@ class NotificationModule(private val service: Service) : Module() {
private val scope = CoroutineScope(Dispatchers.Default)
override fun onInstall() {
State.notificationParamsFlow.value?.let {
update(it.extended)
}
scope.launch {
val screenFlow = service.receiveBroadcastFlow {
addAction(Intent.ACTION_SCREEN_ON)
@@ -66,12 +69,6 @@ class NotificationModule(private val service: Service) : Module() {
.collect { (params, _) ->
update(params!!)
}
State.notificationParamsFlow.value?.let {
update(it.extended)
} ?: run {
update(NotificationParams().extended)
}
}
}

View File

@@ -53,8 +53,8 @@ func handleAction(action *Action, result ActionResult) {
result.success(handleShutdown())
return
case validateConfigMethod:
path := action.Data.(string)
result.success(handleValidateConfig(path))
data := []byte(action.Data.(string))
result.success(handleValidateConfig(data))
return
case updateConfigMethod:
data := []byte(action.Data.(string))

View File

@@ -1,6 +1,6 @@
module core
go 1.25
go 1.20
replace github.com/metacubex/mihomo => ./Clash.Meta
@@ -18,8 +18,8 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/coreos/go-iptables v0.8.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/ebitengine/purego v0.9.0 // indirect
github.com/enfein/mieru/v3 v3.20.0 // indirect
github.com/ebitengine/purego v0.8.4 // indirect
github.com/enfein/mieru/v3 v3.19.1 // indirect
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
@@ -34,46 +34,43 @@ require (
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/gofrs/uuid/v5 v5.3.2 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/reedsolomon v1.12.3 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/amneziawg-go v0.0.0-20250902133113-a7f637c14281 // indirect
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a // indirect
github.com/metacubex/ascon v0.1.0 // indirect
github.com/metacubex/bart v0.24.0 // indirect
github.com/metacubex/bart v0.20.5 // indirect
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b // indirect
github.com/metacubex/blake3 v0.1.0 // indirect
github.com/metacubex/chacha v0.1.5 // indirect
github.com/metacubex/fswatch v0.1.1 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b // indirect
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/restls-client-go v0.1.7 // indirect
github.com/metacubex/sing v0.5.6 // indirect
github.com/metacubex/sing-mux v0.3.4 // indirect
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231 // indirect
github.com/metacubex/sing v0.5.5 // indirect
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac // indirect
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb // indirect
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.6 // indirect
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
github.com/metacubex/sing-tun v0.4.8 // indirect
github.com/metacubex/sing-vmess v0.2.4 // indirect
github.com/metacubex/sing-tun v0.4.7 // indirect
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 // indirect
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 // indirect
github.com/metacubex/utls v1.8.1 // indirect
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee // indirect
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 // indirect
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142 // indirect
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
@@ -81,19 +78,24 @@ require (
github.com/openacid/low v0.1.21 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/sagernet/cors v1.2.1 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/samber/lo v1.51.0 // indirect
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.4.0 // indirect

View File

@@ -1,7 +1,6 @@
github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg=
github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss=
github.com/RyuaNerin/testingutil v0.1.0/go.mod h1:yTqj6Ta/ycHMPJHRyO12Mz3VrvTloWOsy23WOZH19AA=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
@@ -23,16 +22,15 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/enfein/mieru/v3 v3.20.0 h1:1ob7pCIVSH5FYFAfYvim8isLW1vBOS4cFOUF9exJS38=
github.com/enfein/mieru/v3 v3.20.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/enfein/mieru/v3 v3.19.1 h1:19b9kgFC7oJXX9RLEO5Pi1gO6yek5cWlpK7IJVUoE8I=
github.com/enfein/mieru/v3 v3.19.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c/go.mod h1:ETASDWf/FmEb6Ysrtd1QhjNedUU/ZQxBCRLh60bQ/UI=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
@@ -46,7 +44,7 @@ github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hH
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
@@ -61,17 +59,16 @@ github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7spovjlY=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
@@ -81,22 +78,20 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/reedsolomon v1.12.3 h1:tzUznbfc3OFwJaTebv/QdhnFf2Xvb7gZ24XaHLBPmdc=
github.com/klauspost/reedsolomon v1.12.3/go.mod h1:3K5rXwABAvzGeR01r6pWZieUALXO/Tq7bFKGIb4m4WI=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/amneziawg-go v0.0.0-20250902133113-a7f637c14281 h1:09EM0sOLb2kfL0KETGhHujsBLB5iy5U/2yHRHsxf/pI=
github.com/metacubex/amneziawg-go v0.0.0-20250902133113-a7f637c14281/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a h1:c1QSGpacSeQdBdWcEKZKGuWLcqIG2wxHEygAcXuDwS4=
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM=
github.com/metacubex/ascon v0.1.0/go.mod h1:eV5oim4cVPPdEL8/EYaTZ0iIKARH9pnhAK/fcT5Kacc=
github.com/metacubex/bart v0.24.0 h1:EyNiPeVOlg0joSHTzi5oentI0j5M89utUq/5dd76pWM=
github.com/metacubex/bart v0.24.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM=
github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b h1:j7dadXD8I2KTmMt8jg1JcaP1ANL3JEObJPdANKcSYPY=
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b/go.mod h1:+WmP0VJZDkDszvpa83HzfUp6QzARl/IKkMorH4+nODw=
github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cqY=
@@ -107,10 +102,8 @@ github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQux
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b h1:z7JLKjugnQ1qvDOAD8yMA5C8AlJY3bG+VrrgRntRlUY=
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 h1:8JVlYuE8uSJAvmyCd4TjvDxs57xjb0WxEoaWafK5+qs=
@@ -120,34 +113,32 @@ github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFq
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.6 h1:mEPDCadsCj3DB8gn+t/EtposlYuALEkExa/LUguw6/c=
github.com/metacubex/sing v0.5.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0=
github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4=
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231 h1:dGvo7UahC/gYBQNBoictr14baJzBjAKUAorP63QFFtg=
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231/go.mod h1:B60FxaPHjR1SeQB0IiLrgwgvKsaoASfOWdiqhLjmMGA=
github.com/metacubex/sing v0.5.5 h1:m5U8iHvRAUxlme3FZlE/LPIGHjU8oMCUzXWGbQQAC1E=
github.com/metacubex/sing v0.5.5/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac h1:wDH/Jh/yqWbzPktqJP+Y1cUG8hchcrzKzUxJiSpnaQs=
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb h1:U/m3h8lp/j7i8zFgfvScLdZa1/Y8dd74oO7iZaQq80s=
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb/go.mod h1:B60FxaPHjR1SeQB0IiLrgwgvKsaoASfOWdiqhLjmMGA=
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
github.com/metacubex/sing-shadowsocks2 v0.2.6 h1:ZR1kYT0f0Vi64iQSS09OdhFfppiNkh7kjgRdMm0SB98=
github.com/metacubex/sing-shadowsocks2 v0.2.6/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.8 h1:3PyiUKWXYi37yHptXskzL1723O3OUdyt0Aej4XHVikM=
github.com/metacubex/sing-tun v0.4.8/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I=
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
github.com/metacubex/sing-tun v0.4.7 h1:ZDY/W+1c7PeWWKeKRyUo18fySF/TWjB0i5ui81Ar778=
github.com/metacubex/sing-tun v0.4.7/go.mod h1:xHecZRwBnKWe6zG9amAK9cXf91lF6blgjBqm+VvOrmU=
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 h1:WZepq4TOZa6WewB8tGAZrrL+bL2R2ivoBzuEgAeolWc=
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 h1:Ui+/2s5Qz0lSnDUBmEL12M5Oi/PzvFxGTNohm8ZcsmE=
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.1 h1:RW8GeCGWAegjV0HW5nw9DoqNoeGAXXeYUP6AysmRvx4=
github.com/metacubex/utls v1.8.1/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 h1:yN3mQ4cT9sPUciw/rO0Isc/8QlO86DB6g9SEMRgQ8Cw=
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142 h1:csEbKOzRAxJXffOeZnnS3/kA/F55JiTbKv5jcYqCXms=
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142/go.mod h1:67I3skhEY4Sya8f1YxELwWPoeQdXqZCrWNYLvq8gn2U=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E=
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49/go.mod h1:MBeEa9IVBphH7vc3LNtW6ZujVXFizotPo3OEiHQ+TNU=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
@@ -157,7 +148,6 @@ github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:U
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
@@ -170,6 +160,8 @@ github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
@@ -178,6 +170,8 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
@@ -197,8 +191,11 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
@@ -212,8 +209,8 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
@@ -243,18 +240,20 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
@@ -264,6 +263,7 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

View File

@@ -33,8 +33,6 @@ var (
)
func handleInitClash(paramsString string) bool {
runLock.Lock()
defer runLock.Unlock()
var params = InitParams{}
err := json.Unmarshal([]byte(paramsString), &params)
if err != nil {
@@ -85,9 +83,8 @@ func handleShutdown() bool {
return true
}
func handleValidateConfig(path string) string {
buf, err := readFile(path)
_, err = config.UnmarshalRawConfig(buf)
func handleValidateConfig(bytes []byte) string {
_, err := config.UnmarshalRawConfig(bytes)
if err != nil {
return err.Error()
}

View File

@@ -27,7 +27,7 @@ import (
"unsafe"
)
var eventListener unsafe.Pointer
var messageCallback unsafe.Pointer
type TunHandler struct {
listener *sing_tun.Listener
@@ -37,8 +37,6 @@ type TunHandler struct {
}
func (th *TunHandler) start(fd int, stack, address, dns string) {
runLock.Lock()
defer runLock.Unlock()
_ = th.limit.Acquire(context.TODO(), 4)
defer th.limit.Release(4)
th.initHook()
@@ -204,12 +202,12 @@ func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar
return true
}
//export setEventListener
func setEventListener(listener unsafe.Pointer) {
if eventListener != nil || listener == nil {
releaseObject(eventListener)
//export setMessageCallback
func setMessageCallback(callback unsafe.Pointer) {
if messageCallback != nil {
releaseObject(messageCallback)
}
eventListener = listener
messageCallback = callback
}
//export getTotalTraffic
@@ -227,12 +225,12 @@ func getTraffic(onlyStatisticsProxy bool) *C.char {
}
func sendMessage(message Message) {
if eventListener == nil {
if messageCallback == nil {
return
}
result := ActionResult{
Method: messageMethod,
callback: eventListener,
callback: messageCallback,
Data: message,
}
result.send()

View File

@@ -1,185 +1,16 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:fl_clash/common/common.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
class LocalImageCacheManager extends CacheManager {
static const key = 'ImageCaches';
static final LocalImageCacheManager _instance = LocalImageCacheManager._();
factory LocalImageCacheManager() {
return _instance;
}
LocalImageCacheManager._()
: super(Config(key, fileService: _LocalImageCacheFileService()));
}
class _LocalImageCacheFileService extends FileService {
_LocalImageCacheFileService();
@override
Future<FileServiceResponse> get(
String url, {
Map<String, String>? headers,
}) async {
final response = await request.dio.get<ResponseBody>(
url,
options: Options(headers: headers, responseType: ResponseType.stream),
);
return _LocalImageResponse(response);
}
}
class _LocalImageResponse implements FileServiceResponse {
_LocalImageResponse(this._response);
class LocalImageResponse extends HttpGetResponse {
LocalImageResponse(super.response);
final DateTime _receivedTime = DateTime.now();
final Response<ResponseBody> _response;
String? _header(String name) {
return _response.headers.value(name);
}
@override
int get statusCode => _response.statusCode ?? 0;
@override
Stream<List<int>> get content =>
_response.data!.stream.transform(uint8ListToListIntConverter);
@override
int? get contentLength => _response.data?.contentLength;
@override
DateTime get validTill {
var ageDuration = const Duration(days: 7);
final controlHeader = _header(HttpHeaders.cacheControlHeader);
if (controlHeader != null) {
final controlSettings = controlHeader.split(',');
for (final setting in controlSettings) {
final sanitizedSetting = setting.trim().toLowerCase();
if (sanitizedSetting == 'no-cache') {
ageDuration = Duration.zero;
}
if (sanitizedSetting.startsWith('max-age=')) {
final validSeconds =
int.tryParse(sanitizedSetting.split('=')[1]) ?? 0;
if (validSeconds > 0) {
ageDuration = Duration(seconds: validSeconds);
}
}
}
final minValidTill = _receivedTime.add(const Duration(days: 7));
if (super.validTill.isBefore(minValidTill)) {
return minValidTill;
}
if (ageDuration > const Duration(days: 7)) {
return _receivedTime.add(ageDuration);
}
return _receivedTime.add(const Duration(days: 7));
}
@override
String? get eTag => _header(HttpHeaders.etagHeader);
@override
String get fileExtension {
var fileExtension = '';
final contentTypeHeader = _header(HttpHeaders.contentTypeHeader);
if (contentTypeHeader != null) {
final contentType = ContentType.parse(contentTypeHeader);
fileExtension = contentType.fileExtension;
}
return fileExtension;
return super.validTill;
}
}
extension ContentTypeConverter on ContentType {
String get fileExtension => mimeTypes[mimeType] ?? '.$subType';
}
const mimeTypes = {
'application/vnd.android.package-archive': '.apk',
'application/epub+zip': '.epub',
'application/gzip': '.gz',
'application/java-archive': '.jar',
'application/json': '.json',
'application/ld+json': '.jsonld',
'application/msword': '.doc',
'application/octet-stream': '.bin',
'application/ogg': '.ogx',
'application/pdf': '.pdf',
'application/php': '.php',
'application/rtf': '.rtf',
'application/vnd.amazon.ebook': '.azw',
'application/vnd.apple.installer+xml': '.mpkg',
'application/vnd.mozilla.xul+xml': '.xul',
'application/vnd.ms-excel': '.xls',
'application/vnd.ms-fontobject': '.eot',
'application/vnd.ms-powerpoint': '.ppt',
'application/vnd.oasis.opendocument.presentation': '.odp',
'application/vnd.oasis.opendocument.spreadsheet': '.ods',
'application/vnd.oasis.opendocument.text': '.odt',
'application/vnd.openxmlformats-officedocument.presentationml.presentation':
'.pptx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
'.docx',
'application/vnd.rar': '.rar',
'application/vnd.visio': '.vsd',
'application/x-7z-compressed': '.7z',
'application/x-abiword': '.abw',
'application/x-bzip': '.bz',
'application/x-bzip2': '.bz2',
'application/x-csh': '.csh',
'application/x-freearc': '.arc',
'application/x-sh': '.sh',
'application/x-shockwave-flash': '.swf',
'application/x-tar': '.tar',
'application/xhtml+xml': '.xhtml',
'application/xml': '.xml',
'application/zip': '.zip',
'audio/3gpp': '.3gp',
'audio/3gpp2': '.3g2',
'audio/aac': '.aac',
'audio/x-aac': '.aac',
'audio/midi': '.midi',
'audio/x-midi': '.midi',
'audio/x-m4a': '.m4a',
'audio/m4a': '.m4a',
'audio/mpeg': '.mp3',
'audio/ogg': '.oga',
'audio/opus': '.opus',
'audio/wav': '.wav',
'audio/x-wav': '.wav',
'audio/webm': '.weba',
'font/otf': '.otf',
'font/ttf': '.ttf',
'font/woff': '.woff',
'font/woff2': '.woff2',
'image/bmp': '.bmp',
'image/gif': '.gif',
'image/jpeg': '.jpg',
'image/png': '.png',
'image/svg+xml': '.svg',
'image/tiff': '.tiff',
'image/vnd.microsoft.icon': '.ico',
'image/webp': '.webp',
'text/calendar': '.ics',
'text/css': '.css',
'text/csv': '.csv',
'text/html': '.html',
'text/javascript': '.js',
'text/plain': '.txt',
'text/xml': '.xml',
'video/3gpp': '.3gp',
'video/3gpp2': '.3g2',
'video/mp2t': '.ts',
'video/mpeg': '.mpeg',
'video/ogg': '.ogv',
'video/webm': '.webm',
'video/x-msvideo': '.avi',
'video/quicktime': '.mov',
};

View File

@@ -1,6 +1,5 @@
import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
class Debouncer {
@@ -37,25 +36,16 @@ class Throttler {
Function func, {
List<dynamic>? args,
Duration duration = const Duration(milliseconds: 600),
bool fire = false,
}) {
final timer = _operations[tag];
if (timer != null) {
return true;
}
if (fire) {
_operations[tag] = Timer(duration, () {
_operations[tag]?.cancel();
_operations.remove(tag);
Function.apply(func, args);
_operations[tag] = Timer(duration, () {
_operations[tag]?.cancel();
_operations.remove(tag);
});
} else {
_operations[tag] = Timer(duration, () {
Function.apply(func, args);
_operations[tag]?.cancel();
_operations.remove(tag);
});
}
});
return false;
}
@@ -69,7 +59,7 @@ Future<T> retry<T>({
required Future<T> Function() task,
int maxAttempts = 3,
required bool Function(T res) retryIf,
Duration delay = midDuration,
Duration delay = Duration.zero,
}) async {
int attempts = 0;
while (attempts < maxAttempts) {
@@ -79,7 +69,7 @@ Future<T> retry<T>({
}
attempts++;
}
throw 'retry error';
throw 'unknown error';
}
final debouncer = Debouncer();

View File

@@ -134,15 +134,11 @@ class CommonPageTransition extends StatefulWidget {
bool allowSnapshotting,
Widget? child,
) {
final CurvedAnimation animation = CurvedAnimation(
final Animation<Offset> delegatedPositionAnimation = CurvedAnimation(
parent: secondaryAnimation,
curve: Curves.linearToEaseOut,
reverseCurve: Curves.easeInToLinear,
);
final Animation<Offset> delegatedPositionAnimation = animation.drive(
_kMiddleLeftTween,
);
animation.dispose();
).drive(_kMiddleLeftTween);
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);

View File

@@ -71,11 +71,6 @@ class AppPath {
return join(homeDirPath, 'config.json');
}
Future<String> get validateFilePath async {
final homeDirPath = await appPath.homeDirPath;
return join(homeDirPath, 'temp', 'validate${utils.id}.yaml');
}
Future<String> get sharedPreferencesPath async {
final directory = await dataDir.future;
return join(directory.path, 'shared_preferences.json');

View File

@@ -1,4 +1,3 @@
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/cupertino.dart';
@@ -13,14 +12,14 @@ class CommonPrint {
return _instance!;
}
void log(String? text, {LogLevel logLevel = LogLevel.info}) {
void log(String? text) {
final payload = '[APP] $text';
debugPrint(payload);
if (!globalState.isInit) {
return;
}
globalState.appController.addLog(
Log.app(payload).copyWith(logLevel: logLevel),
Log.app(payload),
);
}
}

View File

@@ -73,13 +73,13 @@ class Request {
}
final Map<String, IpInfo Function(Map<String, dynamic>)> _ipInfoSources = {
'https://ipwho.is': IpInfo.fromIpWhoIsJson,
'https://api.myip.com': IpInfo.fromMyIpJson,
'https://ipapi.co/json': IpInfo.fromIpApiCoJson,
'https://ident.me/json': IpInfo.fromIdentMeJson,
'http://ip-api.com/json': IpInfo.fromIpAPIJson,
'https://api.ip.sb/geoip': IpInfo.fromIpSbJson,
'https://ipinfo.io/json': IpInfo.fromIpInfoIoJson,
'https://ipwho.is/': IpInfo.fromIpWhoIsJson,
'https://api.myip.com/': IpInfo.fromMyIpJson,
'https://ipapi.co/json/': IpInfo.fromIpApiCoJson,
'https://ident.me/json/': IpInfo.fromIdentMeJson,
'http://ip-api.com/json/': IpInfo.fromIpAPIJson,
'https://api.ip.sb/geoip/': IpInfo.fromIpSbJson,
'https://ipinfo.io/json/': IpInfo.fromIpInfoIoJson,
};
Future<Result<IpInfo?>> checkIp({CancelToken? cancelToken}) async {
@@ -92,13 +92,11 @@ class Request {
}
}
final future = dio
.get<Map<String, dynamic>>(
source.key,
cancelToken: cancelToken,
options: Options(responseType: ResponseType.json),
)
.timeout(const Duration(seconds: 10));
final future = dio.get<Map<String, dynamic>>(
source.key,
cancelToken: cancelToken,
options: Options(responseType: ResponseType.json),
);
future
.then((res) {
if (res.statusCode == HttpStatus.ok && res.data != null) {

View File

@@ -11,15 +11,16 @@ extension StringExtension on String {
}
dynamic get splitByMultipleSeparators {
final parts = split(
RegExp(r'[, ;]+'),
).where((part) => part.isNotEmpty).toList();
final parts =
split(RegExp(r'[, ;]+')).where((part) => part.isNotEmpty).toList();
return parts.length > 1 ? parts : this;
}
int compareToLower(String other) {
return toLowerCase().compareTo(other.toLowerCase());
return toLowerCase().compareTo(
other.toLowerCase(),
);
}
List<int> get encodeUtf16LeWithBom {
@@ -65,9 +66,9 @@ extension StringExtension on String {
return md5.convert(bytes).toString();
}
// bool containsToLower(String target) {
// return toLowerCase().contains(target);
// }
// bool containsToLower(String target) {
// return toLowerCase().contains(target);
// }
}
extension StringExtensionSafe on String? {

View File

@@ -181,31 +181,28 @@ class Windows {
calloc.free(argumentsPtr);
calloc.free(operationPtr);
commonPrint.log(
'windows runas: $command $arguments resultCode:$result',
logLevel: LogLevel.warning,
);
commonPrint.log('windows runas: $command $arguments resultCode:$result');
if (result <= 32) {
if (result < 42) {
return false;
}
return true;
}
// Future<void> _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<void> _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]);
@@ -231,18 +228,16 @@ class Windows {
return true;
}
await _killProcess(helperPort);
final command = [
'/c',
if (status == WindowsHelperServiceStatus.presence) ...[
'taskkill',
'/F',
'/IM',
'$appHelperService.exe'
' & '
'sc',
'sc',
'delete',
appHelperService,
'&',
'/force',
'&&',
],
'sc',
'create',
@@ -258,12 +253,8 @@ class Windows {
final res = runas('cmd.exe', command);
await Future.delayed(Duration(milliseconds: 300));
final retryStatus = await retry(
task: checkService,
retryIf: (status) => status == WindowsHelperServiceStatus.running,
delay: commonDuration,
);
return res && retryStatus == WindowsHelperServiceStatus.running;
return res;
}
Future<bool> registerTask(String appName) async {

View File

@@ -322,15 +322,12 @@ class Utils {
return SingleActivator(trigger, control: control, meta: !control);
}
FutureOr<T> handleWatch<T>({
required Function function,
required void Function(T data, int elapsedMilliseconds) onWatch,
}) async {
FutureOr<T> handleWatch<T>(Function function) async {
if (kDebugMode) {
final stopwatch = Stopwatch()..start();
final res = await function();
stopwatch.stop();
onWatch(res, stopwatch.elapsedMilliseconds);
commonPrint.log('耗时:${stopwatch.elapsedMilliseconds} ms');
return res;
}
return await function();

View File

@@ -353,7 +353,7 @@ class AppController {
try {
await updateProfile(profile);
} catch (e) {
commonPrint.log(e.toString(), logLevel: LogLevel.warning);
commonPrint.log(e.toString());
}
}
}
@@ -466,6 +466,9 @@ class AppController {
Map<String, dynamic>? data,
bool handleError = false,
}) async {
if (globalState.isPre) {
return;
}
if (data != null) {
final tagName = data['tag_name'];
final body = data['body'];
@@ -526,7 +529,6 @@ class AppController {
FlutterError.onError = (details) {
commonPrint.log(
'exception: ${details.exception} stack: ${details.stack}',
logLevel: LogLevel.warning,
);
};
updateTray(true);
@@ -549,12 +551,7 @@ class AppController {
Future<void> _connectCore() async {
_ref.read(coreStatusProvider.notifier).value = CoreStatus.connecting;
final result = await Future.wait([
coreController.preload(),
if (!globalState.isService) Future.delayed(Duration(milliseconds: 300)),
]);
final String message = result[0];
await Future.delayed(commonDuration);
final message = await coreController.preload();
if (message.isNotEmpty) {
_ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
if (context.mounted) {
@@ -959,7 +956,7 @@ class AppController {
final res = await futureFunction();
return res;
} catch (e) {
commonPrint.log('$title===> $e', logLevel: LogLevel.warning);
commonPrint.log('$futureFunction ===> $e');
if (realSilence) {
globalState.showNotifier(e.toString());
} else {

View File

@@ -71,37 +71,18 @@ class CoreController {
FutureOr<bool> get isInit => _interface.isInit;
Future<String> validateConfig(String data) async {
final path = await appPath.validateFilePath;
await globalState.genValidateFile(path, data);
final res = await _interface.validateConfig(path);
await File(path).delete();
return res;
}
Future<String> validateConfigFormBytes(Uint8List bytes) async {
final path = await appPath.validateFilePath;
await globalState.genValidateFileFormBytes(path, bytes);
final res = await _interface.validateConfig(path);
await File(path).delete();
return res;
FutureOr<String> validateConfig(String data) {
return _interface.validateConfig(data);
}
Future<String> updateConfig(UpdateParams updateParams) async {
return await _interface.updateConfig(updateParams);
}
Future<String> setupConfig(
ClashConfig clashConfig, {
VoidCallback? preloadInvoke,
}) async {
Future<String> setupConfig(ClashConfig clashConfig) async {
await globalState.genConfigFile(clashConfig);
final params = await globalState.getSetupParams();
final res = _interface.setupConfig(params);
if (preloadInvoke != null) {
preloadInvoke();
}
return res;
return await _interface.setupConfig(params);
}
Future<List<Group>> getProxiesGroups({

View File

@@ -5,7 +5,6 @@ import 'dart:isolate';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/foundation.dart';
mixin CoreInterface {
Future<bool> init(InitParams params);
@@ -18,7 +17,7 @@ mixin CoreInterface {
Future<bool> forceGc();
Future<String> validateConfig(String path);
Future<String> validateConfig(String data);
Future<Result> getConfig(String path);
@@ -87,18 +86,7 @@ abstract class CoreHandlerInterface with CoreInterface {
Duration? timeout,
}) async {
await connected;
if (kDebugMode) {
commonPrint.log('Invoke ${method.name} ${DateTime.now()} $data');
}
return utils.handleWatch(
function: () async {
return await invoke(method: method, data: data, timeout: timeout);
},
onWatch: (data, elapsedMilliseconds) {
commonPrint.log('Invoke ${method.name} ${elapsedMilliseconds}ms');
},
);
return invoke(method: method, data: data, timeout: timeout);
}
Future<T?> invoke<T>({
@@ -137,10 +125,10 @@ abstract class CoreHandlerInterface with CoreInterface {
}
@override
Future<String> validateConfig(String path) async {
Future<String> validateConfig(String data) async {
return await _invoke<String>(
method: ActionMethod.validateConfig,
data: path,
data: data,
) ??
'';
}
@@ -157,7 +145,7 @@ abstract class CoreHandlerInterface with CoreInterface {
@override
Future<Result> getConfig(String path) async {
return await _invoke<Result>(method: ActionMethod.getConfig, data: path) ??
Result.success({});
Result<Map<String, dynamic>>.success({});
}
@override

View File

@@ -38,29 +38,23 @@ class CoreService extends CoreHandlerInterface {
completer?.complete(data);
}
Future<void> _initServer() async {
final server = await retry(
task: () async {
try {
final address = !system.isWindows
? InternetAddress(unixSocketPath, type: InternetAddressType.unix)
: InternetAddress(localhost, type: InternetAddressType.IPv4);
await _deleteSocketFile();
final server = await ServerSocket.bind(address, 0, shared: true);
server.listen((socket) async {
await _attachSocket(socket);
});
return server;
} catch (_) {
return null;
void _initServer() {
runZonedGuarded(
() async {
final address = !system.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 _attachSocket(socket);
}
},
retryIf: (server) => server == null,
(error, stack) async {
commonPrint.log('Service error: $error');
},
);
if (server == null) {
exit(0);
}
_serverCompleter.complete(server);
}
Future<void> _attachSocket(Socket socket) async {
@@ -103,7 +97,7 @@ class CoreService extends CoreHandlerInterface {
_process?.stderr.listen((e) {
final error = utf8.decode(e);
if (error.isNotEmpty) {
commonPrint.log(error, logLevel: LogLevel.warning);
commonPrint.log(error);
}
});
await _socketCompleter.future;

View File

@@ -22,7 +22,6 @@ Future<void> main() async {
@pragma('vm:entry-point')
Future<void> _service(List<String> flags) async {
WidgetsFlutterBinding.ensureInitialized();
globalState.isService = true;
await globalState.init();
await coreController.preload();
tile?.addListener(
@@ -33,18 +32,16 @@ Future<void> _service(List<String> flags) async {
},
),
);
app?.tip(appLocalizations.startVpn);
final version = await system.version;
await coreController.init(version);
final clashConfig = globalState.config.patchClashConfig.copyWith.tun(
enable: false,
);
coreController.setupConfig(
clashConfig,
preloadInvoke: () {
globalState.handleStart();
},
);
Future(() async {
app?.tip(appLocalizations.startVpn);
final version = await system.version;
await coreController.init(version);
final clashConfig = globalState.config.patchClashConfig.copyWith.tun(
enable: false,
);
await globalState.handleStart();
await coreController.setupConfig(clashConfig);
});
}
@immutable

View File

@@ -101,7 +101,7 @@ class MessageManagerState extends State<MessageManager> {
_cancelMessage(messages.last.id);
},
child: Card(
shape: const RoundedSuperellipseBorder(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(14),
),
@@ -127,7 +127,6 @@ class MessageManagerState extends State<MessageManager> {
),
SizedBox(width: 16),
IconButton(
padding: EdgeInsets.all(2),
visualDensity: VisualDensity.compact,
onPressed: () {
_cancelMessage(messages.last.id);

View File

@@ -87,23 +87,13 @@ class ThemeManager extends ConsumerWidget {
top: padding.top > height * 0.3 ? 20.0 : padding.top,
),
),
child: Theme(
data: Theme.of(context).copyWith(
floatingActionButtonTheme: Theme.of(context).floatingActionButtonTheme
.copyWith(
shape: const RoundedSuperellipseBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0)),
),
),
),
child: LayoutBuilder(
builder: (_, container) {
globalState.appController.updateViewSize(
Size(container.maxWidth, container.maxHeight),
);
return _buildSystemUi(child);
},
),
child: LayoutBuilder(
builder: (_, container) {
globalState.appController.updateViewSize(
Size(container.maxWidth, container.maxHeight),
);
return _buildSystemUi(child);
},
),
);
}

View File

@@ -24,16 +24,11 @@ class _VpnContainerState extends ConsumerState<VpnManager> {
}
void showTip() {
throttler.call(
FunctionTag.vpnTip,
() {
if (ref.read(isStartProvider)) {
globalState.showNotifier(appLocalizations.vpnTip);
}
},
duration: const Duration(seconds: 6),
fire: true,
);
debouncer.call(FunctionTag.vpnTip, () {
if (ref.read(isStartProvider)) {
globalState.showNotifier(appLocalizations.vpnTip);
}
});
}
@override

View File

@@ -271,11 +271,9 @@ class AppIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: ShapeDecoration(
decoration: BoxDecoration(
color: context.colorScheme.surfaceContainerHighest,
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(14),
),
borderRadius: BorderRadius.circular(14),
),
padding: EdgeInsets.all(8),
child: Transform.translate(

View File

@@ -192,7 +192,7 @@ extension ActionResultExt on ActionResult {
if (code == ResultType.success) {
return Result.success(data);
} else {
return Result.error('$data');
return Result.error(data);
}
}
}

View File

@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
@@ -173,7 +174,7 @@ extension ProfileExtension on Profile {
}
Future<Profile> saveFile(Uint8List bytes) async {
final message = await coreController.validateConfigFormBytes(bytes);
final message = await coreController.validateConfig(utf8.decode(bytes));
if (message.isNotEmpty) {
throw message;
}
@@ -181,4 +182,14 @@ extension ProfileExtension on Profile {
await file.writeAsBytes(bytes);
return copyWith(lastUpdateDate: DateTime.now());
}
Future<Profile> saveFileWithString(String value) async {
final message = await coreController.validateConfig(value);
if (message.isNotEmpty) {
throw message;
}
final file = await getFile();
await file.writeAsString(value);
return copyWith(lastUpdateDate: DateTime.now());
}
}

View File

@@ -169,8 +169,8 @@ class ScannerOverlay extends CustomPainter {
final backgroundPath = Path()..addRect(Rect.largest);
final cutoutPath = Path()
..addRSuperellipse(
RSuperellipse.fromRectAndCorners(
..addRRect(
RRect.fromRectAndCorners(
scanWindow,
topLeft: Radius.circular(borderRadius),
topRight: Radius.circular(borderRadius),
@@ -195,7 +195,7 @@ class ScannerOverlay extends CustomPainter {
..style = PaintingStyle.stroke
..strokeWidth = 4.0;
final border = RSuperellipse.fromRectAndCorners(
final borderRect = RRect.fromRectAndCorners(
scanWindow,
topLeft: Radius.circular(borderRadius),
topRight: Radius.circular(borderRadius),
@@ -204,7 +204,7 @@ class ScannerOverlay extends CustomPainter {
);
canvas.drawPath(backgroundWithCutout, backgroundPaint);
canvas.drawRSuperellipse(border, borderPaint);
canvas.drawRRect(borderRect, borderPaint);
}
@override

View File

@@ -86,11 +86,7 @@ class Service {
}
Future<String> init() async {
return await methodChannel.invokeMethod<String>(
'init',
!globalState.isService,
) ??
'';
return await methodChannel.invokeMethod<String>('init') ?? '';
}
Future<bool> shutdown() async {

View File

@@ -48,7 +48,6 @@ class GlobalState {
AppController? _appController;
bool isInit = false;
bool isUserDisconnected = false;
bool isService = false;
bool get isStart => startTime != null && startTime!.isBeforeNow;
@@ -239,7 +238,7 @@ class GlobalState {
return VpnOptions(
stack: config.patchClashConfig.tun.stack.name,
enable: vpnProps.enable,
systemProxy: vpnProps.systemProxy,
systemProxy: networkProps.systemProxy,
port: port,
ipv6: vpnProps.ipv6,
dnsHijacking: vpnProps.dnsHijacking,
@@ -304,13 +303,7 @@ class GlobalState {
Future<void> genConfigFile(ClashConfig pathConfig) async {
final configFilePath = await appPath.configFilePath;
var config = {};
try {
config = await patchRawConfig(patchConfig: pathConfig);
} catch (e) {
globalState.showNotifier(e.toString());
config = {};
}
final config = await patchRawConfig(patchConfig: pathConfig);
final res = await Isolate.run<String>(() async {
try {
final res = json.encode(config);
@@ -329,42 +322,6 @@ class GlobalState {
}
}
Future<void> genValidateFile(String path, String data) async {
final res = await Isolate.run<String>(() async {
try {
final file = File(path);
if (!await file.exists()) {
await file.create(recursive: true);
}
await file.writeAsString(data);
return '';
} catch (e) {
return e.toString();
}
});
if (res.isNotEmpty) {
throw res;
}
}
Future<void> genValidateFileFormBytes(String path, Uint8List bytes) async {
final res = await Isolate.run<String>(() async {
try {
final file = File(path);
if (!await file.exists()) {
await file.create(recursive: true);
}
await file.writeAsBytes(bytes);
return '';
} catch (e) {
return e.toString();
}
});
if (res.isNotEmpty) {
throw res;
}
}
AndroidState getAndroidState() {
return AndroidState(
currentProfileName: config.currentProfile?.label ?? '',

View File

@@ -51,7 +51,7 @@ class UaItem extends ConsumerWidget {
subtitle: Text(globalUa ?? appLocalizations.defaultText),
delegate: OptionsDelegate<String?>(
title: 'UA',
options: [null, 'clash-verge/v2.4.2', 'ClashforWindows/0.19.23'],
options: [null, 'clash-verge/v1.6.6', 'ClashforWindows/0.19.23'],
value: globalUa,
onChanged: (value) {
ref

View File

@@ -14,7 +14,7 @@ class StartButton extends ConsumerStatefulWidget {
class _StartButtonState extends ConsumerState<StartButton>
with SingleTickerProviderStateMixin {
AnimationController? _controller;
late AnimationController _controller;
late Animation<double> _animation;
bool isStart = false;
@@ -28,7 +28,7 @@ class _StartButtonState extends ConsumerState<StartButton>
duration: const Duration(milliseconds: 200),
);
_animation = CurvedAnimation(
parent: _controller!,
parent: _controller,
curve: Curves.easeOutBack,
);
ref.listenManual(runTimeProvider.select((state) => state != null), (
@@ -44,8 +44,7 @@ class _StartButtonState extends ConsumerState<StartButton>
@override
void dispose() {
_controller?.dispose();
_controller = null;
_controller.dispose();
super.dispose();
}
@@ -60,9 +59,9 @@ class _StartButtonState extends ConsumerState<StartButton>
void updateController() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (isStart && mounted) {
_controller?.forward();
_controller.forward();
} else {
_controller?.reverse();
_controller.reverse();
}
});
}
@@ -75,13 +74,12 @@ class _StartButtonState extends ConsumerState<StartButton>
}
return Theme(
data: Theme.of(context).copyWith(
floatingActionButtonTheme: Theme.of(context).floatingActionButtonTheme
.copyWith(
sizeConstraints: BoxConstraints(minWidth: 56, maxWidth: 200),
),
floatingActionButtonTheme: FloatingActionButtonThemeData(
sizeConstraints: BoxConstraints(minWidth: 56, maxWidth: 200),
),
),
child: AnimatedBuilder(
animation: _controller!.view,
animation: _controller.view,
builder: (_, child) {
final textWidth =
globalState.measure

View File

@@ -128,11 +128,10 @@ class TrafficUsage extends StatelessWidget {
Container(
width: 20,
height: 8,
decoration: ShapeDecoration(
decoration: BoxDecoration(
color: primaryColor,
shape: RoundedSuperellipseBorder(
borderRadius:
BorderRadius.circular(3),
borderRadius: BorderRadius.circular(
3,
),
),
),
@@ -152,11 +151,10 @@ class TrafficUsage extends StatelessWidget {
Container(
width: 20,
height: 8,
decoration: ShapeDecoration(
decoration: BoxDecoration(
color: secondaryColor,
shape: RoundedSuperellipseBorder(
borderRadius:
BorderRadius.circular(3),
borderRadius: BorderRadius.circular(
3,
),
),
),

View File

@@ -2,6 +2,7 @@ import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/core/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/providers/app.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
@@ -18,14 +19,12 @@ class DeveloperView extends ConsumerWidget {
items: [
ListItem(
title: Text(appLocalizations.messageTest),
minVerticalPadding: 14,
onTap: () {
context.showNotifier(appLocalizations.messageTestTip);
},
),
ListItem(
title: Text(appLocalizations.logsTest),
minVerticalPadding: 14,
onTap: () {
for (int i = 0; i < 1000; i++) {
globalState.appController.addLog(
@@ -38,7 +37,6 @@ class DeveloperView extends ConsumerWidget {
),
ListItem(
title: Text(appLocalizations.crashTest),
minVerticalPadding: 14,
onTap: () {
if (kDebugMode) {
coreController.crash();
@@ -47,20 +45,18 @@ class DeveloperView extends ConsumerWidget {
),
ListItem(
title: Text(appLocalizations.clearData),
minVerticalPadding: 14,
onTap: () async {
await globalState.appController.handleClear();
},
),
// ListItem(
// title: Text('Loading'),
// minVerticalPadding: 14,
// onTap: () {
// ref.read(loadingProvider.notifier).value = !ref.read(
// loadingProvider,
// );
// },
// ),
ListItem(
title: Text('Loading'),
onTap: () {
ref.read(loadingProvider.notifier).value = !ref.read(
loadingProvider,
);
},
),
],
);
}

View File

@@ -488,7 +488,7 @@ class _ListHeaderState extends State<ListHeader> {
return CommonCard(
enterAnimated: widget.enterAnimated,
key: widget.key,
radius: 18.ap,
radius: 16.ap,
type: CommonCardType.filled,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),

View File

@@ -147,8 +147,7 @@ class ProviderItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
if (provider.updateAt.microsecondsSinceEpoch > 0)
Text(_buildProviderDesc()),
Text(_buildProviderDesc()),
const SizedBox(height: 4),
if (provider.subscriptionInfo != null)
SubscriptionInfoView(subscriptionInfo: provider.subscriptionInfo),

View File

@@ -360,7 +360,7 @@ class DelayTestButton extends StatefulWidget {
class _DelayTestButtonState extends State<DelayTestButton>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
late Animation<double> _scale;
Future<void> _healthcheck() async {
if (_controller.isAnimating) {
@@ -378,10 +378,10 @@ class _DelayTestButtonState extends State<DelayTestButton>
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
duration: const Duration(milliseconds: 200),
);
_animation = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOutBack),
_scale = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(parent: _controller, curve: const Interval(0, 1)),
);
}
@@ -399,10 +399,7 @@ class _DelayTestButtonState extends State<DelayTestButton>
return SizedBox(
width: 56,
height: 56,
child: FadeTransition(
opacity: _animation,
child: ScaleTransition(scale: _animation, child: child),
),
child: Transform.scale(scale: _scale.value, child: child),
);
},
child: FloatingActionButton(

View File

@@ -79,7 +79,7 @@ class CommonCard extends StatelessWidget {
this.type = CommonCardType.plain,
this.onPressed,
this.selectWidget,
this.radius = 14,
this.radius = 12,
required this.child,
this.padding,
this.enterAnimated = false,
@@ -177,9 +177,7 @@ class CommonCard extends StatelessWidget {
style: ButtonStyle(
padding: const WidgetStatePropertyAll(EdgeInsets.zero),
shape: WidgetStatePropertyAll(
RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(radius),
),
RoundedRectangleBorder(borderRadius: BorderRadius.circular(radius)),
),
iconColor: WidgetStatePropertyAll(context.colorScheme.primary),
iconSize: WidgetStateProperty.all(20),

View File

@@ -37,7 +37,7 @@ class ColorSchemeBox extends StatelessWidget {
),
child: Container(
padding: const EdgeInsets.all(8),
child: ClipRSuperellipse(
child: ClipRRect(
borderRadius: BorderRadius.circular(36),
child: SizedBox(
width: 72,
@@ -47,16 +47,22 @@ class ColorSchemeBox extends StatelessWidget {
children: [
GridItem(
mainAxisCellCount: 2,
child: Container(color: colorScheme.primary),
child: Container(
color: colorScheme.primary,
),
),
GridItem(
mainAxisCellCount: 1,
child: Container(color: colorScheme.secondary),
child: Container(
color: colorScheme.secondary,
),
),
GridItem(
mainAxisCellCount: 1,
child: Container(color: colorScheme.tertiary),
),
child: Container(
color: colorScheme.tertiary,
),
)
],
),
),
@@ -67,8 +73,11 @@ class ColorSchemeBox extends StatelessWidget {
const Positioned(
bottom: 4,
right: 4,
child: Icon(Icons.colorize, size: 20),
),
child: Icon(
Icons.colorize,
size: 20,
),
)
],
);
},
@@ -103,7 +112,9 @@ class PrimaryColorBox extends ConsumerWidget {
),
);
return Theme(
data: themeData.copyWith(colorScheme: colorScheme),
data: themeData.copyWith(
colorScheme: colorScheme,
),
child: child,
);
}

View File

@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_svg/svg.dart';
class CommonTargetIcon extends StatelessWidget {
@@ -59,7 +60,7 @@ class _ImageCacheWidgetState extends State<ImageCacheWidget> {
@override
void initState() {
super.initState();
_imageFuture = LocalImageCacheManager().getSingleFile(widget.src);
_imageFuture = DefaultCacheManager().getSingleFile(widget.src);
}
@override

View File

@@ -18,21 +18,30 @@ class RadioDelegate<T> extends Delegate {
final T value;
final void Function()? onTab;
const RadioDelegate({required this.value, this.onTab});
const RadioDelegate({
required this.value,
this.onTab,
});
}
class SwitchDelegate<T> extends Delegate {
final bool value;
final ValueChanged<bool>? onChanged;
const SwitchDelegate({required this.value, this.onChanged});
const SwitchDelegate({
required this.value,
this.onChanged,
});
}
class CheckboxDelegate<T> extends Delegate {
final bool value;
final ValueChanged<bool?>? onChanged;
const CheckboxDelegate({this.value = false, this.onChanged});
const CheckboxDelegate({
this.value = false,
this.onChanged,
});
}
class OpenDelegate extends Delegate {
@@ -120,7 +129,6 @@ class ListItem<T> extends StatelessWidget {
final double? horizontalTitleGap;
final TextStyle? titleTextStyle;
final TextStyle? subtitleTextStyle;
final double minVerticalPadding;
final void Function()? onTap;
const ListItem({
@@ -135,7 +143,6 @@ class ListItem<T> extends StatelessWidget {
this.onTap,
this.titleTextStyle,
this.subtitleTextStyle,
this.minVerticalPadding = 12,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : delegate = const Delegate();
@@ -151,7 +158,6 @@ class ListItem<T> extends StatelessWidget {
this.dense,
this.titleTextStyle,
this.subtitleTextStyle,
this.minVerticalPadding = 12,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : onTap = null;
@@ -167,7 +173,6 @@ class ListItem<T> extends StatelessWidget {
this.dense,
this.titleTextStyle,
this.subtitleTextStyle,
this.minVerticalPadding = 12,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : onTap = null;
@@ -183,7 +188,6 @@ class ListItem<T> extends StatelessWidget {
this.dense,
this.titleTextStyle,
this.subtitleTextStyle,
this.minVerticalPadding = 12,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : onTap = null;
@@ -199,7 +203,6 @@ class ListItem<T> extends StatelessWidget {
this.dense,
this.titleTextStyle,
this.subtitleTextStyle,
this.minVerticalPadding = 12,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : onTap = null;
@@ -214,10 +217,9 @@ class ListItem<T> extends StatelessWidget {
this.dense,
this.titleTextStyle,
this.subtitleTextStyle,
this.minVerticalPadding = 12,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : trailing = null,
onTap = null;
}) : trailing = null,
onTap = null;
const ListItem.switchItem({
super.key,
@@ -230,10 +232,9 @@ class ListItem<T> extends StatelessWidget {
this.dense,
this.titleTextStyle,
this.subtitleTextStyle,
this.minVerticalPadding = 12,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : trailing = null,
onTap = null;
}) : trailing = null,
onTap = null;
const ListItem.radio({
super.key,
@@ -246,10 +247,9 @@ class ListItem<T> extends StatelessWidget {
this.dense,
this.titleTextStyle,
this.subtitleTextStyle,
this.minVerticalPadding = 12,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : leading = null,
onTap = null;
}) : leading = null,
onTap = null;
Widget _buildListTile({
void Function()? onTap,
@@ -264,7 +264,7 @@ class ListItem<T> extends StatelessWidget {
leading: leading ?? this.leading,
horizontalTitleGap: horizontalTitleGap,
title: title,
minVerticalPadding: minVerticalPadding,
minVerticalPadding: 12,
subtitle: subtitle,
titleAlignment: tileTitleAlignment,
onTap: onTap,
@@ -282,7 +282,7 @@ class ListItem<T> extends StatelessWidget {
closedBuilder: (_, action) {
openAction() {
final isMobile = globalState.appState.viewMode == ViewMode.mobile;
if (!isMobile) {
if (!isMobile || system.isDesktop) {
showExtend(
context,
props: ExtendProps(
@@ -306,7 +306,9 @@ class ListItem<T> extends StatelessWidget {
action();
}
return _buildListTile(onTap: openAction);
return _buildListTile(
onTap: openAction,
);
},
openBuilder: (_, action) {
return openDelegate.wrap
@@ -420,7 +422,9 @@ class ListItem<T> extends StatelessWidget {
);
}
return _buildListTile(onTap: onTap);
return _buildListTile(
onTap: onTap,
);
}
}
@@ -444,9 +448,13 @@ class ListHeader extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
alignment: Alignment.centerLeft,
padding:
padding ??
const EdgeInsets.only(left: 16, right: 8, top: 24, bottom: 8),
padding: padding ??
const EdgeInsets.only(
left: 16,
right: 8,
top: 24,
bottom: 8,
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -458,18 +466,19 @@ class ListHeader extends StatelessWidget {
Text(
title,
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurfaceVariant.opacity80,
fontWeight: FontWeight.w600,
),
color: Theme.of(context)
.colorScheme
.onSurfaceVariant
.opacity80,
fontWeight: FontWeight.w600,
),
),
if (subTitle != null)
Text(
subTitle!,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.outline,
),
color: Theme.of(context).colorScheme.outline,
),
),
],
),
@@ -477,7 +486,12 @@ class ListHeader extends StatelessWidget {
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [...genActions(actions, space: space)],
children: [
...genActions(
actions,
space: space,
),
],
),
],
),
@@ -492,11 +506,18 @@ List<Widget> generateSection({
bool separated = true,
}) {
final genItems = separated
? items.separated(const Divider(height: 0))
? items.separated(
const Divider(
height: 0,
),
)
: items;
return [
if (items.isNotEmpty && title != null)
ListHeader(title: title, actions: actions),
ListHeader(
title: title,
actions: actions,
),
...genItems,
];
}
@@ -507,26 +528,22 @@ Widget generateSectionV2({
List<Widget>? actions,
bool separated = true,
}) {
final genItems = items
.map<Widget>((item) {
return ClipRSuperellipse(
borderRadius: BorderRadius.circular(4),
child: CommonCard(
type: CommonCardType.filled,
radius: 0,
child: item,
),
);
})
.separated(const Divider(height: 2, color: Colors.transparent));
return Column(
children: [
if (items.isNotEmpty && title != null)
ListHeader(title: title, actions: actions),
ClipRSuperellipse(
borderRadius: BorderRadius.circular(14),
child: Column(children: [...genItems]),
),
ListHeader(
title: title,
actions: actions,
),
CommonCard(
radius: 18,
type: CommonCardType.filled,
child: Column(
children: [
...items,
],
),
)
],
);
}
@@ -538,10 +555,18 @@ List<Widget> generateInfoSection({
bool separated = true,
}) {
final genItems = separated
? items.separated(const Divider(height: 0))
? items.separated(
const Divider(
height: 0,
),
)
: items;
return [
if (items.isNotEmpty) InfoHeader(info: info, actions: actions),
if (items.isNotEmpty)
InfoHeader(
info: info,
actions: actions,
),
...genItems,
];
}
@@ -550,6 +575,8 @@ Widget generateListView(List<Widget> items) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (_, index) => items[index],
padding: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.only(
bottom: 16,
),
);
}

View File

@@ -2,15 +2,19 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
typedef CloseContainerActionCallback<S> = void Function({S? returnValue});
typedef OpenContainerBuilder<S> =
Widget Function(
BuildContext context,
CloseContainerActionCallback<S> action,
);
typedef CloseContainerBuilder =
Widget Function(BuildContext context, VoidCallback action);
typedef OpenContainerBuilder<S> = Widget Function(
BuildContext context,
CloseContainerActionCallback<S> action,
);
typedef CloseContainerBuilder = Widget Function(
BuildContext context,
VoidCallback action,
);
enum ContainerTransitionType { fade, fadeThrough }
enum ContainerTransitionType {
fade,
fadeThrough,
}
typedef ClosedCallback<S> = void Function(S data);
@@ -52,23 +56,20 @@ class _OpenContainerState<T> extends State<OpenContainer<T?>> {
Future<void> openContainer() async {
final Color middleColor =
widget.middleColor ?? Theme.of(context).canvasColor;
final T? data =
await Navigator.of(
context,
rootNavigator: widget.useRootNavigator,
).push(
_OpenContainerRoute<T>(
middleColor: middleColor,
closedBuilder: widget.closedBuilder,
openBuilder: widget.openBuilder,
hideableKey: _hideableKey,
closedBuilderKey: _closedBuilderKey,
transitionDuration: widget.transitionDuration,
transitionType: widget.transitionType,
useRootNavigator: widget.useRootNavigator,
routeSettings: widget.routeSettings,
),
);
final T? data = await Navigator.of(
context,
rootNavigator: widget.useRootNavigator,
).push(_OpenContainerRoute<T>(
middleColor: middleColor,
closedBuilder: widget.closedBuilder,
openBuilder: widget.openBuilder,
hideableKey: _hideableKey,
closedBuilderKey: _closedBuilderKey,
transitionDuration: widget.transitionDuration,
transitionType: widget.transitionType,
useRootNavigator: widget.useRootNavigator,
routeSettings: widget.routeSettings,
));
if (widget.onClosed != null) {
widget.onClosed!(data);
}
@@ -96,7 +97,10 @@ class _OpenContainerState<T> extends State<OpenContainer<T?>> {
}
class _Hideable extends StatefulWidget {
const _Hideable({super.key, required this.child});
const _Hideable({
super.key,
required this.child,
});
final Widget child;
@@ -157,9 +161,9 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
required this.transitionType,
required this.useRootNavigator,
required RouteSettings? routeSettings,
}) : _closedOpacityTween = _getClosedOpacityTween(transitionType),
_openOpacityTween = _getOpenOpacityTween(transitionType),
super(settings: routeSettings);
}) : _closedOpacityTween = _getClosedOpacityTween(transitionType),
_openOpacityTween = _getOpenOpacityTween(transitionType),
super(settings: routeSettings);
static _FlippableTweenSequence<Color?> _getColorTween({
required ContainerTransitionType transitionType,
@@ -169,89 +173,99 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
}) {
switch (transitionType) {
case ContainerTransitionType.fade:
return _FlippableTweenSequence<Color?>(<TweenSequenceItem<Color?>>[
TweenSequenceItem<Color>(
tween: ConstantTween<Color>(closedColor),
weight: 1 / 5,
),
TweenSequenceItem<Color?>(
tween: ColorTween(begin: closedColor, end: openColor),
weight: 1 / 5,
),
TweenSequenceItem<Color>(
tween: ConstantTween<Color>(openColor),
weight: 3 / 5,
),
]);
return _FlippableTweenSequence<Color?>(
<TweenSequenceItem<Color?>>[
TweenSequenceItem<Color>(
tween: ConstantTween<Color>(closedColor),
weight: 1 / 5,
),
TweenSequenceItem<Color?>(
tween: ColorTween(begin: closedColor, end: openColor),
weight: 1 / 5,
),
TweenSequenceItem<Color>(
tween: ConstantTween<Color>(openColor),
weight: 3 / 5,
),
],
);
case ContainerTransitionType.fadeThrough:
return _FlippableTweenSequence<Color?>(<TweenSequenceItem<Color?>>[
TweenSequenceItem<Color?>(
tween: ColorTween(begin: closedColor, end: middleColor),
weight: 1 / 5,
),
TweenSequenceItem<Color?>(
tween: ColorTween(begin: middleColor, end: openColor),
weight: 4 / 5,
),
]);
return _FlippableTweenSequence<Color?>(
<TweenSequenceItem<Color?>>[
TweenSequenceItem<Color?>(
tween: ColorTween(begin: closedColor, end: middleColor),
weight: 1 / 5,
),
TweenSequenceItem<Color?>(
tween: ColorTween(begin: middleColor, end: openColor),
weight: 4 / 5,
),
],
);
}
}
static _FlippableTweenSequence<double> _getClosedOpacityTween(
ContainerTransitionType transitionType,
) {
ContainerTransitionType transitionType) {
switch (transitionType) {
case ContainerTransitionType.fade:
return _FlippableTweenSequence<double>(<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 1,
),
]);
return _FlippableTweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 1,
),
],
);
case ContainerTransitionType.fadeThrough:
return _FlippableTweenSequence<double>(<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween<double>(begin: 1.0, end: 0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 4 / 5,
),
]);
return _FlippableTweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween<double>(begin: 1.0, end: 0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 4 / 5,
),
],
);
}
}
static _FlippableTweenSequence<double> _getOpenOpacityTween(
ContainerTransitionType transitionType,
) {
ContainerTransitionType transitionType) {
switch (transitionType) {
case ContainerTransitionType.fade:
return _FlippableTweenSequence<double>(<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 3 / 5,
),
]);
return _FlippableTweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 3 / 5,
),
],
);
case ContainerTransitionType.fadeThrough:
return _FlippableTweenSequence<double>(<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 4 / 5,
),
]);
return _FlippableTweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 4 / 5,
),
],
);
}
}
@@ -311,9 +325,8 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
@override
void dispose() {
if (hideableKey.currentState?.isVisible == false) {
SchedulerBinding.instance.addPostFrameCallback(
(Duration d) => _toggleHideable(hide: false),
);
SchedulerBinding.instance
.addPostFrameCallback((Duration d) => _toggleHideable(hide: false));
}
super.dispose();
}
@@ -330,12 +343,10 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
required BuildContext navigatorContext,
bool delayForSourceRoute = false,
}) {
final RenderBox navigator =
Navigator.of(
navigatorContext,
rootNavigator: useRootNavigator,
).context.findRenderObject()!
as RenderBox;
final RenderBox navigator = Navigator.of(
navigatorContext,
rootNavigator: useRootNavigator,
).context.findRenderObject()! as RenderBox;
final Size navSize = _getSize(navigator);
_rectTween.end = Offset.zero & navSize;
@@ -348,9 +359,8 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
}
if (delayForSourceRoute) {
SchedulerBinding.instance.addPostFrameCallback(
takeMeasurementsInSourceRoute,
);
SchedulerBinding.instance
.addPostFrameCallback(takeMeasurementsInSourceRoute);
} else {
takeMeasurementsInSourceRoute();
}
@@ -441,9 +451,8 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
final Animation<double> curvedAnimation = CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn,
reverseCurve: _transitionWasInterrupted
? null
: Curves.fastOutSlowIn.flipped,
reverseCurve:
_transitionWasInterrupted ? null : Curves.fastOutSlowIn.flipped,
);
TweenSequence<Color?>? colorTween;
TweenSequence<double>? closedOpacityTween, openOpacityTween;
@@ -499,9 +508,8 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
child: (hideableKey.currentState?.isInTree ?? false)
? null
: FadeTransition(
opacity: closedOpacityTween!.animate(
animation,
),
opacity:
closedOpacityTween!.animate(animation),
child: Builder(
key: closedBuilderKey,
builder: (BuildContext context) {
@@ -513,18 +521,22 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
),
),
),
// Open child fading in.
OverflowBox(
maxWidth: _rectTween.end!.width,
maxHeight: _rectTween.end!.height,
FittedBox(
fit: BoxFit.fitWidth,
alignment: Alignment.topLeft,
child: FadeTransition(
opacity: openOpacityTween!.animate(animation),
child: Builder(
key: _openBuilderKey,
builder: (BuildContext context) {
return openBuilder(context, closeContainer);
},
child: SizedBox(
width: _rectTween.end!.width,
height: _rectTween.end!.height,
child: FadeTransition(
opacity: openOpacityTween!.animate(animation),
child: Builder(
key: _openBuilderKey,
builder: (BuildContext context) {
return openBuilder(context, closeContainer);
},
),
),
),
),
@@ -566,12 +578,10 @@ class _FlippableTweenSequence<T> extends TweenSequence<T> {
if (_flipped == null) {
final List<TweenSequenceItem<T>> newItems = <TweenSequenceItem<T>>[];
for (int i = 0; i < _items.length; i++) {
newItems.add(
TweenSequenceItem<T>(
tween: _items[i].tween,
weight: _items[_items.length - 1 - i].weight,
),
);
newItems.add(TweenSequenceItem<T>(
tween: _items[i].tween,
weight: _items[_items.length - 1 - i].weight,
));
}
_flipped = _FlippableTweenSequence<T>(newItems);
}

View File

@@ -243,7 +243,7 @@ class _ShadePainter extends CustomPainter {
effectiveSquareRadius * 2,
effectiveSquareRadius * 2,
);
final RSuperellipse rSuperellipse = RSuperellipse.fromRectAndRadius(
final RRect rRect = RRect.fromRectAndRadius(
rectBox,
Radius.circular(trackBorderRadius),
);
@@ -254,8 +254,8 @@ class _ShadePainter extends CustomPainter {
HSVColor.fromAHSV(1, colorHue, 1, 1).toColor(),
],
).createShader(rectBox);
canvas.drawRSuperellipse(
rSuperellipse,
canvas.drawRRect(
rRect,
Paint()
..style = PaintingStyle.fill
..shader = horizontal,
@@ -266,8 +266,8 @@ class _ShadePainter extends CustomPainter {
end: Alignment.bottomCenter,
colors: <Color>[Colors.transparent, Colors.black],
).createShader(rectBox);
canvas.drawRSuperellipse(
rSuperellipse,
canvas.drawRRect(
rRect,
Paint()
..style = PaintingStyle.fill
..shader = vertical,

View File

@@ -38,9 +38,10 @@ class CommonPopupRoute<T> extends PopupRoute<T> {
Widget child,
) {
final align = Alignment.topRight;
final curveAnimation = animation
.drive(Tween(begin: 0.0, end: 1.0))
.drive(CurveTween(curve: Curves.easeOutBack));
final animationValue = CurvedAnimation(
parent: animation,
curve: Curves.easeIn,
).value;
return SafeArea(
child: ValueListenableBuilder(
valueListenable: offsetNotifier,
@@ -57,17 +58,15 @@ class CommonPopupRoute<T> extends PopupRoute<T> {
},
child: AnimatedBuilder(
animation: animation,
builder: (_, child) {
return FadeTransition(
opacity: curveAnimation,
child: ScaleTransition(
builder: (_, Widget? child) {
return Opacity(
opacity: 0.1 + 0.9 * animationValue,
child: Transform.scale(
alignment: align,
scale: curveAnimation,
child: SlideTransition(
position: curveAnimation.drive(
Tween(begin: const Offset(0, -0.02), end: Offset.zero),
),
child: child,
scale: 0.7 + 0.3 * animationValue,
child: Transform.translate(
offset: Offset(0, -10) * (1 - animationValue),
child: child!,
),
),
);
@@ -79,7 +78,7 @@ class CommonPopupRoute<T> extends PopupRoute<T> {
}
@override
Duration get transitionDuration => const Duration(milliseconds: 250);
Duration get transitionDuration => const Duration(milliseconds: 150);
}
class PopupController extends ValueNotifier<bool> {
@@ -271,8 +270,8 @@ class CommonPopupMenu extends StatelessWidget {
elevation: 12,
color: context.colorScheme.surfaceContainer,
clipBehavior: Clip.antiAlias,
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,

View File

@@ -18,6 +18,7 @@ class CommonScaffold extends StatefulWidget {
final Widget body;
final Color? backgroundColor;
final String? title;
final Widget? leading;
final List<Widget>? actions;
final bool? centerTitle;
final Widget? floatingActionButton;
@@ -30,6 +31,7 @@ class CommonScaffold extends StatefulWidget {
this.appBar,
required this.body,
this.backgroundColor,
this.leading,
this.title,
this.actions,
this.centerTitle,
@@ -161,7 +163,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
_keywordsNotifier.value = keywords;
}
Widget? _buildLeading(VoidCallback? backAction) {
Widget? _buildLeading() {
if (_isEdit) {
return IconButton(
onPressed: _appBarState.value.editState?.onExit,
@@ -174,16 +176,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
icon: Icon(Icons.arrow_back),
);
}
return backAction != null
? BackButton(
onPressed: () {
if (!mounted) {
return;
}
backAction();
},
)
: null;
return widget.leading;
}
Widget _buildTitle(AppBarSearchState? startState) {
@@ -258,7 +251,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
);
}
PreferredSizeWidget _buildAppBar(VoidCallback? backAction) {
PreferredSizeWidget _buildAppBar() {
return PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Stack(
@@ -270,11 +263,8 @@ class CommonScaffoldState extends State<CommonScaffold> {
builder: (_, state, _) {
return _buildAppBarWrap(
AppBar(
automaticallyImplyLeading: backAction != null
? false
: true,
centerTitle: widget.centerTitle ?? false,
leading: _buildLeading(backAction),
leading: _buildLeading(),
title: _buildTitle(state.searchState),
actions: _buildActions(
state.searchState != null,
@@ -295,7 +285,6 @@ class CommonScaffoldState extends State<CommonScaffold> {
@override
Widget build(BuildContext context) {
assert(widget.appBar != null || widget.title != null);
final backActionProvider = CommonScaffoldBackActionProvider.of(context);
final body = SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -338,7 +327,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
),
);
return Scaffold(
appBar: _buildAppBar(backActionProvider?.backAction),
appBar: _buildAppBar(),
body: body,
resizeToAvoidBottomInset: true,
backgroundColor: widget.backgroundColor,
@@ -360,23 +349,3 @@ List<Widget> genActions(List<Widget> actions, {double? space}) {
SizedBox(width: 8),
];
}
class CommonScaffoldBackActionProvider extends InheritedWidget {
final VoidCallback? backAction;
const CommonScaffoldBackActionProvider({
super.key,
required this.backAction,
required super.child,
});
static CommonScaffoldBackActionProvider? of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<CommonScaffoldBackActionProvider>();
}
@override
bool updateShouldNotify(CommonScaffoldBackActionProvider oldWidget) {
return false;
}
}

View File

@@ -39,7 +39,11 @@ class ExtendProps {
});
}
enum SheetType { page, bottomSheet, sideSheet }
enum SheetType {
page,
bottomSheet,
sideSheet,
}
typedef SheetBuilder = Widget Function(BuildContext context, SheetType type);
@@ -51,24 +55,28 @@ Future<T?> showSheet<T>({
final isMobile = globalState.appState.viewMode == ViewMode.mobile;
return switch (isMobile) {
true => showModalBottomSheet<T>(
context: context,
isScrollControlled: props.isScrollControlled,
builder: (_) {
return SafeArea(child: builder(context, SheetType.bottomSheet));
},
showDragHandle: false,
useSafeArea: props.useSafeArea,
),
context: context,
isScrollControlled: props.isScrollControlled,
builder: (_) {
return SafeArea(
child: builder(context, SheetType.bottomSheet),
);
},
showDragHandle: false,
useSafeArea: props.useSafeArea,
),
false => showModalSideSheet<T>(
useSafeArea: props.useSafeArea,
isScrollControlled: props.isScrollControlled,
context: context,
constraints: BoxConstraints(maxWidth: props.maxWidth ?? 360),
filter: props.blur ? commonFilter : null,
builder: (_) {
return builder(context, SheetType.sideSheet);
},
),
useSafeArea: props.useSafeArea,
isScrollControlled: props.isScrollControlled,
context: context,
constraints: BoxConstraints(
maxWidth: props.maxWidth ?? 360,
),
filter: props.blur ? commonFilter : null,
builder: (_) {
return builder(context, SheetType.sideSheet);
},
),
};
}
@@ -79,16 +87,21 @@ Future<T?> showExtend<T>(
}) {
final isMobile = globalState.appState.viewMode == ViewMode.mobile;
return switch (isMobile || props.forceFull) {
true => BaseNavigator.push(context, builder(context, SheetType.page)),
true => BaseNavigator.push(
context,
builder(context, SheetType.page),
),
false => showModalSideSheet<T>(
useSafeArea: props.useSafeArea,
context: context,
constraints: BoxConstraints(maxWidth: props.maxWidth ?? 360),
filter: props.blur ? commonFilter : null,
builder: (context) {
return builder(context, SheetType.sideSheet);
},
),
useSafeArea: props.useSafeArea,
context: context,
constraints: BoxConstraints(
maxWidth: props.maxWidth ?? 360,
),
filter: props.blur ? commonFilter : null,
builder: (context) {
return builder(context, SheetType.sideSheet);
},
),
};
}
@@ -121,11 +134,13 @@ class _AdaptiveSheetScaffoldState extends State<AdaptiveSheetScaffold> {
automaticallyImplyLeading: bottomSheet
? false
: widget.actions.isEmpty && sideSheet
? false
: true,
? false
: true,
centerTitle: bottomSheet,
backgroundColor: backgroundColor,
title: Text(widget.title),
title: Text(
widget.title,
),
actions: genActions([
if (widget.actions.isEmpty && sideSheet) CloseButton(),
...widget.actions,
@@ -135,11 +150,9 @@ class _AdaptiveSheetScaffoldState extends State<AdaptiveSheetScaffold> {
final handleSize = Size(32, 4);
return Container(
clipBehavior: Clip.hardEdge,
decoration: ShapeDecoration(
decoration: BoxDecoration(
color: backgroundColor,
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(28.0)),
),
borderRadius: BorderRadius.vertical(top: Radius.circular(28.0)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -150,16 +163,17 @@ class _AdaptiveSheetScaffoldState extends State<AdaptiveSheetScaffold> {
alignment: Alignment.center,
height: handleSize.height,
width: handleSize.width,
decoration: ShapeDecoration(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(handleSize.height / 2),
color: context.colorScheme.onSurfaceVariant,
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(handleSize.height / 2),
),
),
),
),
appBar,
Flexible(flex: 1, child: widget.body),
Flexible(
flex: 1,
child: widget.body,
)
],
),
);

View File

@@ -82,12 +82,12 @@ class _SideSheetState extends State<SideSheet> {
final Color surfaceTintColor = colorScheme.surfaceTint;
final Color shadowColor = widget.shadowColor ?? Colors.transparent;
final double elevation = widget.elevation ?? 0;
final ShapeBorder shape =
widget.shape ??
RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(0));
final ShapeBorder shape = widget.shape ??
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
);
final BoxConstraints constraints =
widget.constraints ??
final BoxConstraints constraints = widget.constraints ??
const BoxConstraints(maxWidth: 320, minWidth: 320);
final Clip clipBehavior = widget.clipBehavior ?? Clip.none;
@@ -103,7 +103,10 @@ class _SideSheetState extends State<SideSheet> {
child: widget.builder(context),
);
return ConstrainedBox(constraints: constraints, child: sideSheet);
return ConstrainedBox(
constraints: constraints,
child: sideSheet,
);
}
}
@@ -125,8 +128,7 @@ class _SideSheetLayoutWithSizeListener extends SingleChildRenderObjectWidget {
@override
_RenderSideSheetLayoutWithSizeListener createRenderObject(
BuildContext context,
) {
BuildContext context) {
return _RenderSideSheetLayoutWithSizeListener(
onChildSizeChanged: onChildSizeChanged,
animationValue: animationValue,
@@ -136,10 +138,8 @@ class _SideSheetLayoutWithSizeListener extends SingleChildRenderObjectWidget {
}
@override
void updateRenderObject(
BuildContext context,
_RenderSideSheetLayoutWithSizeListener renderObject,
) {
void updateRenderObject(BuildContext context,
_RenderSideSheetLayoutWithSizeListener renderObject) {
renderObject.onChildSizeChanged = onChildSizeChanged;
renderObject.animationValue = animationValue;
renderObject.isScrollControlled = isScrollControlled;
@@ -155,12 +155,12 @@ class _RenderSideSheetLayoutWithSizeListener extends RenderShiftedBox {
required double animationValue,
required bool isScrollControlled,
required double scrollControlDisabledMaxHeightRatio,
}) : _onChildSizeChanged = onChildSizeChanged,
_animationValue = animationValue,
_isScrollControlled = isScrollControlled,
_scrollControlDisabledMaxHeightRatio =
scrollControlDisabledMaxHeightRatio,
super(child);
}) : _onChildSizeChanged = onChildSizeChanged,
_animationValue = animationValue,
_isScrollControlled = isScrollControlled,
_scrollControlDisabledMaxHeightRatio =
scrollControlDisabledMaxHeightRatio,
super(child);
Size _lastSize = Size.zero;
@@ -219,9 +219,8 @@ class _RenderSideSheetLayoutWithSizeListener extends RenderShiftedBox {
@override
double computeMinIntrinsicWidth(double height) {
final double width = _getSize(
BoxConstraints.tightForFinite(height: height),
).width;
final double width =
_getSize(BoxConstraints.tightForFinite(height: height)).width;
if (width.isFinite) {
return width;
}
@@ -230,9 +229,8 @@ class _RenderSideSheetLayoutWithSizeListener extends RenderShiftedBox {
@override
double computeMaxIntrinsicWidth(double height) {
final double width = _getSize(
BoxConstraints.tightForFinite(height: height),
).width;
final double width =
_getSize(BoxConstraints.tightForFinite(height: height)).width;
if (width.isFinite) {
return width;
}
@@ -241,9 +239,8 @@ class _RenderSideSheetLayoutWithSizeListener extends RenderShiftedBox {
@override
double computeMinIntrinsicHeight(double width) {
final double height = _getSize(
BoxConstraints.tightForFinite(width: width),
).height;
final double height =
_getSize(BoxConstraints.tightForFinite(width: width)).height;
if (height.isFinite) {
return height;
}
@@ -252,9 +249,8 @@ class _RenderSideSheetLayoutWithSizeListener extends RenderShiftedBox {
@override
double computeMaxIntrinsicHeight(double width) {
final double height = _getSize(
BoxConstraints.tightForFinite(width: width),
).height;
final double height =
_getSize(BoxConstraints.tightForFinite(width: width)).height;
if (height.isFinite) {
return height;
}
@@ -267,7 +263,9 @@ class _RenderSideSheetLayoutWithSizeListener extends RenderShiftedBox {
}
BoxConstraints _getConstraintsForChild(BoxConstraints constraints) {
return BoxConstraints(maxHeight: constraints.maxHeight);
return BoxConstraints(
maxHeight: constraints.maxHeight,
);
}
Offset _getPositionForChild(Size size, Size childSize) {
@@ -278,9 +276,8 @@ class _RenderSideSheetLayoutWithSizeListener extends RenderShiftedBox {
void performLayout() {
size = _getSize(constraints);
if (child != null) {
final BoxConstraints childConstraints = _getConstraintsForChild(
constraints,
);
final BoxConstraints childConstraints =
_getConstraintsForChild(constraints);
assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
child!.layout(
childConstraints,
@@ -291,9 +288,8 @@ class _RenderSideSheetLayoutWithSizeListener extends RenderShiftedBox {
size,
childConstraints.isTight ? childConstraints.smallest : child!.size,
);
final Size childSize = childConstraints.isTight
? childConstraints.smallest
: child!.size;
final Size childSize =
childConstraints.isTight ? childConstraints.smallest : child!.size;
if (_lastSize != childSize) {
_lastSize = childSize;
@@ -358,9 +354,8 @@ class _ModalSideSheetState<T> extends State<_ModalSideSheet<T>> {
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
assert(debugCheckHasMaterialLocalizations(context));
final MaterialLocalizations localizations = MaterialLocalizations.of(
context,
);
final MaterialLocalizations localizations =
MaterialLocalizations.of(context);
final String routeLabel = _getRouteLabel(localizations);
return AnimatedBuilder(
@@ -411,27 +406,26 @@ class _ModalSideSheetState<T> extends State<_ModalSideSheet<T>> {
}
class ModalSideSheetRoute<T> extends PopupRoute<T> {
ModalSideSheetRoute({
required this.builder,
this.capturedThemes,
this.barrierLabel,
this.barrierOnTapHint,
this.backgroundColor,
this.elevation,
this.shape,
this.clipBehavior,
this.constraints,
this.modalBarrierColor,
this.isDismissible = true,
this.isScrollControlled = false,
this.scrollControlDisabledMaxHeightRatio =
_defaultScrollControlDisabledMaxHeightRatio,
super.settings,
this.transitionAnimationController,
this.anchorPoint,
this.useSafeArea = false,
super.filter,
});
ModalSideSheetRoute(
{required this.builder,
this.capturedThemes,
this.barrierLabel,
this.barrierOnTapHint,
this.backgroundColor,
this.elevation,
this.shape,
this.clipBehavior,
this.constraints,
this.modalBarrierColor,
this.isDismissible = true,
this.isScrollControlled = false,
this.scrollControlDisabledMaxHeightRatio =
_defaultScrollControlDisabledMaxHeightRatio,
super.settings,
this.transitionAnimationController,
this.anchorPoint,
this.useSafeArea = false,
super.filter});
final WidgetBuilder builder;
@@ -510,11 +504,8 @@ class ModalSideSheetRoute<T> extends PopupRoute<T> {
}
@override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
final Widget content = DisplayFeatureSubScreen(
anchorPoint: anchorPoint,
child: Builder(
@@ -548,7 +539,9 @@ class ModalSideSheetRoute<T> extends PopupRoute<T> {
ColorTween(
begin: barrierColor.opacity0,
end: barrierColor,
).chain(CurveTween(curve: barrierCurve)),
).chain(
CurveTween(curve: barrierCurve),
),
);
return AnimatedModalBarrier(
color: color,
@@ -594,39 +587,32 @@ Future<T?> showModalSideSheet<T>({
assert(debugCheckHasMediaQuery(context));
assert(debugCheckHasMaterialLocalizations(context));
final NavigatorState navigator = Navigator.of(
context,
rootNavigator: useRootNavigator,
);
final NavigatorState navigator =
Navigator.of(context, rootNavigator: useRootNavigator);
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
return navigator.push(
ModalSideSheetRoute<T>(
builder: builder,
filter: filter,
capturedThemes: InheritedTheme.capture(
from: context,
to: navigator.context,
),
isScrollControlled: isScrollControlled,
scrollControlDisabledMaxHeightRatio: scrollControlDisabledMaxHeightRatio,
barrierLabel: barrierLabel ?? localizations.scrimLabel,
barrierOnTapHint: localizations.scrimOnTapHint(
localizations.bottomSheetLabel,
),
backgroundColor: backgroundColor,
elevation: elevation,
shape: shape,
clipBehavior: clipBehavior,
constraints: constraints,
isDismissible: isDismissible,
modalBarrierColor:
barrierColor ?? Theme.of(context).bottomSheetTheme.modalBarrierColor,
settings: routeSettings,
transitionAnimationController: transitionAnimationController,
anchorPoint: anchorPoint,
useSafeArea: useSafeArea,
),
);
return navigator.push(ModalSideSheetRoute<T>(
builder: builder,
filter: filter,
capturedThemes:
InheritedTheme.capture(from: context, to: navigator.context),
isScrollControlled: isScrollControlled,
scrollControlDisabledMaxHeightRatio: scrollControlDisabledMaxHeightRatio,
barrierLabel: barrierLabel ?? localizations.scrimLabel,
barrierOnTapHint:
localizations.scrimOnTapHint(localizations.bottomSheetLabel),
backgroundColor: backgroundColor,
elevation: elevation,
shape: shape,
clipBehavior: clipBehavior,
constraints: constraints,
isDismissible: isDismissible,
modalBarrierColor:
barrierColor ?? Theme.of(context).bottomSheetTheme.modalBarrierColor,
settings: routeSettings,
transitionAnimationController: transitionAnimationController,
anchorPoint: anchorPoint,
useSafeArea: useSafeArea,
));
}
// class ModalAppBar extends StatelessWidget {

View File

@@ -8,10 +8,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/rendering.dart';
const EdgeInsetsGeometry _kHorizontalItemPadding = EdgeInsets.symmetric(
vertical: 2,
horizontal: 3,
);
const EdgeInsetsGeometry _kHorizontalItemPadding =
EdgeInsets.symmetric(vertical: 2, horizontal: 3);
const Radius _kCornerRadius = Radius.circular(9);
@@ -65,11 +63,11 @@ class CommonTabBar<T extends Object> extends StatefulWidget {
this.padding = _kHorizontalItemPadding,
this.backgroundColor,
this.proportionalWidth = false,
}) : assert(children.length >= 2),
assert(
groupValue == null || children.keys.contains(groupValue),
'The groupValue must be either null or one of the keys in the children map.',
);
}) : assert(children.length >= 2),
assert(
groupValue == null || children.keys.contains(groupValue),
'The groupValue must be either null or one of the keys in the children map.',
);
final Map<T, Widget> children;
final Set<T> disabledChildren;
final T? groupValue;
@@ -192,9 +190,8 @@ class _CommonTabBarState<T extends Object> extends State<CommonTabBar<T>>
void _playThumbScaleAnimation({required bool isExpanding}) {
thumbScaleAnimation = thumbScaleController.drive(
Tween<double>(
begin: thumbScaleAnimation.value,
end: isExpanding ? 1 : _kMinThumbScale,
),
begin: thumbScaleAnimation.value,
end: isExpanding ? 1 : _kMinThumbScale),
);
thumbScaleController.animateWith(_kThumbSpringAnimationSimulation);
}
@@ -234,9 +231,8 @@ class _CommonTabBarState<T extends Object> extends State<CommonTabBar<T>>
void onDown(DragDownDetails details) {
final T touchDownSegment = segmentForXPosition(details.localPosition.dx);
_startedOnSelectedSegment = touchDownSegment == highlighted;
_startedOnDisabledSegment = widget.disabledChildren.contains(
touchDownSegment,
);
_startedOnDisabledSegment =
widget.disabledChildren.contains(touchDownSegment);
if (widget.disabledChildren.contains(touchDownSegment)) {
return;
}
@@ -377,10 +373,8 @@ class _CommonTabBarState<T extends Object> extends State<CommonTabBar<T>>
child: Container(
clipBehavior: Clip.antiAlias,
padding: widget.padding.resolve(Directionality.of(context)),
decoration: ShapeDecoration(
shape: RoundedSuperellipseBorder(
borderRadius: const BorderRadius.all(_kCornerRadius),
),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(_kCornerRadius),
color: widget.backgroundColor,
),
child: AnimatedBuilder(
@@ -461,9 +455,8 @@ class _SegmentState<T> extends State<_Segment<T>>
end: widget.shouldScaleContent ? _kMinThumbScale : 1.0,
),
);
highlightPressScaleController.animateWith(
_kThumbSpringAnimationSimulation,
);
highlightPressScaleController
.animateWith(_kThumbSpringAnimationSimulation);
}
}
@@ -487,21 +480,20 @@ class _SegmentState<T> extends State<_Segment<T>>
alignment: Alignment.center,
children: <Widget>[
AnimatedOpacity(
opacity: widget.shouldFadeoutContent
? _kContentPressedMinOpacity
: 1,
opacity:
widget.shouldFadeoutContent ? _kContentPressedMinOpacity : 1,
duration: _kOpacityAnimationDuration,
curve: Curves.ease,
child: AnimatedDefaultTextStyle(
style: DefaultTextStyle.of(context).style.merge(
TextStyle(
fontWeight: widget.highlighted
? _kHighlightedFontWeight
: _kFontWeight,
fontSize: _kFontSize,
color: widget.enabled ? null : _kDisabledContentColor,
),
),
TextStyle(
fontWeight: widget.highlighted
? _kHighlightedFontWeight
: _kFontWeight,
fontSize: _kFontSize,
color: widget.enabled ? null : _kDisabledContentColor,
),
),
duration: _kHighlightAnimationDuration,
curve: Curves.ease,
child: ScaleTransition(
@@ -513,9 +505,7 @@ class _SegmentState<T> extends State<_Segment<T>>
),
DefaultTextStyle.merge(
style: const TextStyle(
fontWeight: _kHighlightedFontWeight,
fontSize: _kFontSize,
),
fontWeight: _kHighlightedFontWeight, fontSize: _kFontSize),
child: widget.child,
),
],
@@ -580,7 +570,9 @@ class _SegmentSeparatorState extends State<_SegmentSeparator>
return Padding(
padding: _kSeparatorInset,
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.transparent),
decoration: BoxDecoration(
color: Colors.transparent,
),
child: child,
),
);
@@ -620,9 +612,7 @@ class _CommonTabBarRenderWidget<T extends Object>
@override
void updateRenderObject(
BuildContext context,
_RenderSegmentedControl<T> renderObject,
) {
BuildContext context, _RenderSegmentedControl<T> renderObject) {
assert(renderObject.state == state);
renderObject
..thumbColor = thumbColor
@@ -639,24 +629,20 @@ enum _SegmentLocation { leftmost, rightmost, inbetween }
class _RenderSegmentedControl<T extends Object> extends RenderBox
with
ContainerRenderObjectMixin<
RenderBox,
ContainerBoxParentData<RenderBox>
>,
RenderBoxContainerDefaultsMixin<
RenderBox,
ContainerBoxParentData<RenderBox>
> {
ContainerRenderObjectMixin<RenderBox,
ContainerBoxParentData<RenderBox>>,
RenderBoxContainerDefaultsMixin<RenderBox,
ContainerBoxParentData<RenderBox>> {
_RenderSegmentedControl({
required int? highlightedIndex,
required Color thumbColor,
required double thumbScale,
required bool proportionalWidth,
required this.state,
}) : _highlightedIndex = highlightedIndex,
_thumbColor = thumbColor,
_thumbScale = thumbScale,
_proportionalWidth = proportionalWidth;
}) : _highlightedIndex = highlightedIndex,
_thumbColor = thumbColor,
_thumbScale = thumbScale,
_proportionalWidth = proportionalWidth;
final _CommonTabBarState<T> state;
@@ -853,9 +839,7 @@ class _RenderSegmentedControl<T extends Object> extends RenderBox
child = nonSeparatorChildAfter(child);
}
return math.min(
childWidth,
(constraints.maxWidth - totalSeparatorWidth) / childCount,
);
childWidth, (constraints.maxWidth - totalSeparatorWidth) / childCount);
}
List<double> _getChildWidths(BoxConstraints constraints) {
@@ -889,28 +873,20 @@ class _RenderSegmentedControl<T extends Object> extends RenderBox
}
Size _computeOverallSize(BoxConstraints constraints) {
final double maxChildHeight = _getMaxChildHeight(
constraints,
constraints.maxWidth,
);
final double maxChildHeight =
_getMaxChildHeight(constraints, constraints.maxWidth);
return constraints.constrain(
Size(
_getChildWidths(constraints).sum + totalSeparatorWidth,
maxChildHeight,
),
Size(_getChildWidths(constraints).sum + totalSeparatorWidth,
maxChildHeight),
);
}
@override
double? computeDryBaseline(
covariant BoxConstraints constraints,
TextBaseline baseline,
) {
covariant BoxConstraints constraints, TextBaseline baseline) {
final List<double> segmentWidths = _getChildWidths(constraints);
final double childHeight = _getMaxChildHeight(
constraints,
constraints.maxWidth,
);
final double childHeight =
_getMaxChildHeight(constraints, constraints.maxWidth);
int index = 0;
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
@@ -952,10 +928,8 @@ class _RenderSegmentedControl<T extends Object> extends RenderBox
final BoxConstraints childConstraints = BoxConstraints.tight(
Size(segmentWidths[index ~/ 2], childHeight),
);
child.layout(
index.isEven ? childConstraints : separatorConstraints,
parentUsesSize: true,
);
child.layout(index.isEven ? childConstraints : separatorConstraints,
parentUsesSize: true);
final _SegmentedControlContainerBoxParentData childParentData =
child.parentData! as _SegmentedControlContainerBoxParentData;
final Offset childOffset = Offset(start, 0);
@@ -985,9 +959,9 @@ class _RenderSegmentedControl<T extends Object> extends RenderBox
final double leftMost = firstChildOffset.dx;
final double rightMost =
(children.last.parentData! as _SegmentedControlContainerBoxParentData)
.offset
.dx +
children.last.size.width;
.offset
.dx +
children.last.size.width;
assert(rightMost > leftMost);
return Rect.fromLTRB(
math.max(thumbRect.left, leftMost - _kThumbInsets.left),
@@ -1018,10 +992,8 @@ class _RenderSegmentedControl<T extends Object> extends RenderBox
if (thumbTween == null) {
final Rect startingRect =
moveThumbRectInBound(currentThumbRect, children) ?? newThumbRect;
state.thumbAnimatable = RectTween(
begin: startingRect,
end: newThumbRect,
);
state.thumbAnimatable =
RectTween(begin: startingRect, end: newThumbRect);
} else if (newThumbRect != thumbTween.transform(1)) {
final Rect startingRect =
moveThumbRectInBound(currentThumbRect, children) ?? newThumbRect;
@@ -1036,7 +1008,7 @@ class _RenderSegmentedControl<T extends Object> extends RenderBox
final Rect unscaledThumbRect =
state.thumbAnimatable?.evaluate(state.thumbController) ??
newThumbRect;
newThumbRect;
currentThumbRect = unscaledThumbRect;
final _SegmentLocation childLocation;
@@ -1073,10 +1045,7 @@ class _RenderSegmentedControl<T extends Object> extends RenderBox
final Paint separatorPaint = Paint();
void _paintSeparator(
PaintingContext context,
Offset offset,
RenderBox child,
) {
PaintingContext context, Offset offset, RenderBox child) {
final _SegmentedControlContainerBoxParentData childParentData =
child.parentData! as _SegmentedControlContainerBoxParentData;
context.paintChild(child, offset + childParentData.offset);
@@ -1089,20 +1058,23 @@ class _RenderSegmentedControl<T extends Object> extends RenderBox
}
void _paintThumb(PaintingContext context, Offset offset, Rect thumbRect) {
final RSuperellipse thumbRSuperellipse = RSuperellipse.fromRectAndRadius(
thumbRect.shift(offset),
_kThumbRadius,
);
// const List<BoxShadow> thumbShadow = <BoxShadow>[
// BoxShadow(color: Color(0x1F000000), offset: Offset(0, 3), blurRadius: 8),
// BoxShadow(color: Color(0x0A000000), offset: Offset(0, 3), blurRadius: 1),
// ];
context.canvas.drawRSuperellipse(
thumbRSuperellipse.inflate(0.5),
Paint()..color = const Color(0x0A000000),
);
final RRect thumbRRect =
RRect.fromRectAndRadius(thumbRect.shift(offset), _kThumbRadius);
context.canvas.drawRSuperellipse(
thumbRSuperellipse,
Paint()..color = thumbColor,
);
// for (final BoxShadow shadow in thumbShadow) {
// context.canvas
// .drawRRect(thumbRRect.shift(shadow.offset), shadow.toPaint());
// }
context.canvas.drawRRect(
thumbRRect.inflate(0.5), Paint()..color = const Color(0x0A000000));
context.canvas.drawRRect(thumbRRect, Paint()..color = thumbColor);
}
@override

View File

@@ -1,14 +1,10 @@
export 'activate_box.dart';
export 'animate_grid.dart';
export 'pop_scope.dart';
export 'builder.dart';
export 'card.dart';
export 'chip.dart';
export 'color_scheme_box.dart';
export 'container.dart';
export 'dialog.dart';
export 'disabled_mask.dart';
export 'donut_chart.dart';
export 'effect.dart';
export 'fade_box.dart';
export 'float_layout.dart';
export 'grid.dart';
@@ -17,19 +13,23 @@ export 'input.dart';
export 'keep_scope.dart';
export 'line_chart.dart';
export 'list.dart';
export 'notification.dart';
export 'null_status.dart';
export 'open_container.dart';
export 'palette.dart';
export 'pop_scope.dart';
export 'popup.dart';
export 'scaffold.dart';
export 'scroll.dart';
export 'setting.dart';
export 'sheet.dart';
export 'side_sheet.dart';
export 'subscription_info_view.dart';
export 'super_grid.dart';
export 'tab.dart';
export 'text.dart';
export 'super_grid.dart';
export 'donut_chart.dart';
export 'activate_box.dart';
export 'wave.dart';
export 'scroll.dart';
export 'dialog.dart';
export 'effect.dart';
export 'palette.dart';
export 'tab.dart';
export 'container.dart';
export 'notification.dart';

View File

@@ -666,10 +666,10 @@ packages:
dependency: transitive
description:
name: image_picker_android
sha256: "8dfe08ea7fcf7467dbaf6889e72eebd5e0d6711caae201fdac780eb45232cd02"
sha256: a45bef33deb24839a51fb85a4d9e504ead2b1ad1c4779d02d09bf6a8857cdd52
url: "https://pub.dev"
source: hosted
version: "0.8.13+3"
version: "0.8.13+2"
image_picker_for_web:
dependency: transitive
description:
@@ -1010,10 +1010,10 @@ packages:
dependency: transitive
description:
name: pool
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.2"
version: "1.5.1"
posix:
dependency: transitive
description:
@@ -1471,10 +1471,10 @@ packages:
dependency: transitive
description:
name: url_launcher_android
sha256: "199bc33e746088546a39cc5f36bac5a278c5e53b40cb3196f99e7345fdcfae6b"
sha256: "07cffecb7d68cbc6437cd803d5f11a86fe06736735c3dfe46ff73bcb0f958eed"
url: "https://pub.dev"
source: hosted
version: "6.3.22"
version: "6.3.21"
url_launcher_ios:
dependency: transitive
description:

View File

@@ -1,7 +1,7 @@
name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none'
version: 0.8.90+2025100201
version: 0.8.88+2025091701
environment:
sdk: '>=3.8.0 <4.0.0'

View File

@@ -15,8 +15,8 @@ SolidCompression=yes
SetupIconFile={{SETUP_ICON_FILE}}
WizardStyle=modern
PrivilegesRequired={{PRIVILEGES_REQUIRED}}
ArchitecturesAllowed=x64 arm64
ArchitecturesInstallIn64BitMode=x64 arm64
ArchitecturesAllowed={{ARCH}}
ArchitecturesInstallIn64BitMode={{ARCH}}
[Code]
procedure KillProcesses;