Compare commits
1 Commits
v0.8.90-pr
...
v0.8.88-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9428d4ef4c |
6
.github/workflows/build.yaml
vendored
6
.github/workflows/build.yaml
vendored
@@ -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
16
.gitignore
vendored
@@ -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/
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -20,9 +20,7 @@ class BroadcastReceiver : BroadcastReceiver() {
|
||||
|
||||
BroadcastAction.SERVICE_DESTROYED.action -> {
|
||||
GlobalState.log("Receiver service destroyed")
|
||||
GlobalState.launch {
|
||||
State.handleStopServiceAction()
|
||||
}
|
||||
State.handleStopServiceAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -65,24 +61,24 @@ object Service {
|
||||
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)
|
||||
true -> 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
|
||||
})
|
||||
false -> null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,30 +65,19 @@ object State {
|
||||
}
|
||||
|
||||
suspend fun handleStartServiceAction() {
|
||||
runLock.withLock {
|
||||
if (runStateFlow.value != RunState.STOP) {
|
||||
return
|
||||
}
|
||||
tilePlugin?.handleStart()
|
||||
if (flutterEngine != null) {
|
||||
return
|
||||
}
|
||||
startServiceWithEngine()
|
||||
tilePlugin?.handleStart()
|
||||
if (flutterEngine != null) {
|
||||
return
|
||||
}
|
||||
|
||||
startServiceWithEngine()
|
||||
}
|
||||
|
||||
suspend fun handleStopServiceAction() {
|
||||
runLock.withLock {
|
||||
if (runStateFlow.value != RunState.START) {
|
||||
return
|
||||
}
|
||||
tilePlugin?.handleStop()
|
||||
if (flutterEngine != null || serviceFlutterEngine != null) {
|
||||
return
|
||||
}
|
||||
handleStopService()
|
||||
fun handleStopServiceAction() {
|
||||
tilePlugin?.handleStop()
|
||||
if (flutterEngine != null || serviceFlutterEngine != null) {
|
||||
return
|
||||
}
|
||||
handleStopService()
|
||||
}
|
||||
|
||||
fun handleStartService() {
|
||||
@@ -101,23 +90,8 @@ object State {
|
||||
startService()
|
||||
}
|
||||
|
||||
fun handleStopService() {
|
||||
GlobalState.launch {
|
||||
runLock.withLock {
|
||||
if (runStateFlow.value != RunState.START) {
|
||||
return@launch
|
||||
}
|
||||
runStateFlow.tryEmit(RunState.PENDING)
|
||||
runTime = Service.stopService()
|
||||
runStateFlow.tryEmit(RunState.STOP)
|
||||
}
|
||||
destroyServiceEngine()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun destroyServiceEngine() {
|
||||
runLock.withLock {
|
||||
GlobalState.log("Destroy service engine")
|
||||
withContext(Dispatchers.Main) {
|
||||
runCatching {
|
||||
serviceFlutterEngine?.destroy()
|
||||
@@ -127,24 +101,17 @@ object State {
|
||||
}
|
||||
}
|
||||
|
||||
private fun startServiceWithEngine() {
|
||||
GlobalState.launch {
|
||||
runLock.withLock {
|
||||
if (runStateFlow.value != RunState.STOP) {
|
||||
return@launch
|
||||
}
|
||||
GlobalState.log("Create service engine")
|
||||
withContext(Dispatchers.Main) {
|
||||
serviceFlutterEngine?.destroy()
|
||||
serviceFlutterEngine = FlutterEngine(GlobalState.application)
|
||||
serviceFlutterEngine?.plugins?.add(ServicePlugin())
|
||||
serviceFlutterEngine?.plugins?.add(AppPlugin())
|
||||
serviceFlutterEngine?.plugins?.add(TilePlugin())
|
||||
val dartEntrypoint = DartExecutor.DartEntrypoint(
|
||||
FlutterInjector.instance().flutterLoader().findAppBundlePath(), "_service"
|
||||
)
|
||||
serviceFlutterEngine?.dartExecutor?.executeDartEntrypoint(dartEntrypoint)
|
||||
}
|
||||
suspend fun startServiceWithEngine() {
|
||||
runLock.withLock {
|
||||
withContext(Dispatchers.Main) {
|
||||
serviceFlutterEngine = FlutterEngine(GlobalState.application)
|
||||
serviceFlutterEngine?.plugins?.add(ServicePlugin())
|
||||
serviceFlutterEngine?.plugins?.add(AppPlugin())
|
||||
serviceFlutterEngine?.plugins?.add(TilePlugin())
|
||||
val dartEntrypoint = DartExecutor.DartEntrypoint(
|
||||
FlutterInjector.instance().flutterLoader().findAppBundlePath(), "_service"
|
||||
)
|
||||
serviceFlutterEngine?.dartExecutor?.executeDartEntrypoint(dartEntrypoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,7 +119,7 @@ object State {
|
||||
private fun startService() {
|
||||
GlobalState.launch {
|
||||
runLock.withLock {
|
||||
if (runStateFlow.value != RunState.STOP) {
|
||||
if (runStateFlow.value == RunState.PENDING || runStateFlow.value == RunState.START) {
|
||||
return@launch
|
||||
}
|
||||
runStateFlow.tryEmit(RunState.PENDING)
|
||||
@@ -171,6 +138,20 @@ object State {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun handleStopService() {
|
||||
GlobalState.launch {
|
||||
runLock.withLock {
|
||||
if (runStateFlow.value == RunState.PENDING || runStateFlow.value == RunState.STOP) {
|
||||
return@launch
|
||||
}
|
||||
runStateFlow.tryEmit(RunState.PENDING)
|
||||
runTime = Service.stopService()
|
||||
runStateFlow.tryEmit(RunState.STOP)
|
||||
}
|
||||
destroyServiceEngine()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -21,9 +21,7 @@ class TempActivity : Activity(),
|
||||
}
|
||||
|
||||
QuickAction.STOP.action -> {
|
||||
launch {
|
||||
State.handleStopServiceAction()
|
||||
}
|
||||
State.handleStopServiceAction()
|
||||
}
|
||||
|
||||
QuickAction.TOGGLE.action -> {
|
||||
|
||||
@@ -45,7 +45,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -60,9 +59,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// IAckInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
import com.follow.clash.service.IAckInterface;
|
||||
|
||||
interface IAckInterface {
|
||||
oneway void onAck();
|
||||
}
|
||||
@@ -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);
|
||||
oneway void onResult(in byte[] data,in boolean isSuccess);
|
||||
}
|
||||
@@ -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);
|
||||
oneway void onEvent(in String id, in byte[] data,in boolean isSuccess);
|
||||
}
|
||||
@@ -17,10 +17,8 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.util.UUID
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
class RemoteService : Service(),
|
||||
CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
|
||||
@@ -77,22 +75,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,7 +89,6 @@ class RemoteService : Service(),
|
||||
State.notificationParamsFlow.tryEmit(params)
|
||||
}
|
||||
|
||||
|
||||
override fun startService(
|
||||
options: VpnOptions,
|
||||
runtime: Long,
|
||||
@@ -120,24 +106,12 @@ class RemoteService : Service(),
|
||||
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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
runCatching {
|
||||
val id = UUID.randomUUID().toString()
|
||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||
val totalSize = chunks.size
|
||||
chunks.forEachIndexed { index, chunk ->
|
||||
eventListener.onEvent(id, chunk, totalSize - 1 == index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Submodule core/Clash.Meta updated: 168fc4232c...573489787b
@@ -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))
|
||||
|
||||
40
core/go.mod
40
core/go.mod
@@ -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
|
||||
|
||||
91
core/go.sum
91
core/go.sum
@@ -22,10 +22,10 @@ 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=
|
||||
@@ -44,6 +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-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=
|
||||
@@ -58,15 +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/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/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=
|
||||
@@ -76,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=
|
||||
@@ -102,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=
|
||||
@@ -115,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=
|
||||
@@ -164,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=
|
||||
@@ -172,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=
|
||||
@@ -191,7 +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.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=
|
||||
@@ -205,7 +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/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=
|
||||
@@ -235,13 +240,16 @@ 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=
|
||||
@@ -255,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=
|
||||
|
||||
@@ -33,8 +33,6 @@ var (
|
||||
)
|
||||
|
||||
func handleInitClash(paramsString string) bool {
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
var params = InitParams{}
|
||||
err := json.Unmarshal([]byte(paramsString), ¶ms)
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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? {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -82,22 +82,8 @@ class AppController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> tryStartCore() async {
|
||||
if (coreController.isCompleted) {
|
||||
return;
|
||||
}
|
||||
globalState.isUserDisconnected = true;
|
||||
await _connectCore();
|
||||
await _initCore();
|
||||
_ref.read(initProvider.notifier).value = true;
|
||||
if (_ref.read(isStartProvider)) {
|
||||
await globalState.handleStart();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateStatus(bool isStart) async {
|
||||
if (isStart) {
|
||||
await globalState.appController.tryStartCore();
|
||||
await globalState.handleStart([updateRunTime, updateTraffic]);
|
||||
final currentLastModified = await _ref
|
||||
.read(currentProfileProvider)
|
||||
@@ -367,7 +353,7 @@ class AppController {
|
||||
try {
|
||||
await updateProfile(profile);
|
||||
} catch (e) {
|
||||
commonPrint.log(e.toString(), logLevel: LogLevel.warning);
|
||||
commonPrint.log(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -480,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'];
|
||||
@@ -540,7 +529,6 @@ class AppController {
|
||||
FlutterError.onError = (details) {
|
||||
commonPrint.log(
|
||||
'exception: ${details.exception} stack: ${details.stack}',
|
||||
logLevel: LogLevel.warning,
|
||||
);
|
||||
};
|
||||
updateTray(true);
|
||||
@@ -563,11 +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];
|
||||
final message = await coreController.preload();
|
||||
if (message.isNotEmpty) {
|
||||
_ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
|
||||
if (context.mounted) {
|
||||
@@ -972,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 {
|
||||
|
||||
@@ -29,8 +29,6 @@ class CoreController {
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
bool get isCompleted => _interface.completer.isCompleted;
|
||||
|
||||
Future<String> preload() {
|
||||
return _interface.preload();
|
||||
}
|
||||
@@ -73,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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -77,7 +76,7 @@ mixin CoreInterface {
|
||||
}
|
||||
|
||||
abstract class CoreHandlerInterface with CoreInterface {
|
||||
Completer get completer;
|
||||
Future get connected;
|
||||
|
||||
FutureOr<bool> destroy();
|
||||
|
||||
@@ -86,19 +85,8 @@ abstract class CoreHandlerInterface with CoreInterface {
|
||||
dynamic data,
|
||||
Duration? timeout,
|
||||
}) async {
|
||||
await completer.future;
|
||||
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');
|
||||
},
|
||||
);
|
||||
await connected;
|
||||
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
|
||||
|
||||
@@ -62,7 +62,7 @@ class CoreLib extends CoreHandlerInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Completer get completer => _connectedCompleter;
|
||||
Future get connected => _connectedCompleter.future;
|
||||
}
|
||||
|
||||
CoreLib? get coreLib => system.isAndroid ? CoreLib() : null;
|
||||
|
||||
@@ -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;
|
||||
@@ -186,7 +180,9 @@ class CoreService extends CoreHandlerInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Completer get completer => _socketCompleter;
|
||||
Future get connected {
|
||||
return _socketCompleter.future;
|
||||
}
|
||||
}
|
||||
|
||||
final coreService = system.isDesktop ? CoreService() : null;
|
||||
|
||||
@@ -33,18 +33,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
|
||||
|
||||
@@ -71,16 +71,16 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
|
||||
@override
|
||||
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
|
||||
commonPrint.log('$state');
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
if (state == AppLifecycleState.paused ||
|
||||
state == AppLifecycleState.inactive) {
|
||||
globalState.appController.savePreferences();
|
||||
} else {
|
||||
render?.resume();
|
||||
}
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
if (state == AppLifecycleState.inactive) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
detectionState.tryStartCheck();
|
||||
});
|
||||
if (system.isAndroid) {
|
||||
globalState.appController.tryStartCore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,8 +93,7 @@ class _CoreContainerState extends ConsumerState<CoreManager>
|
||||
|
||||
@override
|
||||
Future<void> onCrash(String message) async {
|
||||
if (!globalState.isUserDisconnected &&
|
||||
WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
|
||||
if (!globalState.isUserDisconnected) {
|
||||
context.showNotifier(message);
|
||||
}
|
||||
globalState.isUserDisconnected = false;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class _TileContainerState extends State<TileManager> with TileListener {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onStart() async {
|
||||
void onStart() {
|
||||
if (globalState.appState.isStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -239,7 +239,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 +304,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 +323,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 ?? '',
|
||||
|
||||
@@ -53,6 +53,7 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
|
||||
if (res != true) {
|
||||
return;
|
||||
}
|
||||
await Future.delayed(commonDuration);
|
||||
globalState.appController.restartCore();
|
||||
}
|
||||
|
||||
|
||||
@@ -34,11 +34,7 @@ class _MemoryInfoState extends State<MemoryInfo> {
|
||||
Future<void> _updateMemory() async {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
final rss = ProcessInfo.currentRss;
|
||||
if (coreController.isCompleted) {
|
||||
_memoryStateNotifier.value = await coreController.getMemory() + rss;
|
||||
} else {
|
||||
_memoryStateNotifier.value = rss;
|
||||
}
|
||||
_memoryStateNotifier.value = await coreController.getMemory() + rss;
|
||||
timer = Timer(Duration(seconds: 2), () async {
|
||||
_updateMemory();
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,9 +37,7 @@ class DeveloperView extends ConsumerWidget {
|
||||
),
|
||||
ListItem(
|
||||
title: Text(appLocalizations.crashTest),
|
||||
minVerticalPadding: 14,
|
||||
onTap: () {
|
||||
// coreController.crash();
|
||||
if (kDebugMode) {
|
||||
coreController.crash();
|
||||
}
|
||||
@@ -48,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,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
12
pubspec.lock
12
pubspec.lock
@@ -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:
|
||||
|
||||
@@ -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+2025100601
|
||||
version: 0.8.88+2025092001
|
||||
environment:
|
||||
sdk: '>=3.8.0 <4.0.0'
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user