Compare commits
1 Commits
v0.8.88-pr
...
v0.8.88-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9d22d7616 |
@@ -3,6 +3,7 @@ package com.follow.clash
|
||||
import com.follow.clash.common.ServiceDelegate
|
||||
import com.follow.clash.common.intent
|
||||
import com.follow.clash.service.ICallbackInterface
|
||||
import com.follow.clash.service.IMessageInterface
|
||||
import com.follow.clash.service.IRemoteInterface
|
||||
import com.follow.clash.service.RemoteService
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
@@ -36,12 +37,12 @@ object Service {
|
||||
}
|
||||
|
||||
suspend fun invokeAction(
|
||||
data: String, cb: (result: String?) -> Unit
|
||||
data: String, cb: (result: ByteArray?, isSuccess: Boolean) -> Unit
|
||||
) {
|
||||
delegate.useService {
|
||||
it.invokeAction(data, object : ICallbackInterface.Stub() {
|
||||
override fun onResult(result: String?) {
|
||||
cb(result)
|
||||
override fun onResult(result: ByteArray?, isSuccess: Boolean) {
|
||||
cb(result, isSuccess)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -59,7 +60,7 @@ object Service {
|
||||
cb: (result: String?) -> Unit
|
||||
) {
|
||||
delegate.useService {
|
||||
it.setMessageCallback(object : ICallbackInterface.Stub() {
|
||||
it.setMessageCallback(object : IMessageInterface.Stub() {
|
||||
override fun onResult(result: String?) {
|
||||
cb(result)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.follow.clash.Service
|
||||
import com.follow.clash.State
|
||||
import com.follow.clash.awaitResult
|
||||
import com.follow.clash.common.Components
|
||||
import com.follow.clash.common.formatString
|
||||
import com.follow.clash.invokeMethodOnMainThread
|
||||
import com.follow.clash.models.AppState
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
@@ -68,8 +69,12 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
private fun handleInvokeAction(call: MethodCall, result: MethodChannel.Result) {
|
||||
launch {
|
||||
val data = call.arguments<String>()!!
|
||||
Service.invokeAction(data) {
|
||||
result.success(it)
|
||||
val res = mutableListOf<ByteArray>()
|
||||
Service.invokeAction(data) { byteArray, isSuccess ->
|
||||
res.add(byteArray ?: byteArrayOf())
|
||||
if (isSuccess) {
|
||||
result.success(res.formatString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,7 +122,6 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
)
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun handleInit(result: MethodChannel.Result) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.util.Log
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import java.nio.charset.Charset
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
//fun Context.startForegroundServiceCompat(intent: Intent?) {
|
||||
@@ -201,4 +202,37 @@ val Long.formatBytes: String
|
||||
} else {
|
||||
"%.1f${units[unitIndex]}".format(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
total <= 10 * 1024 * 1024 -> 128 * 1024
|
||||
else -> 256 * 1024
|
||||
}
|
||||
|
||||
val result = mutableListOf<ByteArray>()
|
||||
var index = 0
|
||||
while (index < total) {
|
||||
val end = minOf(index + maxBytes, total)
|
||||
result.add(allBytes.copyOfRange(index, end))
|
||||
index = end
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
fun <T : List<ByteArray>> T.formatString(charset: Charset = Charsets.UTF_8): String {
|
||||
val totalSize = this.sumOf { it.size }
|
||||
val combined = ByteArray(totalSize)
|
||||
var offset = 0
|
||||
this.forEach { byteArray ->
|
||||
byteArray.copyInto(combined, offset)
|
||||
offset += byteArray.size
|
||||
}
|
||||
return String(combined, charset)
|
||||
}
|
||||
@@ -9,16 +9,17 @@
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb,
|
||||
jstring address, jstring dns) {
|
||||
jstring stack, jstring address, jstring dns) {
|
||||
const auto interface = new_global(cb);
|
||||
scoped_string stackChar = get_string(stack);
|
||||
scoped_string addressChar = get_string(address);
|
||||
scoped_string dnsChar = get_string(dns);
|
||||
startTUN(interface, fd, addressChar, dnsChar);
|
||||
startTUN(interface, fd, stackChar, addressChar, dnsChar);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_stopTun(JNIEnv *) {
|
||||
Java_com_follow_clash_core_Core_stopTun(JNIEnv *env, jobject thiz) {
|
||||
stopTun();
|
||||
}
|
||||
|
||||
@@ -97,12 +98,12 @@ call_tun_interface_resolve_process_impl(void *tun_interface, const int protocol,
|
||||
const int uid) {
|
||||
ATTACH_JNI();
|
||||
const auto packageName = reinterpret_cast<jstring>(env->CallObjectMethod(
|
||||
static_cast<jobject>(tun_interface),
|
||||
m_tun_interface_resolve_process,
|
||||
protocol,
|
||||
new_string(source),
|
||||
new_string(target),
|
||||
uid));
|
||||
static_cast<jobject>(tun_interface),
|
||||
m_tun_interface_resolve_process,
|
||||
protocol,
|
||||
new_string(source),
|
||||
new_string(target),
|
||||
uid));
|
||||
scoped_string packageNameChar = get_string(packageName);
|
||||
return packageNameChar;
|
||||
}
|
||||
@@ -146,7 +147,7 @@ JNI_OnLoad(JavaVM *vm, void *) {
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb,
|
||||
jstring address, jstring dns) {
|
||||
jstring stack, jstring address, jstring dns) {
|
||||
}
|
||||
|
||||
extern "C"
|
||||
|
||||
@@ -8,6 +8,7 @@ data object Core {
|
||||
private external fun startTun(
|
||||
fd: Int,
|
||||
cb: TunInterface,
|
||||
stack: String,
|
||||
address: String,
|
||||
dns: String,
|
||||
)
|
||||
@@ -29,6 +30,7 @@ data object Core {
|
||||
fd: Int,
|
||||
protect: (Int) -> Boolean,
|
||||
resolverProcess: (protocol: Int, source: InetSocketAddress, target: InetSocketAddress, uid: Int) -> String,
|
||||
stack: String,
|
||||
address: String,
|
||||
dns: String,
|
||||
) {
|
||||
@@ -53,6 +55,7 @@ data object Core {
|
||||
)
|
||||
}
|
||||
},
|
||||
stack,
|
||||
address,
|
||||
dns
|
||||
)
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface ICallbackInterface {
|
||||
void onResult(String result);
|
||||
void onResult(in byte[] result, boolean isSuccess);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// IMessageInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface IMessageInterface {
|
||||
void onResult(String result);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.follow.clash.service;
|
||||
|
||||
import com.follow.clash.service.ICallbackInterface;
|
||||
import com.follow.clash.service.IMessageInterface;
|
||||
import com.follow.clash.service.models.VpnOptions;
|
||||
import com.follow.clash.service.models.NotificationParams;
|
||||
|
||||
@@ -10,5 +11,5 @@ interface IRemoteInterface {
|
||||
void updateNotificationParams(in NotificationParams params);
|
||||
void startService(in VpnOptions options,in boolean inApp);
|
||||
void stopService();
|
||||
void setMessageCallback(in ICallbackInterface messageCallback);
|
||||
void setMessageCallback(in IMessageInterface messageCallback);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import com.follow.clash.common.ServiceDelegate
|
||||
import com.follow.clash.common.chunkedForAidl
|
||||
import com.follow.clash.common.intent
|
||||
import com.follow.clash.core.Core
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
@@ -57,7 +58,13 @@ class RemoteService : Service(),
|
||||
|
||||
private val binder: IRemoteInterface.Stub = object : IRemoteInterface.Stub() {
|
||||
override fun invokeAction(data: String, callback: ICallbackInterface) {
|
||||
Core.invokeAction(data, callback::onResult)
|
||||
Core.invokeAction(data) {
|
||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||
val totalSize = chunks.size
|
||||
chunks.forEachIndexed { index, chunk ->
|
||||
callback.onResult(chunk, totalSize - 1 == index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateNotificationParams(params: NotificationParams?) {
|
||||
@@ -76,7 +83,7 @@ class RemoteService : Service(),
|
||||
handleStopService()
|
||||
}
|
||||
|
||||
override fun setMessageCallback(messageCallback: ICallbackInterface) {
|
||||
override fun setMessageCallback(messageCallback: IMessageInterface) {
|
||||
setMessageCallback(messageCallback::onResult)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import android.util.Log
|
||||
import androidx.core.content.getSystemService
|
||||
import com.follow.clash.common.AccessControlMode
|
||||
import com.follow.clash.common.BroadcastAction
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.common.sendBroadcast
|
||||
import com.follow.clash.core.Core
|
||||
import com.follow.clash.service.models.VpnOptions
|
||||
@@ -108,7 +107,6 @@ class VpnService : SystemVpnService(), IBaseService,
|
||||
try {
|
||||
val isSuccess = super.onTransact(code, data, reply, flags)
|
||||
if (!isSuccess) {
|
||||
GlobalState.log("onTransact error ===>")
|
||||
BroadcastAction.STOP.sendBroadcast()
|
||||
}
|
||||
return isSuccess
|
||||
@@ -222,6 +220,7 @@ class VpnService : SystemVpnService(), IBaseService,
|
||||
fd,
|
||||
protect = this::protect,
|
||||
resolverProcess = this::resolverProcess,
|
||||
options.stack,
|
||||
options.address,
|
||||
options.dns
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ data class VpnOptions(
|
||||
val allowBypass: Boolean,
|
||||
val systemProxy: Boolean,
|
||||
val bypassDomain: List<String>,
|
||||
val stack: String,
|
||||
val routeAddress: List<String>,
|
||||
) : Parcelable
|
||||
|
||||
|
||||
@@ -25,18 +25,30 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.zip
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class ExtendedNotificationParams(
|
||||
val title: String,
|
||||
val stopText: String,
|
||||
val onlyStatisticsProxy: Boolean,
|
||||
val contentText: String,
|
||||
)
|
||||
|
||||
val NotificationParams.extended: ExtendedNotificationParams
|
||||
get() = ExtendedNotificationParams(
|
||||
title, stopText, onlyStatisticsProxy, Core.getSpeedTrafficText(onlyStatisticsProxy)
|
||||
)
|
||||
|
||||
class NotificationModule(private val service: Service) : Module() {
|
||||
private val scope = CoroutineScope(Dispatchers.Default)
|
||||
|
||||
override fun onInstall() {
|
||||
State.notificationParamsFlow.value?.let {
|
||||
update(it)
|
||||
update(it.extended)
|
||||
}
|
||||
scope.launch {
|
||||
val screenFlow = service.receiveBroadcastFlow {
|
||||
@@ -48,11 +60,12 @@ class NotificationModule(private val service: Service) : Module() {
|
||||
emit(isScreenOn())
|
||||
}
|
||||
|
||||
tickerFlow(1000, 0)
|
||||
.combine(State.notificationParamsFlow.zip(screenFlow) { params, screenOn ->
|
||||
params to screenOn
|
||||
}) { _, (params, screenOn) -> params to screenOn }
|
||||
.filter { (params, screenOn) -> params != null && screenOn }
|
||||
combine(
|
||||
tickerFlow(1000, 0), State.notificationParamsFlow, screenFlow
|
||||
) { _, params, screenOn ->
|
||||
params?.extended to screenOn
|
||||
}.filter { (params, screenOn) -> params != null && screenOn }
|
||||
.distinctUntilChanged { old, new -> old.first == new.first && old.second == new.second }
|
||||
.collect { (params, _) ->
|
||||
update(params!!)
|
||||
}
|
||||
@@ -87,18 +100,15 @@ class NotificationModule(private val service: Service) : Module() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun update(params: NotificationParams) {
|
||||
val contentText = Core.getSpeedTrafficText(params.onlyStatisticsProxy)
|
||||
private fun update(params: ExtendedNotificationParams) {
|
||||
service.startForeground(
|
||||
with(notificationBuilder) {
|
||||
setContentTitle(params.title)
|
||||
setContentText(contentText)
|
||||
setContentText(params.contentText)
|
||||
setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
clearActions()
|
||||
addAction(
|
||||
0,
|
||||
params.stopText,
|
||||
QuickAction.STOP.quickIntent.toPendingIntent
|
||||
0, params.stopText, QuickAction.STOP.quickIntent.toPendingIntent
|
||||
).build()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,12 +17,14 @@ import (
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
cp "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/hub"
|
||||
"github.com/metacubex/mihomo/hub/executor"
|
||||
"github.com/metacubex/mihomo/hub/route"
|
||||
"github.com/metacubex/mihomo/listener"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
rp "github.com/metacubex/mihomo/rules/provider"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -159,7 +161,6 @@ func patchSelectGroup(mapping map[string]string) {
|
||||
|
||||
func defaultSetupParams() *SetupParams {
|
||||
return &SetupParams{
|
||||
Config: config.DefaultRawConfig(),
|
||||
TestURL: "https://www.gstatic.com/generate_204",
|
||||
SelectedMap: map[string]string{},
|
||||
}
|
||||
@@ -240,7 +241,7 @@ func setupConfig(params *SetupParams) error {
|
||||
defer runLock.Unlock()
|
||||
var err error
|
||||
constant.DefaultTestURL = params.TestURL
|
||||
currentConfig, err = config.ParseRawConfig(params.Config)
|
||||
currentConfig, err = executor.ParseWithPath(filepath.Join(constant.Path.HomeDir(), "config.yaml"))
|
||||
if err != nil {
|
||||
currentConfig, _ = config.ParseRawConfig(config.DefaultRawConfig())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"github.com/metacubex/mihomo/adapter/provider"
|
||||
P "github.com/metacubex/mihomo/component/process"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
@@ -18,7 +17,6 @@ type InitParams struct {
|
||||
}
|
||||
|
||||
type SetupParams struct {
|
||||
Config *config.RawConfig `json:"config"`
|
||||
SelectedMap map[string]string `json:"selected-map"`
|
||||
TestURL string `json:"test-url"`
|
||||
}
|
||||
|
||||
12
core/lib.go
12
core/lib.go
@@ -36,11 +36,11 @@ type TunHandler struct {
|
||||
limit *semaphore.Weighted
|
||||
}
|
||||
|
||||
func (th *TunHandler) start(fd int, address, dns string) {
|
||||
func (th *TunHandler) start(fd int, stack, address, dns string) {
|
||||
_ = th.limit.Acquire(context.TODO(), 4)
|
||||
defer th.limit.Release(4)
|
||||
th.initHook()
|
||||
tunListener := t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack, address, dns)
|
||||
tunListener := t.Start(fd, stack, address, dns)
|
||||
if tunListener != nil {
|
||||
log.Infoln("TUN address: %v", tunListener.Address())
|
||||
th.listener = tunListener
|
||||
@@ -136,7 +136,7 @@ func handleStopTun() {
|
||||
}
|
||||
}
|
||||
|
||||
func handleStartTun(callback unsafe.Pointer, fd int, address, dns string) {
|
||||
func handleStartTun(callback unsafe.Pointer, fd int, stack, address, dns string) {
|
||||
handleStopTun()
|
||||
tunLock.Lock()
|
||||
defer tunLock.Unlock()
|
||||
@@ -145,7 +145,7 @@ func handleStartTun(callback unsafe.Pointer, fd int, address, dns string) {
|
||||
callback: callback,
|
||||
limit: semaphore.NewWeighted(4),
|
||||
}
|
||||
tunHandler.start(fd, address, dns)
|
||||
tunHandler.start(fd, stack, address, dns)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,8 +197,8 @@ func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
|
||||
}
|
||||
|
||||
//export startTUN
|
||||
func startTUN(callback unsafe.Pointer, fd C.int, addressChar, dnsChar *C.char) bool {
|
||||
handleStartTun(callback, int(fd), parseCString(addressChar), parseCString(dnsChar))
|
||||
func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool {
|
||||
handleStartTun(callback, int(fd), parseCString(stackChar), parseCString(addressChar), parseCString(dnsChar))
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,13 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Start(fd int, device string, stack constant.TUNStack, address, dns string) *sing_tun.Listener {
|
||||
func Start(fd int, stack string, address, dns string) *sing_tun.Listener {
|
||||
var prefix4 []netip.Prefix
|
||||
var prefix6 []netip.Prefix
|
||||
tunStack, ok := constant.StackTypeMapping[strings.ToLower(stack)]
|
||||
if !ok {
|
||||
tunStack = constant.TunSystem
|
||||
}
|
||||
for _, a := range strings.Split(address, ",") {
|
||||
a = strings.TrimSpace(a)
|
||||
if len(a) == 0 {
|
||||
@@ -45,8 +49,8 @@ func Start(fd int, device string, stack constant.TUNStack, address, dns string)
|
||||
|
||||
options := LC.Tun{
|
||||
Enable: true,
|
||||
Device: device,
|
||||
Stack: stack,
|
||||
Device: "FlClash",
|
||||
Stack: tunStack,
|
||||
DNSHijack: dnsHijack,
|
||||
AutoRoute: false,
|
||||
AutoDetectInterface: false,
|
||||
|
||||
@@ -58,8 +58,13 @@ class AppPath {
|
||||
}
|
||||
|
||||
Future<String> get lockFilePath async {
|
||||
final directory = await dataDir.future;
|
||||
return join(directory.path, 'FlClash.lock');
|
||||
final homeDirPath = await appPath.homeDirPath;
|
||||
return join(homeDirPath, 'FlClash.lock');
|
||||
}
|
||||
|
||||
Future<String> get configFilePath async {
|
||||
final homeDirPath = await appPath.homeDirPath;
|
||||
return join(homeDirPath, 'config.yaml');
|
||||
}
|
||||
|
||||
Future<String> get sharedPreferencesPath async {
|
||||
|
||||
@@ -303,10 +303,7 @@ class AppController {
|
||||
}
|
||||
final realTunEnable = _ref.read(realTunEnableProvider);
|
||||
final realPatchConfig = patchConfig.copyWith.tun(enable: realTunEnable);
|
||||
final params = await globalState.getSetupParams(
|
||||
pathConfig: realPatchConfig,
|
||||
);
|
||||
final message = await coreController.setupConfig(params);
|
||||
final message = await coreController.setupConfig(realPatchConfig);
|
||||
lastProfileModified = await _ref.read(
|
||||
currentProfileProvider.select((state) => state?.profileLastModified),
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:fl_clash/core/core.dart';
|
||||
import 'package:fl_clash/core/interface.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
@@ -83,8 +84,10 @@ class CoreController {
|
||||
return await _interface.updateConfig(updateParams);
|
||||
}
|
||||
|
||||
Future<String> setupConfig(SetupParams setupParams) async {
|
||||
return await _interface.setupConfig(setupParams);
|
||||
Future<String> setupConfig(ClashConfig clashConfig) async {
|
||||
await globalState.genConfigFile(clashConfig);
|
||||
final params = await globalState.getSetupParams();
|
||||
return await _interface.setupConfig(params);
|
||||
}
|
||||
|
||||
Future<List<Group>> getProxiesGroups() async {
|
||||
@@ -154,9 +157,6 @@ class CoreController {
|
||||
if (externalProvidersRawString.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
if (externalProvidersRawString.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return ExternalProvider.fromJson(json.decode(externalProvidersRawString));
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ abstract class CoreHandlerInterface with CoreInterface {
|
||||
|
||||
@override
|
||||
Future<Map> getProxies() async {
|
||||
var map = await _invoke<Map>(method: ActionMethod.getProxies);
|
||||
final map = await _invoke<Map>(method: ActionMethod.getProxies);
|
||||
return map ?? {};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/color.dart';
|
||||
import 'package:fl_clash/common/system.dart';
|
||||
import 'package:fl_clash/views/dashboard/widgets/widgets.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
@@ -95,7 +96,7 @@ extension LogLevelExt on LogLevel {
|
||||
LogLevel.silent => Colors.grey.shade700,
|
||||
LogLevel.debug => Colors.grey.shade400,
|
||||
LogLevel.info => null,
|
||||
LogLevel.warning => Colors.yellowAccent,
|
||||
LogLevel.warning => Colors.orangeAccent.darken(),
|
||||
LogLevel.error => Colors.redAccent,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,9 +40,8 @@ Future<void> _service(List<String> flags) async {
|
||||
final clashConfig = globalState.config.patchClashConfig.copyWith.tun(
|
||||
enable: false,
|
||||
);
|
||||
final params = await globalState.getSetupParams(pathConfig: clashConfig);
|
||||
await coreController.setupConfig(params);
|
||||
await globalState.handleStart();
|
||||
await coreController.setupConfig(clashConfig);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ part 'generated/core.g.dart';
|
||||
@freezed
|
||||
abstract class SetupParams with _$SetupParams {
|
||||
const factory SetupParams({
|
||||
@JsonKey(name: 'config') required Map<String, dynamic> config,
|
||||
@JsonKey(name: 'selected-map') required Map<String, String> selectedMap,
|
||||
@JsonKey(name: 'test-url') required String testUrl,
|
||||
}) = _SetupParams;
|
||||
@@ -51,6 +50,7 @@ abstract class VpnOptions with _$VpnOptions {
|
||||
required bool allowBypass,
|
||||
required bool systemProxy,
|
||||
required List<String> bypassDomain,
|
||||
required String stack,
|
||||
@Default([]) List<String> routeAddress,
|
||||
}) = _VpnOptions;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$SetupParams {
|
||||
|
||||
@JsonKey(name: 'config') Map<String, dynamic> get config;@JsonKey(name: 'selected-map') Map<String, String> get selectedMap;@JsonKey(name: 'test-url') String get testUrl;
|
||||
@JsonKey(name: 'selected-map') Map<String, String> get selectedMap;@JsonKey(name: 'test-url') String get testUrl;
|
||||
/// Create a copy of SetupParams
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -28,16 +28,16 @@ $SetupParamsCopyWith<SetupParams> get copyWith => _$SetupParamsCopyWithImpl<Setu
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SetupParams&&const DeepCollectionEquality().equals(other.config, config)&&const DeepCollectionEquality().equals(other.selectedMap, selectedMap)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SetupParams&&const DeepCollectionEquality().equals(other.selectedMap, selectedMap)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(config),const DeepCollectionEquality().hash(selectedMap),testUrl);
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(selectedMap),testUrl);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SetupParams(config: $config, selectedMap: $selectedMap, testUrl: $testUrl)';
|
||||
return 'SetupParams(selectedMap: $selectedMap, testUrl: $testUrl)';
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ abstract mixin class $SetupParamsCopyWith<$Res> {
|
||||
factory $SetupParamsCopyWith(SetupParams value, $Res Function(SetupParams) _then) = _$SetupParamsCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
@JsonKey(name: 'config') Map<String, dynamic> config,@JsonKey(name: 'selected-map') Map<String, String> selectedMap,@JsonKey(name: 'test-url') String testUrl
|
||||
@JsonKey(name: 'selected-map') Map<String, String> selectedMap,@JsonKey(name: 'test-url') String testUrl
|
||||
});
|
||||
|
||||
|
||||
@@ -65,10 +65,9 @@ class _$SetupParamsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SetupParams
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? config = null,Object? selectedMap = null,Object? testUrl = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? selectedMap = null,Object? testUrl = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,selectedMap: null == selectedMap ? _self.selectedMap : selectedMap // ignore: cast_nullable_to_non_nullable
|
||||
selectedMap: null == selectedMap ? _self.selectedMap : selectedMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>,testUrl: null == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
@@ -155,10 +154,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(name: 'config') Map<String, dynamic> config, @JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SetupParams() when $default != null:
|
||||
return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
|
||||
return $default(_that.selectedMap,_that.testUrl);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -176,10 +175,10 @@ return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(name: 'config') Map<String, dynamic> config, @JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SetupParams():
|
||||
return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
|
||||
return $default(_that.selectedMap,_that.testUrl);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -196,10 +195,10 @@ return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(name: 'config') Map<String, dynamic> config, @JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SetupParams() when $default != null:
|
||||
return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
|
||||
return $default(_that.selectedMap,_that.testUrl);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -211,16 +210,9 @@ return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
|
||||
@JsonSerializable()
|
||||
|
||||
class _SetupParams implements SetupParams {
|
||||
const _SetupParams({@JsonKey(name: 'config') required final Map<String, dynamic> config, @JsonKey(name: 'selected-map') required final Map<String, String> selectedMap, @JsonKey(name: 'test-url') required this.testUrl}): _config = config,_selectedMap = selectedMap;
|
||||
const _SetupParams({@JsonKey(name: 'selected-map') required final Map<String, String> selectedMap, @JsonKey(name: 'test-url') required this.testUrl}): _selectedMap = selectedMap;
|
||||
factory _SetupParams.fromJson(Map<String, dynamic> json) => _$SetupParamsFromJson(json);
|
||||
|
||||
final Map<String, dynamic> _config;
|
||||
@override@JsonKey(name: 'config') Map<String, dynamic> get config {
|
||||
if (_config is EqualUnmodifiableMapView) return _config;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_config);
|
||||
}
|
||||
|
||||
final Map<String, String> _selectedMap;
|
||||
@override@JsonKey(name: 'selected-map') Map<String, String> get selectedMap {
|
||||
if (_selectedMap is EqualUnmodifiableMapView) return _selectedMap;
|
||||
@@ -243,16 +235,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SetupParams&&const DeepCollectionEquality().equals(other._config, _config)&&const DeepCollectionEquality().equals(other._selectedMap, _selectedMap)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SetupParams&&const DeepCollectionEquality().equals(other._selectedMap, _selectedMap)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_config),const DeepCollectionEquality().hash(_selectedMap),testUrl);
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_selectedMap),testUrl);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SetupParams(config: $config, selectedMap: $selectedMap, testUrl: $testUrl)';
|
||||
return 'SetupParams(selectedMap: $selectedMap, testUrl: $testUrl)';
|
||||
}
|
||||
|
||||
|
||||
@@ -263,7 +255,7 @@ abstract mixin class _$SetupParamsCopyWith<$Res> implements $SetupParamsCopyWith
|
||||
factory _$SetupParamsCopyWith(_SetupParams value, $Res Function(_SetupParams) _then) = __$SetupParamsCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
@JsonKey(name: 'config') Map<String, dynamic> config,@JsonKey(name: 'selected-map') Map<String, String> selectedMap,@JsonKey(name: 'test-url') String testUrl
|
||||
@JsonKey(name: 'selected-map') Map<String, String> selectedMap,@JsonKey(name: 'test-url') String testUrl
|
||||
});
|
||||
|
||||
|
||||
@@ -280,10 +272,9 @@ class __$SetupParamsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SetupParams
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? config = null,Object? selectedMap = null,Object? testUrl = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? selectedMap = null,Object? testUrl = null,}) {
|
||||
return _then(_SetupParams(
|
||||
config: null == config ? _self._config : config // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,selectedMap: null == selectedMap ? _self._selectedMap : selectedMap // ignore: cast_nullable_to_non_nullable
|
||||
selectedMap: null == selectedMap ? _self._selectedMap : selectedMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>,testUrl: null == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
@@ -604,7 +595,7 @@ $TunCopyWith<$Res> get tun {
|
||||
/// @nodoc
|
||||
mixin _$VpnOptions {
|
||||
|
||||
bool get enable; int get port; bool get ipv6; bool get dnsHijacking; AccessControl get accessControl; bool get allowBypass; bool get systemProxy; List<String> get bypassDomain; List<String> get routeAddress;
|
||||
bool get enable; int get port; bool get ipv6; bool get dnsHijacking; AccessControl get accessControl; bool get allowBypass; bool get systemProxy; List<String> get bypassDomain; String get stack; List<String> get routeAddress;
|
||||
/// Create a copy of VpnOptions
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -617,16 +608,16 @@ $VpnOptionsCopyWith<VpnOptions> get copyWith => _$VpnOptionsCopyWithImpl<VpnOpti
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is VpnOptions&&(identical(other.enable, enable) || other.enable == enable)&&(identical(other.port, port) || other.port == port)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.dnsHijacking, dnsHijacking) || other.dnsHijacking == dnsHijacking)&&(identical(other.accessControl, accessControl) || other.accessControl == accessControl)&&(identical(other.allowBypass, allowBypass) || other.allowBypass == allowBypass)&&(identical(other.systemProxy, systemProxy) || other.systemProxy == systemProxy)&&const DeepCollectionEquality().equals(other.bypassDomain, bypassDomain)&&const DeepCollectionEquality().equals(other.routeAddress, routeAddress));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is VpnOptions&&(identical(other.enable, enable) || other.enable == enable)&&(identical(other.port, port) || other.port == port)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.dnsHijacking, dnsHijacking) || other.dnsHijacking == dnsHijacking)&&(identical(other.accessControl, accessControl) || other.accessControl == accessControl)&&(identical(other.allowBypass, allowBypass) || other.allowBypass == allowBypass)&&(identical(other.systemProxy, systemProxy) || other.systemProxy == systemProxy)&&const DeepCollectionEquality().equals(other.bypassDomain, bypassDomain)&&(identical(other.stack, stack) || other.stack == stack)&&const DeepCollectionEquality().equals(other.routeAddress, routeAddress));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,enable,port,ipv6,dnsHijacking,accessControl,allowBypass,systemProxy,const DeepCollectionEquality().hash(bypassDomain),const DeepCollectionEquality().hash(routeAddress));
|
||||
int get hashCode => Object.hash(runtimeType,enable,port,ipv6,dnsHijacking,accessControl,allowBypass,systemProxy,const DeepCollectionEquality().hash(bypassDomain),stack,const DeepCollectionEquality().hash(routeAddress));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VpnOptions(enable: $enable, port: $port, ipv6: $ipv6, dnsHijacking: $dnsHijacking, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, routeAddress: $routeAddress)';
|
||||
return 'VpnOptions(enable: $enable, port: $port, ipv6: $ipv6, dnsHijacking: $dnsHijacking, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, stack: $stack, routeAddress: $routeAddress)';
|
||||
}
|
||||
|
||||
|
||||
@@ -637,7 +628,7 @@ abstract mixin class $VpnOptionsCopyWith<$Res> {
|
||||
factory $VpnOptionsCopyWith(VpnOptions value, $Res Function(VpnOptions) _then) = _$VpnOptionsCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress
|
||||
bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress
|
||||
});
|
||||
|
||||
|
||||
@@ -654,7 +645,7 @@ class _$VpnOptionsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of VpnOptions
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? enable = null,Object? port = null,Object? ipv6 = null,Object? dnsHijacking = null,Object? accessControl = null,Object? allowBypass = null,Object? systemProxy = null,Object? bypassDomain = null,Object? routeAddress = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? enable = null,Object? port = null,Object? ipv6 = null,Object? dnsHijacking = null,Object? accessControl = null,Object? allowBypass = null,Object? systemProxy = null,Object? bypassDomain = null,Object? stack = null,Object? routeAddress = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
enable: null == enable ? _self.enable : enable // ignore: cast_nullable_to_non_nullable
|
||||
as bool,port: null == port ? _self.port : port // ignore: cast_nullable_to_non_nullable
|
||||
@@ -664,7 +655,8 @@ as bool,accessControl: null == accessControl ? _self.accessControl : accessContr
|
||||
as AccessControl,allowBypass: null == allowBypass ? _self.allowBypass : allowBypass // ignore: cast_nullable_to_non_nullable
|
||||
as bool,systemProxy: null == systemProxy ? _self.systemProxy : systemProxy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,bypassDomain: null == bypassDomain ? _self.bypassDomain : bypassDomain // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,routeAddress: null == routeAddress ? _self.routeAddress : routeAddress // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,stack: null == stack ? _self.stack : stack // ignore: cast_nullable_to_non_nullable
|
||||
as String,routeAddress: null == routeAddress ? _self.routeAddress : routeAddress // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
));
|
||||
}
|
||||
@@ -759,10 +751,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _VpnOptions() when $default != null:
|
||||
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.routeAddress);case _:
|
||||
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.stack,_that.routeAddress);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -780,10 +772,10 @@ return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.acce
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _VpnOptions():
|
||||
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.routeAddress);case _:
|
||||
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.stack,_that.routeAddress);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -800,10 +792,10 @@ return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.acce
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _VpnOptions() when $default != null:
|
||||
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.routeAddress);case _:
|
||||
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.stack,_that.routeAddress);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -815,7 +807,7 @@ return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.acce
|
||||
@JsonSerializable()
|
||||
|
||||
class _VpnOptions implements VpnOptions {
|
||||
const _VpnOptions({required this.enable, required this.port, required this.ipv6, required this.dnsHijacking, required this.accessControl, required this.allowBypass, required this.systemProxy, required final List<String> bypassDomain, final List<String> routeAddress = const []}): _bypassDomain = bypassDomain,_routeAddress = routeAddress;
|
||||
const _VpnOptions({required this.enable, required this.port, required this.ipv6, required this.dnsHijacking, required this.accessControl, required this.allowBypass, required this.systemProxy, required final List<String> bypassDomain, required this.stack, final List<String> routeAddress = const []}): _bypassDomain = bypassDomain,_routeAddress = routeAddress;
|
||||
factory _VpnOptions.fromJson(Map<String, dynamic> json) => _$VpnOptionsFromJson(json);
|
||||
|
||||
@override final bool enable;
|
||||
@@ -832,6 +824,7 @@ class _VpnOptions implements VpnOptions {
|
||||
return EqualUnmodifiableListView(_bypassDomain);
|
||||
}
|
||||
|
||||
@override final String stack;
|
||||
final List<String> _routeAddress;
|
||||
@override@JsonKey() List<String> get routeAddress {
|
||||
if (_routeAddress is EqualUnmodifiableListView) return _routeAddress;
|
||||
@@ -853,16 +846,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _VpnOptions&&(identical(other.enable, enable) || other.enable == enable)&&(identical(other.port, port) || other.port == port)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.dnsHijacking, dnsHijacking) || other.dnsHijacking == dnsHijacking)&&(identical(other.accessControl, accessControl) || other.accessControl == accessControl)&&(identical(other.allowBypass, allowBypass) || other.allowBypass == allowBypass)&&(identical(other.systemProxy, systemProxy) || other.systemProxy == systemProxy)&&const DeepCollectionEquality().equals(other._bypassDomain, _bypassDomain)&&const DeepCollectionEquality().equals(other._routeAddress, _routeAddress));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _VpnOptions&&(identical(other.enable, enable) || other.enable == enable)&&(identical(other.port, port) || other.port == port)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.dnsHijacking, dnsHijacking) || other.dnsHijacking == dnsHijacking)&&(identical(other.accessControl, accessControl) || other.accessControl == accessControl)&&(identical(other.allowBypass, allowBypass) || other.allowBypass == allowBypass)&&(identical(other.systemProxy, systemProxy) || other.systemProxy == systemProxy)&&const DeepCollectionEquality().equals(other._bypassDomain, _bypassDomain)&&(identical(other.stack, stack) || other.stack == stack)&&const DeepCollectionEquality().equals(other._routeAddress, _routeAddress));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,enable,port,ipv6,dnsHijacking,accessControl,allowBypass,systemProxy,const DeepCollectionEquality().hash(_bypassDomain),const DeepCollectionEquality().hash(_routeAddress));
|
||||
int get hashCode => Object.hash(runtimeType,enable,port,ipv6,dnsHijacking,accessControl,allowBypass,systemProxy,const DeepCollectionEquality().hash(_bypassDomain),stack,const DeepCollectionEquality().hash(_routeAddress));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VpnOptions(enable: $enable, port: $port, ipv6: $ipv6, dnsHijacking: $dnsHijacking, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, routeAddress: $routeAddress)';
|
||||
return 'VpnOptions(enable: $enable, port: $port, ipv6: $ipv6, dnsHijacking: $dnsHijacking, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, stack: $stack, routeAddress: $routeAddress)';
|
||||
}
|
||||
|
||||
|
||||
@@ -873,7 +866,7 @@ abstract mixin class _$VpnOptionsCopyWith<$Res> implements $VpnOptionsCopyWith<$
|
||||
factory _$VpnOptionsCopyWith(_VpnOptions value, $Res Function(_VpnOptions) _then) = __$VpnOptionsCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress
|
||||
bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress
|
||||
});
|
||||
|
||||
|
||||
@@ -890,7 +883,7 @@ class __$VpnOptionsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of VpnOptions
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? enable = null,Object? port = null,Object? ipv6 = null,Object? dnsHijacking = null,Object? accessControl = null,Object? allowBypass = null,Object? systemProxy = null,Object? bypassDomain = null,Object? routeAddress = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? enable = null,Object? port = null,Object? ipv6 = null,Object? dnsHijacking = null,Object? accessControl = null,Object? allowBypass = null,Object? systemProxy = null,Object? bypassDomain = null,Object? stack = null,Object? routeAddress = null,}) {
|
||||
return _then(_VpnOptions(
|
||||
enable: null == enable ? _self.enable : enable // ignore: cast_nullable_to_non_nullable
|
||||
as bool,port: null == port ? _self.port : port // ignore: cast_nullable_to_non_nullable
|
||||
@@ -900,7 +893,8 @@ as bool,accessControl: null == accessControl ? _self.accessControl : accessContr
|
||||
as AccessControl,allowBypass: null == allowBypass ? _self.allowBypass : allowBypass // ignore: cast_nullable_to_non_nullable
|
||||
as bool,systemProxy: null == systemProxy ? _self.systemProxy : systemProxy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,bypassDomain: null == bypassDomain ? _self._bypassDomain : bypassDomain // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,routeAddress: null == routeAddress ? _self._routeAddress : routeAddress // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,stack: null == stack ? _self.stack : stack // ignore: cast_nullable_to_non_nullable
|
||||
as String,routeAddress: null == routeAddress ? _self._routeAddress : routeAddress // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -7,14 +7,12 @@ part of '../core.dart';
|
||||
// **************************************************************************
|
||||
|
||||
_SetupParams _$SetupParamsFromJson(Map<String, dynamic> json) => _SetupParams(
|
||||
config: json['config'] as Map<String, dynamic>,
|
||||
selectedMap: Map<String, String>.from(json['selected-map'] as Map),
|
||||
testUrl: json['test-url'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SetupParamsToJson(_SetupParams instance) =>
|
||||
<String, dynamic>{
|
||||
'config': instance.config,
|
||||
'selected-map': instance.selectedMap,
|
||||
'test-url': instance.testUrl,
|
||||
};
|
||||
@@ -91,6 +89,7 @@ _VpnOptions _$VpnOptionsFromJson(Map<String, dynamic> json) => _VpnOptions(
|
||||
bypassDomain: (json['bypassDomain'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList(),
|
||||
stack: json['stack'] as String,
|
||||
routeAddress:
|
||||
(json['routeAddress'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
@@ -108,6 +107,7 @@ Map<String, dynamic> _$VpnOptionsToJson(_VpnOptions instance) =>
|
||||
'allowBypass': instance.allowBypass,
|
||||
'systemProxy': instance.systemProxy,
|
||||
'bypassDomain': instance.bypassDomain,
|
||||
'stack': instance.stack,
|
||||
'routeAddress': instance.routeAddress,
|
||||
};
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ class Requests extends _$Requests with AutoDisposeNotifierMixin {
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
@Riverpod(keepAlive: true)
|
||||
class Providers extends _$Providers with AutoDisposeNotifierMixin {
|
||||
@override
|
||||
List<ExternalProvider> build() {
|
||||
@@ -67,6 +67,9 @@ class Providers extends _$Providers with AutoDisposeNotifierMixin {
|
||||
}
|
||||
|
||||
void setProvider(ExternalProvider? provider) {
|
||||
if (!ref.mounted) {
|
||||
return;
|
||||
}
|
||||
if (provider == null) return;
|
||||
final index = state.indexWhere((item) => item.name == provider.name);
|
||||
if (index == -1) return;
|
||||
|
||||
@@ -90,7 +90,7 @@ final class LogsProvider extends $NotifierProvider<Logs, FixedList<Log>> {
|
||||
}
|
||||
}
|
||||
|
||||
String _$logsHash() => r'0a32e067292d449d61af59a689cb26691f4afe44';
|
||||
String _$logsHash() => r'a671cf70f13d38cae75dc51841b651fe2d2dad9a';
|
||||
|
||||
abstract class _$Logs extends $Notifier<FixedList<Log>> {
|
||||
FixedList<Log> build();
|
||||
@@ -143,7 +143,7 @@ final class RequestsProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$requestsHash() => r'526f2c1da1347fd2e6e3e23aac0335fcfe0cb28a';
|
||||
String _$requestsHash() => r'8642621b8b5f2e56f3abb04554c058fb30389795';
|
||||
|
||||
abstract class _$Requests extends $Notifier<FixedList<TrackerInfo>> {
|
||||
FixedList<TrackerInfo> build();
|
||||
@@ -176,7 +176,7 @@ final class ProvidersProvider
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'providersProvider',
|
||||
isAutoDispose: true,
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
@@ -197,7 +197,7 @@ final class ProvidersProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$providersHash() => r'4292240629a99470b2e72426dde3b9049b9b57e0';
|
||||
String _$providersHash() => r'b1175e1e2b22e34f8d414386fe672c4933fc47a3';
|
||||
|
||||
abstract class _$Providers extends $Notifier<List<ExternalProvider>> {
|
||||
List<ExternalProvider> build();
|
||||
@@ -304,7 +304,7 @@ final class SystemBrightnessProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$systemBrightnessHash() => r'46eb2d23b05405723efc29480e8f258bf2d8138b';
|
||||
String _$systemBrightnessHash() => r'2fb112459d5f505768f8c33b314aa62cf1fb0a0a';
|
||||
|
||||
abstract class _$SystemBrightness extends $Notifier<Brightness> {
|
||||
Brightness build();
|
||||
@@ -357,7 +357,7 @@ final class TrafficsProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$trafficsHash() => r'8b86eb718fed5776de174c51fd5b231957011fe6';
|
||||
String _$trafficsHash() => r'7df7d01f39e9fa1bf629221c9f73273757fa535a';
|
||||
|
||||
abstract class _$Traffics extends $Notifier<FixedList<Traffic>> {
|
||||
FixedList<Traffic> build();
|
||||
@@ -462,7 +462,7 @@ final class LocalIpProvider extends $NotifierProvider<LocalIp, String?> {
|
||||
}
|
||||
}
|
||||
|
||||
String _$localIpHash() => r'2dd4afdb29db4791ebd80d976f9ea31c62959199';
|
||||
String _$localIpHash() => r'25ff07ff9ae316eac7ef39c29d9ae2714b7ba323';
|
||||
|
||||
abstract class _$LocalIp extends $Notifier<String?> {
|
||||
String? build();
|
||||
@@ -1199,7 +1199,7 @@ final class DelayDataSourceProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$delayDataSourceHash() => r'1b94dcfdb9e1eb4c0b7ca69d933f2299d1f94ed5';
|
||||
String _$delayDataSourceHash() => r'0cc7064c6e7e7a1823df1c5b339001ae49ee54f1';
|
||||
|
||||
abstract class _$DelayDataSource extends $Notifier<DelayMap> {
|
||||
DelayMap build();
|
||||
@@ -1308,7 +1308,7 @@ final class ProfileOverrideStateProvider
|
||||
}
|
||||
|
||||
String _$profileOverrideStateHash() =>
|
||||
r'c57bb59c3900b6ee752d6c1cd5c9c0ccc6d1f45e';
|
||||
r'6bcf739e034cc39623dc63bf304189d63fc19404';
|
||||
|
||||
abstract class _$ProfileOverrideState extends $Notifier<ProfileOverrideModel?> {
|
||||
ProfileOverrideModel? build();
|
||||
@@ -1414,7 +1414,7 @@ final class QueryMapProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$queryMapHash() => r'102d489d31d312f082d7117f80a6de8318eaaf75';
|
||||
String _$queryMapHash() => r'f64a1bf5fcd4f85986d8ba3c956e397abc4f2d5d';
|
||||
|
||||
abstract class _$QueryMap extends $Notifier<Map<QueryTag, String>> {
|
||||
Map<QueryTag, String> build();
|
||||
|
||||
@@ -38,7 +38,7 @@ final class AppSettingProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$appSettingHash() => r'13a93334e18b97f5d52eb3e05bbc7b0b8a5c453e';
|
||||
String _$appSettingHash() => r'7ec7fbf146e690dea42cf854fa4452b2652d8a46';
|
||||
|
||||
abstract class _$AppSetting extends $Notifier<AppSettingProps> {
|
||||
AppSettingProps build();
|
||||
@@ -91,7 +91,7 @@ final class WindowSettingProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$windowSettingHash() => r'9bf31c7e08fab84213f31e249270f9d730bdf711';
|
||||
String _$windowSettingHash() => r'fc0e5c4ec95a57a24e0e656fc2fab6f31add31e7';
|
||||
|
||||
abstract class _$WindowSetting extends $Notifier<WindowProps> {
|
||||
WindowProps build();
|
||||
@@ -143,7 +143,7 @@ final class VpnSettingProvider extends $NotifierProvider<VpnSetting, VpnProps> {
|
||||
}
|
||||
}
|
||||
|
||||
String _$vpnSettingHash() => r'3dae8b56504bfb906aca546c5a5389d79d259a5e';
|
||||
String _$vpnSettingHash() => r'1dad4881ae7bcec76678585ac7b84f820b2ca92b';
|
||||
|
||||
abstract class _$VpnSetting extends $Notifier<VpnProps> {
|
||||
VpnProps build();
|
||||
@@ -196,7 +196,7 @@ final class NetworkSettingProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$networkSettingHash() => r'5a30d4cbfaba94cc29ad08dc1771ebb368b4ba14';
|
||||
String _$networkSettingHash() => r'6ac5959ad478247fd60329221743cccc7a7d010b';
|
||||
|
||||
abstract class _$NetworkSetting extends $Notifier<NetworkProps> {
|
||||
NetworkProps build();
|
||||
@@ -249,7 +249,7 @@ final class ThemeSettingProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$themeSettingHash() => r'0b5620b696d73260d94f63cbfb65857acd2000f0';
|
||||
String _$themeSettingHash() => r'0ddad89cb63fc2b2094dd82262c76d972c2def5c';
|
||||
|
||||
abstract class _$ThemeSetting extends $Notifier<ThemeProps> {
|
||||
ThemeProps build();
|
||||
@@ -302,7 +302,7 @@ final class ProfilesProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$profilesHash() => r'3203cc7de88b91fff86b79c75c2cacd8116fffb7';
|
||||
String _$profilesHash() => r'aad57222a4a0bd16f2c70f9eb8ba0053d1a26d0f';
|
||||
|
||||
abstract class _$Profiles extends $Notifier<List<Profile>> {
|
||||
List<Profile> build();
|
||||
@@ -408,7 +408,7 @@ final class AppDAVSettingProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$appDAVSettingHash() => r'4bf293ac0d1fba157f60df920b7ffd5afefaab26';
|
||||
String _$appDAVSettingHash() => r'fa8de5d89d7a11f34f3f8e20b71cf164e5e11888';
|
||||
|
||||
abstract class _$AppDAVSetting extends $Notifier<DAV?> {
|
||||
DAV? build();
|
||||
@@ -567,7 +567,7 @@ final class ProxiesStyleSettingProvider
|
||||
}
|
||||
|
||||
String _$proxiesStyleSettingHash() =>
|
||||
r'54ebf20a8d4455b2d7a65824f375c4c02a5fba28';
|
||||
r'4ff62951ddc8289220191850516b6751ee69d642';
|
||||
|
||||
abstract class _$ProxiesStyleSetting extends $Notifier<ProxiesStyle> {
|
||||
ProxiesStyle build();
|
||||
@@ -620,7 +620,7 @@ final class ScriptStateProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$scriptStateHash() => r'afbb70d1dd7e577b2377ecd8ab35d0905c1d0e87';
|
||||
String _$scriptStateHash() => r'4770c34c3d24451fef95e372450e4a333b419977';
|
||||
|
||||
abstract class _$ScriptState extends $Notifier<ScriptProps> {
|
||||
ScriptProps build();
|
||||
@@ -673,7 +673,7 @@ final class PatchClashConfigProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$patchClashConfigHash() => r'd9acdd0ace673fc1c1460b63d7a27c5787713c14';
|
||||
String _$patchClashConfigHash() => r'b355bd89969d4d119631fdf117df230a71493fa8';
|
||||
|
||||
abstract class _$PatchClashConfig extends $Notifier<ClashConfig> {
|
||||
ClashConfig build();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi' show Pointer;
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
@@ -17,6 +19,7 @@ import 'package:flutter_js/flutter_js.dart';
|
||||
import 'package:material_color_utilities/palettes/core_palette.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:yaml_writer/yaml_writer.dart';
|
||||
|
||||
import 'common/common.dart';
|
||||
import 'controller.dart';
|
||||
@@ -199,6 +202,7 @@ class GlobalState {
|
||||
final networkProps = config.networkProps;
|
||||
final port = config.patchClashConfig.mixedPort;
|
||||
return VpnOptions(
|
||||
stack: config.patchClashConfig.tun.stack.name,
|
||||
enable: vpnProps.enable,
|
||||
systemProxy: networkProps.systemProxy,
|
||||
port: port,
|
||||
@@ -253,16 +257,35 @@ class GlobalState {
|
||||
}
|
||||
}
|
||||
|
||||
Future<SetupParams> getSetupParams({required ClashConfig pathConfig}) async {
|
||||
final clashConfig = await patchRawConfig(patchConfig: pathConfig);
|
||||
Future<SetupParams> getSetupParams() async {
|
||||
final params = SetupParams(
|
||||
config: clashConfig,
|
||||
selectedMap: config.currentProfile?.selectedMap ?? {},
|
||||
testUrl: config.appSetting.testUrl,
|
||||
);
|
||||
return params;
|
||||
}
|
||||
|
||||
Future<void> genConfigFile(ClashConfig pathConfig) async {
|
||||
final configFilePath = await appPath.configFilePath;
|
||||
final config = await patchRawConfig(patchConfig: pathConfig);
|
||||
final res = await Isolate.run<String>(() async {
|
||||
try {
|
||||
final data = YamlWriter().write(config);
|
||||
final file = File(configFilePath);
|
||||
if (!await file.exists()) {
|
||||
await file.create(recursive: true);
|
||||
}
|
||||
await file.writeAsString(data, flush: true);
|
||||
return '';
|
||||
} catch (e) {
|
||||
return e.toString();
|
||||
}
|
||||
});
|
||||
if (res.isNotEmpty) {
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
|
||||
AndroidState getAndroidState() {
|
||||
return AndroidState(
|
||||
currentProfileName: config.currentProfile?.label ?? '',
|
||||
@@ -400,7 +423,7 @@ class GlobalState {
|
||||
rules = [...overrideData.runningRule, ...rules];
|
||||
}
|
||||
}
|
||||
rawConfig['rule'] = rules;
|
||||
rawConfig['rules'] = rules;
|
||||
return rawConfig;
|
||||
}
|
||||
|
||||
|
||||
@@ -1690,6 +1690,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
yaml_writer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: yaml_writer
|
||||
sha256: "69651cd7238411179ac32079937d4aa9a2970150d6b2ae2c6fe6de09402a5dc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
sdks:
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.29.0"
|
||||
|
||||
@@ -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.88+2025082601
|
||||
version: 0.8.88+2025082901
|
||||
environment:
|
||||
sdk: '>=3.8.0 <4.0.0'
|
||||
|
||||
@@ -63,6 +63,7 @@ dependencies:
|
||||
flutter_cache_manager: ^3.4.1
|
||||
crypto: ^3.0.3
|
||||
flutter_acrylic: ^1.1.4
|
||||
yaml_writer: ^2.1.0
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
Reference in New Issue
Block a user