Compare commits
1 Commits
v0.8.88-pr
...
v0.8.88-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9428d4ef4c |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- platform: android
|
||||
os: ubuntu-latest
|
||||
- platform: windows
|
||||
os: windows-latest
|
||||
os: Windows-2022
|
||||
arch: amd64
|
||||
- platform: linux
|
||||
os: ubuntu-22.04
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
<service
|
||||
android:name=".TileService"
|
||||
android:label="FlClash Debug"
|
||||
tools:replace="android:label"
|
||||
tools:targetApi="24" />
|
||||
tools:replace="android:label" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -103,9 +103,8 @@
|
||||
android:exported="true"
|
||||
android:permission="${applicationId}.permission.RECEIVE_BROADCASTS">
|
||||
<intent-filter>
|
||||
<action android:name="${applicationId}.intent.action.START" />
|
||||
<action android:name="${applicationId}.intent.action.STOP" />
|
||||
<action android:name="${applicationId}.intent.action.TOGGLE" />
|
||||
<action android:name="${applicationId}.intent.action.SERVICE_CREATED" />
|
||||
<action android:name="${applicationId}.intent.action.SERVICE_DESTROYED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
||||
@@ -3,9 +3,6 @@ package com.follow.clash
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.common.processName
|
||||
import com.google.firebase.FirebaseApp
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
|
||||
class Application : Application() {
|
||||
|
||||
@@ -13,21 +10,4 @@ class Application : Application() {
|
||||
super.attachBaseContext(base)
|
||||
GlobalState.init(this)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
initRemoteCrashlytics(this)
|
||||
}
|
||||
|
||||
private fun initRemoteCrashlytics(context: Context) {
|
||||
try {
|
||||
if (processName?.endsWith(":remote") == true) {
|
||||
FirebaseApp.initializeApp(context)
|
||||
FirebaseCrashlytics.getInstance().isCrashlyticsCollectionEnabled = true
|
||||
GlobalState.log("init remote crashlytics")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
GlobalState.log("initRemoteCrashlytics error: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,31 +4,24 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.follow.clash.common.BroadcastAction
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.common.action
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class BroadcastReceiver : BroadcastReceiver(),
|
||||
CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
|
||||
class BroadcastReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
BroadcastAction.START.action -> {
|
||||
launch {
|
||||
BroadcastAction.SERVICE_CREATED.action -> {
|
||||
GlobalState.log("Receiver service created")
|
||||
GlobalState.launch {
|
||||
State.handleStartServiceAction()
|
||||
}
|
||||
}
|
||||
|
||||
BroadcastAction.STOP.action -> {
|
||||
BroadcastAction.SERVICE_DESTROYED.action -> {
|
||||
GlobalState.log("Receiver service destroyed")
|
||||
State.handleStopServiceAction()
|
||||
}
|
||||
|
||||
BroadcastAction.TOGGLE.action -> {
|
||||
launch {
|
||||
State.handleToggleAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,9 +32,8 @@ suspend fun PackageManager.getPackageIconPath(packageName: String): String =
|
||||
if (iconFile.exists() && !isExpired(iconFile)) {
|
||||
return@withContext iconFile.absolutePath
|
||||
}
|
||||
iconDir.listFiles()?.forEach { file ->
|
||||
if (file.name.startsWith(packageName + "_")) file.delete()
|
||||
}
|
||||
iconDir.listFiles { f -> f.name.startsWith("${packageName}_") }?.forEach(File::delete)
|
||||
|
||||
val icon = getApplicationIcon(packageName)
|
||||
saveDrawableToFile(icon, iconFile)
|
||||
iconFile.absolutePath
|
||||
@@ -47,8 +46,10 @@ suspend fun PackageManager.getPackageIconPath(packageName: String): String =
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveDrawableToFile(drawable: Drawable, file: File) {
|
||||
val bitmap = drawable.toBitmap()
|
||||
private suspend fun saveDrawableToFile(drawable: Drawable, file: File) {
|
||||
val bitmap = withContext(Dispatchers.Default) {
|
||||
drawable.toBitmap(width = 128, height = 128)
|
||||
}
|
||||
try {
|
||||
val format = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||
@@ -97,7 +98,6 @@ inline fun <reified T : FlutterPlugin> FlutterEngine.plugin(): T? {
|
||||
return plugins.get(T::class.java) as T?
|
||||
}
|
||||
|
||||
|
||||
fun <T> MethodChannel.invokeMethodOnMainThread(
|
||||
method: String, arguments: Any? = null, callback: ((Result<T>) -> Unit)? = null
|
||||
) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.follow.clash
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.plugins.AppPlugin
|
||||
import com.follow.clash.plugins.ServicePlugin
|
||||
import com.follow.clash.plugins.TilePlugin
|
||||
@@ -31,6 +32,9 @@ class MainActivity : FlutterActivity(),
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
GlobalState.launch {
|
||||
Service.setEventListener(null)
|
||||
}
|
||||
State.flutterEngine = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
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.ICallbackInterface
|
||||
import com.follow.clash.service.IMessageInterface
|
||||
import com.follow.clash.service.IEventInterface
|
||||
import com.follow.clash.service.IRemoteInterface
|
||||
import com.follow.clash.service.IResultInterface
|
||||
import com.follow.clash.service.RemoteService
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
import com.follow.clash.service.models.VpnOptions
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
object Service {
|
||||
private val delegate by lazy {
|
||||
@@ -34,15 +39,46 @@ object Service {
|
||||
delegate.unbind()
|
||||
}
|
||||
|
||||
suspend fun invokeAction(
|
||||
data: String, cb: (result: ByteArray?, isSuccess: Boolean) -> Unit
|
||||
): Result<Unit> {
|
||||
suspend fun invokeAction(data: String, cb: (result: String) -> Unit): Result<Unit> {
|
||||
val res = mutableListOf<ByteArray>()
|
||||
return delegate.useService {
|
||||
it.invokeAction(data, object : ICallbackInterface.Stub() {
|
||||
override fun onResult(result: ByteArray?, isSuccess: Boolean) {
|
||||
cb(result, isSuccess)
|
||||
it.invokeAction(
|
||||
data, object : ICallbackInterface.Stub() {
|
||||
override fun onResult(result: ByteArray?, isSuccess: Boolean) {
|
||||
res.add(result ?: byteArrayOf())
|
||||
if (isSuccess) {
|
||||
cb(res.formatString())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setEventListener(
|
||||
cb: ((result: String?) -> Unit)?
|
||||
): Result<Unit> {
|
||||
val results = HashMap<String, MutableList<ByteArray>>()
|
||||
return delegate.useService {
|
||||
it.setEventListener(
|
||||
when (cb != null) {
|
||||
true -> object : IEventInterface.Stub() {
|
||||
override fun onEvent(
|
||||
id: String, data: ByteArray?, isSuccess: Boolean
|
||||
) {
|
||||
if (results[id] == null) {
|
||||
results[id] = mutableListOf()
|
||||
}
|
||||
results[id]?.add(data ?: byteArrayOf())
|
||||
if (isSuccess) {
|
||||
cb(results[id]?.formatString())
|
||||
results.remove(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false -> null
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,23 +90,54 @@ object Service {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setMessageCallback(
|
||||
cb: (result: String?) -> Unit
|
||||
suspend fun setCrashlytics(
|
||||
enable: Boolean
|
||||
): Result<Unit> {
|
||||
return delegate.useService {
|
||||
it.setMessageCallback(object : IMessageInterface.Stub() {
|
||||
override fun onResult(result: String?) {
|
||||
cb(result)
|
||||
}
|
||||
})
|
||||
it.setCrashlytics(enable)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun startService(options: VpnOptions, inApp: Boolean) {
|
||||
delegate.useService { it.startService(options, inApp) }
|
||||
private suspend fun awaitIResultInterface(
|
||||
block: (IResultInterface) -> Unit
|
||||
): Long = suspendCancellableCoroutine { continuation ->
|
||||
val callback = object : IResultInterface.Stub() {
|
||||
override fun onResult(time: Long) {
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(time)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
block(callback)
|
||||
} catch (e: Exception) {
|
||||
if (continuation.isActive) {
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun stopService() {
|
||||
delegate.useService { it.stopService() }
|
||||
|
||||
suspend fun startService(options: VpnOptions, runTime: Long): Long {
|
||||
return delegate.useService {
|
||||
awaitIResultInterface { callback ->
|
||||
it.startService(options, runTime, callback)
|
||||
}
|
||||
}.getOrNull() ?: 0L
|
||||
}
|
||||
|
||||
suspend fun stopService(): Long {
|
||||
return delegate.useService {
|
||||
awaitIResultInterface { callback ->
|
||||
it.stopService(callback)
|
||||
}
|
||||
}.getOrNull() ?: 0L
|
||||
}
|
||||
|
||||
suspend fun getRunTime(): Long {
|
||||
return delegate.useService {
|
||||
it.runTime
|
||||
}.getOrNull() ?: 0L
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ object State {
|
||||
var runTime: Long = 0
|
||||
|
||||
val runStateFlow: MutableStateFlow<RunState> = MutableStateFlow(RunState.STOP)
|
||||
|
||||
var flutterEngine: FlutterEngine? = null
|
||||
var serviceFlutterEngine: FlutterEngine? = null
|
||||
|
||||
@@ -51,6 +52,18 @@ object State {
|
||||
action?.invoke()
|
||||
}
|
||||
|
||||
suspend fun handleSyncState() {
|
||||
runLock.withLock {
|
||||
Service.bind()
|
||||
runTime = Service.getRunTime()
|
||||
val runState = when (runTime == 0L) {
|
||||
true -> RunState.STOP
|
||||
false -> RunState.START
|
||||
}
|
||||
runStateFlow.tryEmit(runState)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun handleStartServiceAction() {
|
||||
tilePlugin?.handleStart()
|
||||
if (flutterEngine != null) {
|
||||
@@ -100,7 +113,6 @@ object State {
|
||||
)
|
||||
serviceFlutterEngine?.dartExecutor?.executeDartEntrypoint(dartEntrypoint)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +131,7 @@ object State {
|
||||
return@launch
|
||||
}
|
||||
appPlugin?.prepare(options.enable) {
|
||||
runTime = System.currentTimeMillis()
|
||||
Service.startService(options, true)
|
||||
runTime = Service.startService(options, runTime)
|
||||
runStateFlow.tryEmit(RunState.START)
|
||||
}
|
||||
}
|
||||
@@ -135,14 +146,13 @@ object State {
|
||||
return@launch
|
||||
}
|
||||
runStateFlow.tryEmit(RunState.PENDING)
|
||||
Service.stopService()
|
||||
runTime = Service.stopService()
|
||||
runStateFlow.tryEmit(RunState.STOP)
|
||||
runTime = 0
|
||||
}
|
||||
destroyServiceEngine()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class TileService : TileService() {
|
||||
scope?.cancel()
|
||||
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
scope?.launch {
|
||||
State.handleSyncState()
|
||||
State.runStateFlow.collect {
|
||||
updateTile(it)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ package com.follow.clash.models
|
||||
|
||||
|
||||
data class AppState(
|
||||
val currentProfileName: String,
|
||||
val stopText: String,
|
||||
val onlyStatisticsProxy: Boolean,
|
||||
val crashlytics: Boolean = true,
|
||||
val currentProfileName: String = "FlClash",
|
||||
val stopText: String = "Stop",
|
||||
val onlyStatisticsProxy: Boolean = false,
|
||||
)
|
||||
|
||||
@@ -57,7 +57,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
private var vpnPrepareCallback: (suspend () -> Unit)? = null
|
||||
|
||||
private var requestNotificationCallback: (() -> Unit)? = null
|
||||
|
||||
|
||||
private val packages = mutableListOf<Package>()
|
||||
|
||||
private val skipPrefixList = listOf(
|
||||
|
||||
@@ -5,7 +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.common.GlobalState
|
||||
import com.follow.clash.invokeMethodOnMainThread
|
||||
import com.follow.clash.models.AppState
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
@@ -38,7 +38,7 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) {
|
||||
"init" -> {
|
||||
handleInit(result)
|
||||
handleInit(call, result)
|
||||
}
|
||||
|
||||
"shutdown" -> {
|
||||
@@ -73,12 +73,8 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
private fun handleInvokeAction(call: MethodCall, result: MethodChannel.Result) {
|
||||
launch {
|
||||
val data = call.arguments<String>()!!
|
||||
val res = mutableListOf<ByteArray>()
|
||||
Service.invokeAction(data) { byteArray, isSuccess ->
|
||||
res.add(byteArray ?: byteArrayOf())
|
||||
if (isSuccess) {
|
||||
result.success(res.formatString())
|
||||
}
|
||||
Service.invokeAction(data) {
|
||||
result.success(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,28 +115,32 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
}
|
||||
|
||||
private fun handleSyncState(call: MethodCall, result: MethodChannel.Result) {
|
||||
val data = call.arguments<String>()!!
|
||||
val params = Gson().fromJson(data, AppState::class.java)
|
||||
GlobalState.setCrashlytics(params.crashlytics)
|
||||
launch {
|
||||
val data = call.arguments<String>()!!
|
||||
val params = Gson().fromJson(data, AppState::class.java)
|
||||
Service.updateNotificationParams(
|
||||
NotificationParams(
|
||||
title = params.currentProfileName,
|
||||
stopText = params.stopText,
|
||||
onlyStatisticsProxy = params.onlyStatisticsProxy
|
||||
)
|
||||
).onSuccess {
|
||||
result.success("")
|
||||
}.onFailure {
|
||||
result.success(it.message)
|
||||
}
|
||||
)
|
||||
Service.setCrashlytics(params.crashlytics)
|
||||
result.success("")
|
||||
}
|
||||
}
|
||||
|
||||
fun handleInit(result: MethodChannel.Result) {
|
||||
fun handleInit(call: MethodCall, result: MethodChannel.Result) {
|
||||
Service.bind()
|
||||
launch {
|
||||
Service.setMessageCallback {
|
||||
handleSendEvent(it)
|
||||
val needSetEventListener = call.arguments<Boolean>() ?: false
|
||||
when (needSetEventListener) {
|
||||
true -> Service.setEventListener {
|
||||
handleSendEvent(it)
|
||||
}
|
||||
|
||||
false -> Service.setEventListener(null)
|
||||
}.onSuccess {
|
||||
result.success("")
|
||||
}.onFailure {
|
||||
@@ -152,6 +152,9 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
}
|
||||
|
||||
private fun handleGetRunTime(result: MethodChannel.Result) {
|
||||
return result.success(State.runTime)
|
||||
launch {
|
||||
State.handleSyncState()
|
||||
result.success(State.runTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,4 +39,7 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.gson)
|
||||
implementation(platform(libs.firebase.bom))
|
||||
implementation(libs.firebase.crashlytics.ndk)
|
||||
implementation(libs.firebase.analytics)
|
||||
}
|
||||
@@ -10,9 +10,8 @@ enum class QuickAction {
|
||||
}
|
||||
|
||||
enum class BroadcastAction {
|
||||
START,
|
||||
STOP,
|
||||
TOGGLE,
|
||||
SERVICE_CREATED,
|
||||
SERVICE_DESTROYED,
|
||||
}
|
||||
|
||||
enum class AccessControlMode {
|
||||
|
||||
@@ -149,7 +149,7 @@ fun Context.receiveBroadcastFlow(
|
||||
inline fun <reified T : IBinder> Context.bindServiceFlow(
|
||||
intent: Intent,
|
||||
flags: Int = Context.BIND_AUTO_CREATE,
|
||||
maxRetries: Int = 5,
|
||||
maxRetries: Int = 10,
|
||||
retryDelayMillis: Long = 200L
|
||||
): Flow<Pair<IBinder?, String>> = callbackFlow {
|
||||
val connection = object : ServiceConnection {
|
||||
@@ -186,6 +186,7 @@ inline fun <reified T : IBinder> Context.bindServiceFlow(
|
||||
awaitClose {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
unbindService(connection)
|
||||
trySend(Pair(null, ""))
|
||||
}
|
||||
}
|
||||
}.retryWhen { cause, attempt ->
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.follow.clash.common
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import com.google.firebase.FirebaseApp
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -13,16 +15,16 @@ object GlobalState : CoroutineScope by CoroutineScope(Dispatchers.Default) {
|
||||
const val NOTIFICATION_ID = 1
|
||||
|
||||
val packageName: String
|
||||
get() = _application.packageName
|
||||
get() = application.packageName
|
||||
|
||||
val RECEIVE_BROADCASTS_PERMISSIONS: String
|
||||
get() = "${packageName}.permission.RECEIVE_BROADCASTS"
|
||||
|
||||
|
||||
private lateinit var _application: Application
|
||||
private var _application: Application? = null
|
||||
|
||||
val application: Application
|
||||
get() = _application
|
||||
get() = _application!!
|
||||
|
||||
|
||||
fun log(text: String) {
|
||||
@@ -32,4 +34,14 @@ object GlobalState : CoroutineScope by CoroutineScope(Dispatchers.Default) {
|
||||
fun init(application: Application) {
|
||||
_application = application
|
||||
}
|
||||
|
||||
fun setCrashlytics(enable: Boolean) {
|
||||
_application?.let {
|
||||
FirebaseApp.initializeApp(it)
|
||||
FirebaseCrashlytics.getInstance().isCrashlyticsCollectionEnabled = enable
|
||||
if (enable) {
|
||||
log("init crashlytics ${it.processName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,13 +44,16 @@ class ServiceDelegate<T>(
|
||||
job = null
|
||||
_serviceState.value = null
|
||||
job = launch {
|
||||
GlobalState.application.bindServiceFlow<IBinder>(intent).collect { handleBind(it) }
|
||||
runCatching {
|
||||
GlobalState.application.bindServiceFlow<IBinder>(intent)
|
||||
.collect { handleBind(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <R> useService(
|
||||
timeoutMillis: Long = 5000, crossinline block: (T) -> R
|
||||
timeoutMillis: Long = 5000, crossinline block: suspend (T) -> R
|
||||
): Result<R> {
|
||||
return runCatching {
|
||||
withTimeout(timeoutMillis) {
|
||||
|
||||
@@ -41,9 +41,13 @@ Java_com_follow_clash_core_Core_invokeAction(JNIEnv *env, jobject thiz, jstring
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_setMessageCallback(JNIEnv *env, jobject thiz, jobject cb) {
|
||||
const auto interface = new_global(cb);
|
||||
setMessageCallback(interface);
|
||||
Java_com_follow_clash_core_Core_setEventListener(JNIEnv *env, jobject thiz, jobject cb) {
|
||||
if (cb != nullptr) {
|
||||
const auto interface = new_global(cb);
|
||||
setEventListener(interface);
|
||||
} else {
|
||||
setEventListener(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C"
|
||||
@@ -169,7 +173,7 @@ Java_com_follow_clash_core_Core_updateDNS(JNIEnv *env, jobject thiz, jstring dns
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_setMessageCallback(JNIEnv *env, jobject thiz, jobject cb) {
|
||||
Java_com_follow_clash_core_Core_setEventListener(JNIEnv *env, jobject thiz, jobject cb) {
|
||||
}
|
||||
|
||||
extern "C"
|
||||
|
||||
@@ -84,18 +84,22 @@ data object Core {
|
||||
)
|
||||
}
|
||||
|
||||
private external fun setMessageCallback(cb: InvokeInterface)
|
||||
private external fun setEventListener(cb: InvokeInterface?)
|
||||
|
||||
fun setMessageCallback(
|
||||
cb: (result: String?) -> Unit
|
||||
fun callSetEventListener(
|
||||
cb: ((result: String?) -> Unit)?
|
||||
) {
|
||||
setMessageCallback(
|
||||
object : InvokeInterface {
|
||||
override fun onResult(result: String?) {
|
||||
cb(result)
|
||||
}
|
||||
},
|
||||
)
|
||||
when (cb != null) {
|
||||
true -> setEventListener(
|
||||
object : InvokeInterface {
|
||||
override fun onResult(result: String?) {
|
||||
cb(result)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
false -> setEventListener(null)
|
||||
}
|
||||
}
|
||||
|
||||
external fun stopTun()
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface ICallbackInterface {
|
||||
void onResult(in byte[] result, boolean isSuccess);
|
||||
oneway void onResult(in byte[] data,in boolean isSuccess);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// IEventInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface IEventInterface {
|
||||
oneway void onEvent(in String id, in byte[] data,in boolean isSuccess);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// IMessageInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface IMessageInterface {
|
||||
void onResult(String result);
|
||||
}
|
||||
@@ -2,14 +2,17 @@
|
||||
package com.follow.clash.service;
|
||||
|
||||
import com.follow.clash.service.ICallbackInterface;
|
||||
import com.follow.clash.service.IMessageInterface;
|
||||
import com.follow.clash.service.IEventInterface;
|
||||
import com.follow.clash.service.IResultInterface;
|
||||
import com.follow.clash.service.models.VpnOptions;
|
||||
import com.follow.clash.service.models.NotificationParams;
|
||||
|
||||
interface IRemoteInterface {
|
||||
void invokeAction(in String data, in ICallbackInterface callback);
|
||||
void updateNotificationParams(in NotificationParams params);
|
||||
void startService(in VpnOptions options,in boolean inApp);
|
||||
void stopService();
|
||||
void setMessageCallback(in IMessageInterface messageCallback);
|
||||
void startService(in VpnOptions options, in long runTime, in IResultInterface result);
|
||||
void stopService(in IResultInterface result);
|
||||
void setEventListener(in IEventInterface event);
|
||||
void setCrashlytics(in boolean enable);
|
||||
long getRunTime();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// IResultInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface IResultInterface {
|
||||
oneway void onResult(in long runTime);
|
||||
}
|
||||
@@ -29,6 +29,11 @@ class CommonService : Service(), IBaseService,
|
||||
handleCreate()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
handleDestroy()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
Core.forceGC()
|
||||
super.onLowMemory()
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package com.follow.clash.service
|
||||
|
||||
import com.follow.clash.common.BroadcastAction
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.common.sendBroadcast
|
||||
|
||||
interface IBaseService {
|
||||
fun handleCreate() {
|
||||
if (!State.inApp) {
|
||||
BroadcastAction.START.sendBroadcast()
|
||||
} else {
|
||||
State.inApp = false
|
||||
}
|
||||
GlobalState.log("Service create")
|
||||
BroadcastAction.SERVICE_CREATED.sendBroadcast()
|
||||
}
|
||||
|
||||
fun handleDestroy() {
|
||||
GlobalState.log("Service destroy")
|
||||
BroadcastAction.SERVICE_DESTROYED.sendBroadcast()
|
||||
}
|
||||
|
||||
fun start()
|
||||
|
||||
@@ -3,56 +3,71 @@ package com.follow.clash.service
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.common.ServiceDelegate
|
||||
import com.follow.clash.common.chunkedForAidl
|
||||
import com.follow.clash.common.intent
|
||||
import com.follow.clash.core.Core
|
||||
import com.follow.clash.service.State.delegate
|
||||
import com.follow.clash.service.State.intent
|
||||
import com.follow.clash.service.State.runLock
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
import com.follow.clash.service.models.VpnOptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.util.UUID
|
||||
|
||||
class RemoteService : Service(),
|
||||
CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
|
||||
private var delegate: ServiceDelegate<IBaseService>? = null
|
||||
private var intent: Intent? = null
|
||||
|
||||
private fun handleStopService() {
|
||||
private fun handleStopService(result: IResultInterface) {
|
||||
launch {
|
||||
delegate?.useService { service ->
|
||||
service.stop()
|
||||
delegate?.unbind()
|
||||
runLock.withLock {
|
||||
delegate?.useService { service ->
|
||||
service.stop()
|
||||
delegate?.unbind()
|
||||
}
|
||||
State.runTime = 0
|
||||
result.onResult(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleServiceDisconnected(message: String) {
|
||||
GlobalState.log("Background service disconnected: $message")
|
||||
intent = null
|
||||
delegate = null
|
||||
}
|
||||
|
||||
private fun handleStartService() {
|
||||
private fun handleStartService(runTime: Long, result: IResultInterface) {
|
||||
launch {
|
||||
val nextIntent = when (State.options?.enable == true) {
|
||||
true -> VpnService::class.intent
|
||||
false -> CommonService::class.intent
|
||||
}
|
||||
if (intent != nextIntent) {
|
||||
delegate?.unbind()
|
||||
delegate = ServiceDelegate(nextIntent, ::handleServiceDisconnected) { binder ->
|
||||
when (binder) {
|
||||
is VpnService.LocalBinder -> binder.getService()
|
||||
is CommonService.LocalBinder -> binder.getService()
|
||||
else -> throw IllegalArgumentException("Invalid binder type")
|
||||
}
|
||||
runLock.withLock {
|
||||
val nextIntent = when (State.options?.enable == true) {
|
||||
true -> VpnService::class.intent
|
||||
false -> CommonService::class.intent
|
||||
}
|
||||
intent = nextIntent
|
||||
delegate?.bind()
|
||||
}
|
||||
delegate?.useService { service ->
|
||||
service.start()
|
||||
if (intent != nextIntent) {
|
||||
delegate?.unbind()
|
||||
delegate = ServiceDelegate(nextIntent, ::handleServiceDisconnected) { binder ->
|
||||
when (binder) {
|
||||
is VpnService.LocalBinder -> binder.getService()
|
||||
is CommonService.LocalBinder -> binder.getService()
|
||||
else -> throw IllegalArgumentException("Invalid binder type")
|
||||
}
|
||||
}
|
||||
intent = nextIntent
|
||||
delegate?.bind()
|
||||
}
|
||||
delegate?.useService { service ->
|
||||
service.start()
|
||||
}
|
||||
State.runTime = when (runTime != 0L) {
|
||||
true -> runTime
|
||||
false -> System.currentTimeMillis()
|
||||
}
|
||||
result.onResult(State.runTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,10 +75,12 @@ class RemoteService : Service(),
|
||||
private val binder = object : IRemoteInterface.Stub() {
|
||||
override fun invokeAction(data: String, callback: ICallbackInterface) {
|
||||
Core.invokeAction(data) {
|
||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||
val totalSize = chunks.size
|
||||
chunks.forEachIndexed { index, chunk ->
|
||||
callback.onResult(chunk, totalSize - 1 == index)
|
||||
runCatching {
|
||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||
val totalSize = chunks.size
|
||||
chunks.forEachIndexed { index, chunk ->
|
||||
callback.onResult(chunk, totalSize - 1 == index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,27 +90,51 @@ class RemoteService : Service(),
|
||||
}
|
||||
|
||||
override fun startService(
|
||||
options: VpnOptions, inApp: Boolean
|
||||
options: VpnOptions,
|
||||
runtime: Long,
|
||||
result: IResultInterface,
|
||||
) {
|
||||
State.options = options
|
||||
State.inApp = inApp
|
||||
handleStartService()
|
||||
handleStartService(runtime, result)
|
||||
}
|
||||
|
||||
override fun stopService() {
|
||||
handleStopService()
|
||||
override fun stopService(result: IResultInterface) {
|
||||
handleStopService(result)
|
||||
}
|
||||
|
||||
override fun setMessageCallback(messageCallback: IMessageInterface) {
|
||||
setMessageCallback(messageCallback::onResult)
|
||||
}
|
||||
}
|
||||
override fun setEventListener(eventListener: IEventInterface?) {
|
||||
GlobalState.log("RemoveEventListener ${eventListener == null}")
|
||||
when (eventListener != null) {
|
||||
true -> Core.callSetEventListener {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMessageCallback(cb: (result: String?) -> Unit) {
|
||||
Core.setMessageCallback(cb)
|
||||
false -> Core.callSetEventListener(null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setCrashlytics(enable: Boolean) {
|
||||
GlobalState.setCrashlytics(enable)
|
||||
}
|
||||
|
||||
override fun getRunTime(): Long {
|
||||
return State.runTime
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
GlobalState.log("Remote service destroy")
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,22 @@
|
||||
package com.follow.clash.service
|
||||
|
||||
import android.content.Intent
|
||||
import com.follow.clash.common.ServiceDelegate
|
||||
import com.follow.clash.service.models.NotificationParams
|
||||
import com.follow.clash.service.models.VpnOptions
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
||||
object State {
|
||||
var options: VpnOptions? = null
|
||||
var inApp: Boolean = false
|
||||
var notificationParamsFlow: MutableStateFlow<NotificationParams?> = MutableStateFlow(
|
||||
NotificationParams()
|
||||
)
|
||||
|
||||
val runLock = Mutex()
|
||||
var runTime: Long = 0L
|
||||
|
||||
var delegate: ServiceDelegate<IBaseService>? = null
|
||||
|
||||
var intent: Intent? = null
|
||||
}
|
||||
@@ -11,8 +11,7 @@ import android.os.RemoteException
|
||||
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.sendBroadcast
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.core.Core
|
||||
import com.follow.clash.service.models.VpnOptions
|
||||
import com.follow.clash.service.models.getIpv4RouteAddress
|
||||
@@ -44,6 +43,11 @@ class VpnService : SystemVpnService(), IBaseService,
|
||||
handleCreate()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
handleDestroy()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private val connectivity by lazy {
|
||||
getSystemService<ConnectivityManager>()
|
||||
}
|
||||
@@ -107,11 +111,13 @@ class VpnService : SystemVpnService(), IBaseService,
|
||||
try {
|
||||
val isSuccess = super.onTransact(code, data, reply, flags)
|
||||
if (!isSuccess) {
|
||||
BroadcastAction.STOP.sendBroadcast()
|
||||
GlobalState.log("VpnService disconnected")
|
||||
handleDestroy()
|
||||
}
|
||||
return isSuccess
|
||||
} catch (e: RemoteException) {
|
||||
throw e
|
||||
GlobalState.log("VpnService onTransact $e")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,12 +90,13 @@ class NotificationModule(private val service: Service) : Module() {
|
||||
setSmallIcon(R.drawable.ic)
|
||||
setContentTitle("FlClash")
|
||||
setContentIntent(intent.toPendingIntent)
|
||||
setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
|
||||
}
|
||||
setOngoing(true)
|
||||
setShowWhen(false)
|
||||
setShowWhen(true)
|
||||
setOnlyAlertOnce(true)
|
||||
}
|
||||
}
|
||||
@@ -105,7 +106,6 @@ class NotificationModule(private val service: Service) : Module() {
|
||||
with(notificationBuilder) {
|
||||
setContentTitle(params.title)
|
||||
setContentText(params.contentText)
|
||||
setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
clearActions()
|
||||
addAction(
|
||||
0, params.stopText, QuickAction.STOP.quickIntent.toPendingIntent
|
||||
|
||||
@@ -428,5 +428,9 @@
|
||||
"restartCoreTip": "Are you sure you want to restart the core?",
|
||||
"forceRestartCoreTip": "Are you sure you want to force restart the core?",
|
||||
"dnsHijacking": "DNS hijacking",
|
||||
"coreStatus": "Core status"
|
||||
"coreStatus": "Core status",
|
||||
"dataCollectionTip": "Data Collection Notice",
|
||||
"dataCollectionContent": "This app uses Firebase Crashlytics to collect crash information to improve app stability.\nThe collected data includes device information and crash details, but does not contain personal sensitive data.\nYou can disable this feature in settings.",
|
||||
"crashlytics": "Crash Analysis",
|
||||
"crashlyticsTip": "When enabled, automatically uploads crash logs without sensitive information when the app crashes"
|
||||
}
|
||||
@@ -429,5 +429,9 @@
|
||||
"restartCoreTip": "コアを再起動してもよろしいですか?",
|
||||
"forceRestartCoreTip": "コアを強制再起動してもよろしいですか?",
|
||||
"dnsHijacking": "DNSハイジャッキング",
|
||||
"coreStatus": "コアステータス"
|
||||
"coreStatus": "コアステータス",
|
||||
"dataCollectionTip": "データ収集説明",
|
||||
"dataCollectionContent": "本アプリはFirebase Crashlyticsを使用してクラッシュ情報を収集し、アプリの安定性を向上させます。\n収集されるデータにはデバイス情報とクラッシュ詳細が含まれますが、個人の機密データは含まれません。\n設定でこの機能を無効にすることができます。",
|
||||
"crashlytics": "クラッシュ分析",
|
||||
"crashlyticsTip": "有効にすると、アプリがクラッシュした際に機密情報を含まないクラッシュログを自動的にアップロードします"
|
||||
}
|
||||
@@ -429,5 +429,9 @@
|
||||
"restartCoreTip": "Вы уверены, что хотите перезапустить ядро?",
|
||||
"forceRestartCoreTip": "Вы уверены, что хотите принудительно перезапустить ядро?",
|
||||
"dnsHijacking": "DNS-перехват",
|
||||
"coreStatus": "Основной статус"
|
||||
"coreStatus": "Основной статус",
|
||||
"dataCollectionTip": "Уведомление о сборе данных",
|
||||
"dataCollectionContent": "Это приложение использует Firebase Crashlytics для сбора информации о сбоях nhằm улучшения стабильности приложения.\nСобираемые данные включают информацию об устройстве и подробности о сбоях, но не содержат персональных конфиденциальных данных.\nВы можете отключить эту функцию в настройках.",
|
||||
"crashlytics": "Анализ сбоев",
|
||||
"crashlyticsTip": "При включении автоматически загружает журналы сбоев без конфиденциальной информации, когда приложение выходит из строя"
|
||||
}
|
||||
@@ -429,5 +429,9 @@
|
||||
"restartCoreTip": "您确定要重启核心吗?",
|
||||
"forceRestartCoreTip": "您确定要强制重启核心吗?",
|
||||
"dnsHijacking": "DNS劫持",
|
||||
"coreStatus": "核心状态"
|
||||
"coreStatus": "核心状态",
|
||||
"dataCollectionTip": "数据收集说明",
|
||||
"dataCollectionContent": "本应用使用 Firebase Crashlytics 收集崩溃信息以改进应用稳定性。\n收集的数据包括设备信息和崩溃详情,不包含个人敏感数据。\n您可以在设置中关闭此功能。",
|
||||
"crashlytics": "崩溃分析",
|
||||
"crashlyticsTip": "开启后,应用崩溃时自动上传不包含敏感信息的崩溃日志"
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
assets/data/GEOIP.metadb
Normal file
BIN
assets/data/GEOIP.metadb
Normal file
Binary file not shown.
45523
assets/data/GEOSITE.dat
Normal file
45523
assets/data/GEOSITE.dat
Normal file
File diff suppressed because one or more lines are too long
35960
assets/data/GeoSite.dat
35960
assets/data/GeoSite.dat
File diff suppressed because one or more lines are too long
Binary file not shown.
Submodule core/Clash.Meta updated: ef78ae3278...573489787b
@@ -323,13 +323,13 @@ func handleUpdateGeoData(geoType string, geoName string, fn func(value string))
|
||||
fn(err.Error())
|
||||
return
|
||||
}
|
||||
case "GeoIp":
|
||||
case "GEOIP":
|
||||
err := updater.UpdateGeoIpWithPath(path)
|
||||
if err != nil {
|
||||
fn(err.Error())
|
||||
return
|
||||
}
|
||||
case "GeoSite":
|
||||
case "GEOSITE":
|
||||
err := updater.UpdateGeoSiteWithPath(path)
|
||||
if err != nil {
|
||||
fn(err.Error())
|
||||
|
||||
18
core/lib.go
18
core/lib.go
@@ -27,7 +27,7 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var messageCallback unsafe.Pointer
|
||||
var eventListener unsafe.Pointer
|
||||
|
||||
type TunHandler struct {
|
||||
listener *sing_tun.Listener
|
||||
@@ -164,7 +164,7 @@ func (result ActionResult) send() {
|
||||
}
|
||||
invokeResult(result.callback, string(data))
|
||||
if result.Method != messageMethod {
|
||||
defer releaseObject(result.callback)
|
||||
releaseObject(result.callback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,12 +202,12 @@ func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar
|
||||
return true
|
||||
}
|
||||
|
||||
//export setMessageCallback
|
||||
func setMessageCallback(callback unsafe.Pointer) {
|
||||
if messageCallback != nil {
|
||||
releaseObject(messageCallback)
|
||||
//export setEventListener
|
||||
func setEventListener(listener unsafe.Pointer) {
|
||||
if eventListener != nil || listener == nil {
|
||||
releaseObject(eventListener)
|
||||
}
|
||||
messageCallback = callback
|
||||
eventListener = listener
|
||||
}
|
||||
|
||||
//export getTotalTraffic
|
||||
@@ -225,12 +225,12 @@ func getTraffic(onlyStatisticsProxy bool) *C.char {
|
||||
}
|
||||
|
||||
func sendMessage(message Message) {
|
||||
if messageCallback == nil {
|
||||
if eventListener == nil {
|
||||
return
|
||||
}
|
||||
result := ActionResult{
|
||||
Method: messageMethod,
|
||||
callback: messageCallback,
|
||||
callback: eventListener,
|
||||
Data: message,
|
||||
}
|
||||
result.send()
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import 'package:fl_clash/plugins/service.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
|
||||
import 'system.dart';
|
||||
|
||||
class Android {
|
||||
Future<void> init() async {
|
||||
await service?.init();
|
||||
await service?.syncAndroidState(globalState.getAndroidState());
|
||||
}
|
||||
}
|
||||
|
||||
final android = system.isAndroid ? Android() : null;
|
||||
@@ -5,7 +5,7 @@ import 'package:fl_clash/common/common.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
|
||||
class LocalImageCacheManager extends CacheManager {
|
||||
static const key = 'LocalImageCacheData';
|
||||
static const key = 'ImageCaches';
|
||||
|
||||
static final LocalImageCacheManager _instance = LocalImageCacheManager._();
|
||||
|
||||
@@ -14,14 +14,7 @@ class LocalImageCacheManager extends CacheManager {
|
||||
}
|
||||
|
||||
LocalImageCacheManager._()
|
||||
: super(
|
||||
Config(
|
||||
key,
|
||||
stalePeriod: Duration(days: 30),
|
||||
maxNrOfCacheObjects: 1000,
|
||||
fileService: _LocalImageCacheFileService(),
|
||||
),
|
||||
);
|
||||
: super(Config(key, fileService: _LocalImageCacheFileService()));
|
||||
}
|
||||
|
||||
class _LocalImageCacheFileService extends FileService {
|
||||
@@ -47,28 +40,31 @@ class _LocalImageResponse implements FileServiceResponse {
|
||||
|
||||
final Response<ResponseBody> _response;
|
||||
|
||||
String? _header(String name) {
|
||||
return _response.headers.value(name);
|
||||
}
|
||||
|
||||
@override
|
||||
int get statusCode => _response.statusCode ?? 0;
|
||||
|
||||
@override
|
||||
Stream<List<int>> get content =>
|
||||
_response.data?.stream.transform(uint8ListToListIntConverter) ??
|
||||
Stream.empty();
|
||||
_response.data!.stream.transform(uint8ListToListIntConverter);
|
||||
|
||||
@override
|
||||
int? get contentLength => _response.data?.contentLength;
|
||||
|
||||
@override
|
||||
DateTime get validTill {
|
||||
// Without a cache-control header we keep the file for a week
|
||||
var ageDuration = const Duration(days: 7);
|
||||
final controlHeader = _response.headers.value(
|
||||
HttpHeaders.cacheControlHeader,
|
||||
);
|
||||
final controlHeader = _header(HttpHeaders.cacheControlHeader);
|
||||
if (controlHeader != null) {
|
||||
final controlSettings = controlHeader.split(',');
|
||||
for (final setting in controlSettings) {
|
||||
final sanitizedSetting = setting.trim().toLowerCase();
|
||||
if (sanitizedSetting == 'no-cache') {
|
||||
ageDuration = Duration.zero;
|
||||
}
|
||||
if (sanitizedSetting.startsWith('max-age=')) {
|
||||
final validSeconds =
|
||||
int.tryParse(sanitizedSetting.split('=')[1]) ?? 0;
|
||||
@@ -78,6 +74,7 @@ class _LocalImageResponse implements FileServiceResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ageDuration > const Duration(days: 7)) {
|
||||
return _receivedTime.add(ageDuration);
|
||||
}
|
||||
@@ -85,14 +82,12 @@ class _LocalImageResponse implements FileServiceResponse {
|
||||
}
|
||||
|
||||
@override
|
||||
String? get eTag => _response.headers.value(HttpHeaders.etagHeader);
|
||||
String? get eTag => _header(HttpHeaders.etagHeader);
|
||||
|
||||
@override
|
||||
String get fileExtension {
|
||||
var fileExtension = '';
|
||||
final contentTypeHeader = _response.headers.value(
|
||||
HttpHeaders.contentTypeHeader,
|
||||
);
|
||||
final contentTypeHeader = _header(HttpHeaders.contentTypeHeader);
|
||||
if (contentTypeHeader != null) {
|
||||
final contentType = ContentType.parse(contentTypeHeader);
|
||||
fileExtension = contentType.fileExtension;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export 'android.dart';
|
||||
export 'app_localizations.dart';
|
||||
export 'cache.dart';
|
||||
export 'color.dart';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
@@ -30,10 +32,10 @@ const animateDuration = Duration(milliseconds: 100);
|
||||
const midDuration = Duration(milliseconds: 200);
|
||||
const commonDuration = Duration(milliseconds: 300);
|
||||
const defaultUpdateDuration = Duration(days: 1);
|
||||
const mmdbFileName = 'geoip.metadb';
|
||||
const asnFileName = 'ASN.mmdb';
|
||||
const geoIpFileName = 'GeoIP.dat';
|
||||
const geoSiteFileName = 'GeoSite.dat';
|
||||
const MMDB = 'GEOIP.metadb';
|
||||
const ASN = 'ASN.mmdb';
|
||||
const GEOIP = 'GEOIP.dat';
|
||||
const GEOSITE = 'GEOSITE.dat';
|
||||
final double kHeaderHeight = system.isDesktop
|
||||
? !system.isMacOS
|
||||
? 40
|
||||
|
||||
@@ -37,7 +37,7 @@ class System {
|
||||
'macos' => (deviceInfo as MacOsDeviceInfo).majorVersion,
|
||||
'android' => (deviceInfo as AndroidDeviceInfo).version.sdkInt,
|
||||
'windows' => (deviceInfo as WindowsDeviceInfo).majorVersion,
|
||||
String() => 0
|
||||
String() => 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ class System {
|
||||
);
|
||||
final arguments = [
|
||||
'-c',
|
||||
'echo "$password" | sudo -S chown root:root "$corePath" && echo "$password" | sudo -S chmod +sx "$corePath"'
|
||||
'echo "$password" | sudo -S chown root:root "$corePath" && echo "$password" | sudo -S chmod +sx "$corePath"',
|
||||
];
|
||||
final result = await Process.run(shell, arguments);
|
||||
if (result.exitCode != 0) {
|
||||
@@ -148,21 +148,25 @@ class Windows {
|
||||
final argumentsPtr = arguments.toNativeUtf16();
|
||||
final operationPtr = 'runas'.toNativeUtf16();
|
||||
|
||||
final shellExecute = _shell32.lookupFunction<
|
||||
Int32 Function(
|
||||
final shellExecute = _shell32
|
||||
.lookupFunction<
|
||||
Int32 Function(
|
||||
Pointer<Utf16> hwnd,
|
||||
Pointer<Utf16> lpOperation,
|
||||
Pointer<Utf16> lpFile,
|
||||
Pointer<Utf16> lpParameters,
|
||||
Pointer<Utf16> lpDirectory,
|
||||
Int32 nShowCmd),
|
||||
int Function(
|
||||
Int32 nShowCmd,
|
||||
),
|
||||
int Function(
|
||||
Pointer<Utf16> hwnd,
|
||||
Pointer<Utf16> lpOperation,
|
||||
Pointer<Utf16> lpFile,
|
||||
Pointer<Utf16> lpParameters,
|
||||
Pointer<Utf16> lpDirectory,
|
||||
int nShowCmd)>('ShellExecuteW');
|
||||
int nShowCmd,
|
||||
)
|
||||
>('ShellExecuteW');
|
||||
|
||||
final result = shellExecute(
|
||||
nullptr,
|
||||
@@ -248,15 +252,14 @@ class Windows {
|
||||
|
||||
final res = runas('cmd.exe', command);
|
||||
|
||||
await Future.delayed(
|
||||
Duration(milliseconds: 300),
|
||||
);
|
||||
await Future.delayed(Duration(milliseconds: 300));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<bool> registerTask(String appName) async {
|
||||
final taskXml = '''
|
||||
final taskXml =
|
||||
'''
|
||||
<?xml version="1.0" encoding="UTF-16"?>
|
||||
<Task version="1.3" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
||||
<Principals>
|
||||
@@ -295,8 +298,9 @@ class Windows {
|
||||
</Task>''';
|
||||
final taskPath = join(await appPath.tempPath, 'task.xml');
|
||||
await File(taskPath).create(recursive: true);
|
||||
await File(taskPath)
|
||||
.writeAsBytes(taskXml.encodeUtf16LeWithBom, flush: true);
|
||||
await File(
|
||||
taskPath,
|
||||
).writeAsBytes(taskXml.encodeUtf16LeWithBom, flush: true);
|
||||
final commandLine = [
|
||||
'/Create',
|
||||
'/TN',
|
||||
@@ -305,10 +309,7 @@ class Windows {
|
||||
'%s',
|
||||
'/F',
|
||||
].join(' ');
|
||||
return runas(
|
||||
'schtasks',
|
||||
commandLine.replaceFirst('%s', taskPath),
|
||||
);
|
||||
return runas('schtasks', commandLine.replaceFirst('%s', taskPath));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,23 +338,25 @@ class MacOS {
|
||||
return null;
|
||||
}
|
||||
final device = lineSplits[1];
|
||||
final serviceResult = await Process.run(
|
||||
'networksetup',
|
||||
['-listnetworkserviceorder'],
|
||||
);
|
||||
final serviceResult = await Process.run('networksetup', [
|
||||
'-listnetworkserviceorder',
|
||||
]);
|
||||
final serviceResultOutput = serviceResult.stdout.toString();
|
||||
final currentService = serviceResultOutput.split('\n\n').firstWhere(
|
||||
(s) => s.contains('Device: $device'),
|
||||
orElse: () => '',
|
||||
);
|
||||
final currentService = serviceResultOutput
|
||||
.split('\n\n')
|
||||
.firstWhere((s) => s.contains('Device: $device'), orElse: () => '');
|
||||
if (currentService.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final currentServiceNameLine = currentService.split('\n').firstWhere(
|
||||
(line) => RegExp(r'^\(\d+\).*').hasMatch(line),
|
||||
orElse: () => '');
|
||||
final currentServiceNameLineSplits =
|
||||
currentServiceNameLine.trim().split(' ');
|
||||
final currentServiceNameLine = currentService
|
||||
.split('\n')
|
||||
.firstWhere(
|
||||
(line) => RegExp(r'^\(\d+\).*').hasMatch(line),
|
||||
orElse: () => '',
|
||||
);
|
||||
final currentServiceNameLineSplits = currentServiceNameLine.trim().split(
|
||||
' ',
|
||||
);
|
||||
if (currentServiceNameLineSplits.length < 2) {
|
||||
return null;
|
||||
}
|
||||
@@ -365,10 +368,10 @@ class MacOS {
|
||||
if (deviceServiceName == null) {
|
||||
return null;
|
||||
}
|
||||
final result = await Process.run(
|
||||
'networksetup',
|
||||
['-getdnsservers', deviceServiceName],
|
||||
);
|
||||
final result = await Process.run('networksetup', [
|
||||
'-getdnsservers',
|
||||
deviceServiceName,
|
||||
]);
|
||||
final output = result.stdout.toString().trim();
|
||||
if (output.startsWith("There aren't any DNS Servers set on")) {
|
||||
originDns = [];
|
||||
@@ -400,15 +403,12 @@ class MacOS {
|
||||
if (nextDns == null) {
|
||||
return;
|
||||
}
|
||||
await Process.run(
|
||||
'networksetup',
|
||||
[
|
||||
'-setdnsservers',
|
||||
serviceName,
|
||||
if (nextDns.isNotEmpty) ...nextDns,
|
||||
if (nextDns.isEmpty) 'Empty',
|
||||
],
|
||||
);
|
||||
await Process.run('networksetup', [
|
||||
'-setdnsservers',
|
||||
serviceName,
|
||||
if (nextDns.isNotEmpty) ...nextDns,
|
||||
if (nextDns.isEmpty) 'Empty',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:fl_clash/common/archive.dart';
|
||||
import 'package:fl_clash/core/core.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
import 'package:fl_clash/plugins/service.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/dialog.dart';
|
||||
@@ -543,8 +542,8 @@ class AppController {
|
||||
}
|
||||
await _handlePreference();
|
||||
await _handlerDisclaimer();
|
||||
await _showCrashlyticsTip();
|
||||
await _connectCore();
|
||||
await service?.syncAndroidState(globalState.getAndroidState());
|
||||
await _initCore();
|
||||
await _initStatus();
|
||||
_ref.read(initProvider.notifier).value = true;
|
||||
@@ -620,7 +619,6 @@ class AppController {
|
||||
|
||||
Future<bool> showDisclaimer() async {
|
||||
return await globalState.showCommonDialog<bool>(
|
||||
context: context,
|
||||
dismissible: false,
|
||||
child: CommonDialog(
|
||||
title: appLocalizations.disclaimer,
|
||||
@@ -633,30 +631,47 @@ class AppController {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_ref
|
||||
.read(appSettingProvider.notifier)
|
||||
.updateState(
|
||||
(state) => state.copyWith(disclaimerAccepted: true),
|
||||
);
|
||||
Navigator.of(context).pop<bool>(true);
|
||||
},
|
||||
child: Text(appLocalizations.agree),
|
||||
),
|
||||
],
|
||||
child: SelectableText(appLocalizations.disclaimerDesc),
|
||||
child: Text(appLocalizations.disclaimerDesc),
|
||||
),
|
||||
) ??
|
||||
false;
|
||||
}
|
||||
|
||||
Future<void> _showCrashlyticsTip() async {
|
||||
if (!system.isAndroid) {
|
||||
return;
|
||||
}
|
||||
if (_ref.read(appSettingProvider.select((state) => state.crashlyticsTip))) {
|
||||
return;
|
||||
}
|
||||
await globalState.showMessage(
|
||||
title: appLocalizations.dataCollectionTip,
|
||||
cancelable: false,
|
||||
message: TextSpan(text: appLocalizations.dataCollectionContent),
|
||||
);
|
||||
_ref
|
||||
.read(appSettingProvider.notifier)
|
||||
.updateState((state) => state.copyWith(crashlyticsTip: true));
|
||||
}
|
||||
|
||||
Future<void> _handlerDisclaimer() async {
|
||||
if (_ref.read(appSettingProvider).disclaimerAccepted) {
|
||||
if (_ref.read(
|
||||
appSettingProvider.select((state) => state.disclaimerAccepted),
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
final isDisclaimerAccepted = await showDisclaimer();
|
||||
if (!isDisclaimerAccepted) {
|
||||
await handleExit();
|
||||
}
|
||||
_ref
|
||||
.read(appSettingProvider.notifier)
|
||||
.updateState((state) => state.copyWith(disclaimerAccepted: true));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,12 +40,7 @@ class CoreController {
|
||||
if (!isExists) {
|
||||
await homeDir.create(recursive: true);
|
||||
}
|
||||
const geoFileNameList = [
|
||||
mmdbFileName,
|
||||
geoIpFileName,
|
||||
geoSiteFileName,
|
||||
asnFileName,
|
||||
];
|
||||
const geoFileNameList = [MMDB, GEOIP, GEOSITE, ASN];
|
||||
try {
|
||||
for (final geoFileName in geoFileNameList) {
|
||||
final geoFile = File(join(homePath, geoFileName));
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/core.dart';
|
||||
import 'package:fl_clash/plugins/service.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
|
||||
import 'interface.dart';
|
||||
|
||||
@@ -17,10 +18,14 @@ class CoreLib extends CoreHandlerInterface {
|
||||
@override
|
||||
Future<String> preload() async {
|
||||
final res = await service?.init();
|
||||
if (res?.isEmpty == true) {
|
||||
_connectedCompleter.complete(true);
|
||||
if (res?.isEmpty != true) {
|
||||
return res ?? '';
|
||||
}
|
||||
return res ?? '';
|
||||
_connectedCompleter.complete(true);
|
||||
final syncRes = await service?.syncAndroidState(
|
||||
globalState.getAndroidState(),
|
||||
);
|
||||
return syncRes ?? '';
|
||||
}
|
||||
|
||||
factory CoreLib() {
|
||||
|
||||
@@ -100,6 +100,7 @@ class CoreService extends CoreHandlerInterface {
|
||||
commonPrint.log(error);
|
||||
}
|
||||
});
|
||||
await _socketCompleter.future;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -126,9 +127,9 @@ class CoreService extends CoreHandlerInterface {
|
||||
|
||||
Future<void> _destroySocket() async {
|
||||
if (_socketCompleter.isCompleted) {
|
||||
final lastSocket = await _socketCompleter.future;
|
||||
final socket = await _socketCompleter.future;
|
||||
_socketCompleter = Completer();
|
||||
lastSocket.close();
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +180,9 @@ class CoreService extends CoreHandlerInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Future get connected => _socketCompleter.future;
|
||||
Future get connected {
|
||||
return _socketCompleter.future;
|
||||
}
|
||||
}
|
||||
|
||||
final coreService = system.isDesktop ? CoreService() : null;
|
||||
|
||||
@@ -199,11 +199,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"coreStatus": MessageLookupByLibrary.simpleMessage("Core status"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("Country"),
|
||||
"crashTest": MessageLookupByLibrary.simpleMessage("Crash test"),
|
||||
"crashlytics": MessageLookupByLibrary.simpleMessage("Crash Analysis"),
|
||||
"crashlyticsTip": MessageLookupByLibrary.simpleMessage(
|
||||
"When enabled, automatically uploads crash logs without sensitive information when the app crashes",
|
||||
),
|
||||
"create": MessageLookupByLibrary.simpleMessage("Create"),
|
||||
"creationTime": MessageLookupByLibrary.simpleMessage("Creation time"),
|
||||
"cut": MessageLookupByLibrary.simpleMessage("Cut"),
|
||||
"dark": MessageLookupByLibrary.simpleMessage("Dark"),
|
||||
"dashboard": MessageLookupByLibrary.simpleMessage("Dashboard"),
|
||||
"dataCollectionContent": MessageLookupByLibrary.simpleMessage(
|
||||
"This app uses Firebase Crashlytics to collect crash information to improve app stability.\nThe collected data includes device information and crash details, but does not contain personal sensitive data.\nYou can disable this feature in settings.",
|
||||
),
|
||||
"dataCollectionTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Data Collection Notice",
|
||||
),
|
||||
"days": MessageLookupByLibrary.simpleMessage("Days"),
|
||||
"defaultNameserver": MessageLookupByLibrary.simpleMessage(
|
||||
"Default nameserver",
|
||||
|
||||
@@ -151,11 +151,19 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"coreStatus": MessageLookupByLibrary.simpleMessage("コアステータス"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("国"),
|
||||
"crashTest": MessageLookupByLibrary.simpleMessage("クラッシュテスト"),
|
||||
"crashlytics": MessageLookupByLibrary.simpleMessage("クラッシュ分析"),
|
||||
"crashlyticsTip": MessageLookupByLibrary.simpleMessage(
|
||||
"有効にすると、アプリがクラッシュした際に機密情報を含まないクラッシュログを自動的にアップロードします",
|
||||
),
|
||||
"create": MessageLookupByLibrary.simpleMessage("作成"),
|
||||
"creationTime": MessageLookupByLibrary.simpleMessage("作成時間"),
|
||||
"cut": MessageLookupByLibrary.simpleMessage("切り取り"),
|
||||
"dark": MessageLookupByLibrary.simpleMessage("ダーク"),
|
||||
"dashboard": MessageLookupByLibrary.simpleMessage("ダッシュボード"),
|
||||
"dataCollectionContent": MessageLookupByLibrary.simpleMessage(
|
||||
"本アプリはFirebase Crashlyticsを使用してクラッシュ情報を収集し、アプリの安定性を向上させます。\n収集されるデータにはデバイス情報とクラッシュ詳細が含まれますが、個人の機密データは含まれません。\n設定でこの機能を無効にすることができます。",
|
||||
),
|
||||
"dataCollectionTip": MessageLookupByLibrary.simpleMessage("データ収集説明"),
|
||||
"days": MessageLookupByLibrary.simpleMessage("日"),
|
||||
"defaultNameserver": MessageLookupByLibrary.simpleMessage("デフォルトネームサーバー"),
|
||||
"defaultNameserverDesc": MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
@@ -204,11 +204,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"coreStatus": MessageLookupByLibrary.simpleMessage("Основной статус"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("Страна"),
|
||||
"crashTest": MessageLookupByLibrary.simpleMessage("Тест на сбои"),
|
||||
"crashlytics": MessageLookupByLibrary.simpleMessage("Анализ сбоев"),
|
||||
"crashlyticsTip": MessageLookupByLibrary.simpleMessage(
|
||||
"При включении автоматически загружает журналы сбоев без конфиденциальной информации, когда приложение выходит из строя",
|
||||
),
|
||||
"create": MessageLookupByLibrary.simpleMessage("Создать"),
|
||||
"creationTime": MessageLookupByLibrary.simpleMessage("Время создания"),
|
||||
"cut": MessageLookupByLibrary.simpleMessage("Вырезать"),
|
||||
"dark": MessageLookupByLibrary.simpleMessage("Темный"),
|
||||
"dashboard": MessageLookupByLibrary.simpleMessage("Панель управления"),
|
||||
"dataCollectionContent": MessageLookupByLibrary.simpleMessage(
|
||||
"Это приложение использует Firebase Crashlytics для сбора информации о сбоях nhằm улучшения стабильности приложения.\nСобираемые данные включают информацию об устройстве и подробности о сбоях, но не содержат персональных конфиденциальных данных.\nВы можете отключить эту функцию в настройках.",
|
||||
),
|
||||
"dataCollectionTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Уведомление о сборе данных",
|
||||
),
|
||||
"days": MessageLookupByLibrary.simpleMessage("Дней"),
|
||||
"defaultNameserver": MessageLookupByLibrary.simpleMessage(
|
||||
"Сервер имен по умолчанию",
|
||||
|
||||
@@ -141,11 +141,19 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"coreStatus": MessageLookupByLibrary.simpleMessage("核心状态"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("区域"),
|
||||
"crashTest": MessageLookupByLibrary.simpleMessage("崩溃测试"),
|
||||
"crashlytics": MessageLookupByLibrary.simpleMessage("崩溃分析"),
|
||||
"crashlyticsTip": MessageLookupByLibrary.simpleMessage(
|
||||
"开启后,应用崩溃时自动上传不包含敏感信息的崩溃日志",
|
||||
),
|
||||
"create": MessageLookupByLibrary.simpleMessage("创建"),
|
||||
"creationTime": MessageLookupByLibrary.simpleMessage("创建时间"),
|
||||
"cut": MessageLookupByLibrary.simpleMessage("剪切"),
|
||||
"dark": MessageLookupByLibrary.simpleMessage("深色"),
|
||||
"dashboard": MessageLookupByLibrary.simpleMessage("仪表盘"),
|
||||
"dataCollectionContent": MessageLookupByLibrary.simpleMessage(
|
||||
"本应用使用 Firebase Crashlytics 收集崩溃信息以改进应用稳定性。\n收集的数据包括设备信息和崩溃详情,不包含个人敏感数据。\n您可以在设置中关闭此功能。",
|
||||
),
|
||||
"dataCollectionTip": MessageLookupByLibrary.simpleMessage("数据收集说明"),
|
||||
"days": MessageLookupByLibrary.simpleMessage("天"),
|
||||
"defaultNameserver": MessageLookupByLibrary.simpleMessage("默认域名服务器"),
|
||||
"defaultNameserverDesc": MessageLookupByLibrary.simpleMessage("用于解析DNS服务器"),
|
||||
|
||||
@@ -3318,6 +3318,46 @@ class AppLocalizations {
|
||||
String get coreStatus {
|
||||
return Intl.message('Core status', name: 'coreStatus', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Data Collection Notice`
|
||||
String get dataCollectionTip {
|
||||
return Intl.message(
|
||||
'Data Collection Notice',
|
||||
name: 'dataCollectionTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `This app uses Firebase Crashlytics to collect crash information to improve app stability.\nThe collected data includes device information and crash details, but does not contain personal sensitive data.\nYou can disable this feature in settings.`
|
||||
String get dataCollectionContent {
|
||||
return Intl.message(
|
||||
'This app uses Firebase Crashlytics to collect crash information to improve app stability.\nThe collected data includes device information and crash details, but does not contain personal sensitive data.\nYou can disable this feature in settings.',
|
||||
name: 'dataCollectionContent',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Crash Analysis`
|
||||
String get crashlytics {
|
||||
return Intl.message(
|
||||
'Crash Analysis',
|
||||
name: 'crashlytics',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `When enabled, automatically uploads crash logs without sensitive information when the app crashes`
|
||||
String get crashlyticsTip {
|
||||
return Intl.message(
|
||||
'When enabled, automatically uploads crash logs without sensitive information when the app crashes',
|
||||
name: 'crashlyticsTip',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
import 'package:fl_clash/plugins/service.dart';
|
||||
import 'package:fl_clash/plugins/tile.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -23,9 +22,9 @@ Future<void> main() async {
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> _service(List<String> flags) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
globalState.isService = true;
|
||||
await globalState.init();
|
||||
await coreController.preload();
|
||||
await service?.syncAndroidState(globalState.getAndroidState());
|
||||
tile?.addListener(
|
||||
_TileListenerWithService(
|
||||
onStop: () async {
|
||||
|
||||
@@ -186,74 +186,68 @@ class AppSidebarContainer extends ConsumerWidget {
|
||||
children: [
|
||||
_buildBackground(
|
||||
context: context,
|
||||
child: Stack(
|
||||
alignment: Alignment.topRight,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (system.isMacOS) SizedBox(height: 22),
|
||||
SizedBox(height: 10),
|
||||
if (!system.isMacOS) ...[
|
||||
ClipRect(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: AppIcon(),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
],
|
||||
Expanded(
|
||||
child: ScrollConfiguration(
|
||||
behavior: HiddenBarScrollBehavior(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: NavigationRail(
|
||||
scrollable: true,
|
||||
minExtendedWidth: 200,
|
||||
backgroundColor: Colors.transparent,
|
||||
selectedLabelTextStyle: context
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
),
|
||||
unselectedLabelTextStyle: context
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
),
|
||||
destinations: navigationItems
|
||||
.map(
|
||||
(e) => NavigationRailDestination(
|
||||
icon: e.icon,
|
||||
label: Text(Intl.message(e.label.name)),
|
||||
child: SafeArea(
|
||||
child: Stack(
|
||||
alignment: Alignment.topRight,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (system.isMacOS) SizedBox(height: 22),
|
||||
SizedBox(height: 10),
|
||||
if (!system.isMacOS) ...[
|
||||
ClipRect(child: AppIcon()),
|
||||
SizedBox(height: 12),
|
||||
],
|
||||
Expanded(
|
||||
child: ScrollConfiguration(
|
||||
behavior: HiddenBarScrollBehavior(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: NavigationRail(
|
||||
scrollable: true,
|
||||
minExtendedWidth: 200,
|
||||
backgroundColor: Colors.transparent,
|
||||
selectedLabelTextStyle: context
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onDestinationSelected: (index) {
|
||||
globalState.appController.toPage(
|
||||
navigationItems[index].label,
|
||||
);
|
||||
},
|
||||
extended: false,
|
||||
selectedIndex: currentIndex,
|
||||
labelType: showLabel
|
||||
? NavigationRailLabelType.all
|
||||
: NavigationRailLabelType.none,
|
||||
unselectedLabelTextStyle: context
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
),
|
||||
destinations: navigationItems
|
||||
.map(
|
||||
(e) => NavigationRailDestination(
|
||||
icon: e.icon,
|
||||
label: Text(Intl.message(e.label.name)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onDestinationSelected: (index) {
|
||||
globalState.appController.toPage(
|
||||
navigationItems[index].label,
|
||||
);
|
||||
},
|
||||
extended: false,
|
||||
selectedIndex: currentIndex,
|
||||
labelType: showLabel
|
||||
? NavigationRailLabelType.all
|
||||
: NavigationRailLabelType.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: IconButton(
|
||||
const SizedBox(height: 16),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(appSettingProvider.notifier)
|
||||
@@ -267,12 +261,12 @@ class AppSidebarContainer extends ConsumerWidget {
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
_buildLoading(),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
_buildLoading(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
||||
@@ -95,8 +95,8 @@ class _CoreContainerState extends ConsumerState<CoreManager>
|
||||
Future<void> onCrash(String message) async {
|
||||
if (!globalState.isUserDisconnected) {
|
||||
context.showNotifier(message);
|
||||
globalState.isUserDisconnected = false;
|
||||
}
|
||||
globalState.isUserDisconnected = false;
|
||||
if (ref.read(coreStatusProvider) != CoreStatus.connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ class MessageManagerState extends State<MessageManager> {
|
||||
width: min(constraints.maxWidth, 500),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
@@ -126,11 +126,12 @@ class MessageManagerState extends State<MessageManager> {
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
TextButton(
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: () {
|
||||
_cancelMessage(messages.last.id);
|
||||
},
|
||||
child: Text(appLocalizations.cancel),
|
||||
icon: Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:fl_clash/models/app.dart';
|
||||
import 'package:fl_clash/plugins/tile.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -5,10 +6,7 @@ import 'package:flutter/material.dart';
|
||||
class TileManager extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const TileManager({
|
||||
super.key,
|
||||
required this.child,
|
||||
});
|
||||
const TileManager({super.key, required this.child});
|
||||
|
||||
@override
|
||||
State<TileManager> createState() => _TileContainerState();
|
||||
@@ -22,12 +20,18 @@ class _TileContainerState extends State<TileManager> with TileListener {
|
||||
|
||||
@override
|
||||
void onStart() {
|
||||
if (globalState.appState.isStart) {
|
||||
return;
|
||||
}
|
||||
globalState.appController.updateStatus(true);
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onStop() async {
|
||||
if (!globalState.appState.isStart) {
|
||||
return;
|
||||
}
|
||||
globalState.appController.updateStatus(false);
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@@ -272,17 +272,13 @@ class AppIcon extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: context.colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
padding: EdgeInsets.all(6),
|
||||
child: SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: CircleAvatar(
|
||||
foregroundImage: AssetImage('assets/images/icon.png'),
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, -1),
|
||||
child: Image.asset('assets/images/icon.png', width: 34, height: 34),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -445,6 +445,7 @@ abstract class AndroidState with _$AndroidState {
|
||||
required String currentProfileName,
|
||||
required String stopText,
|
||||
required bool onlyStatisticsProxy,
|
||||
required bool crashlytics,
|
||||
}) = _AndroidState;
|
||||
|
||||
factory AndroidState.fromJson(Map<String, Object?> json) =>
|
||||
|
||||
@@ -77,6 +77,8 @@ abstract class AppSettingProps with _$AppSettingProps {
|
||||
@Default(true) bool autoCheckUpdate,
|
||||
@Default(false) bool showLabel,
|
||||
@Default(false) bool disclaimerAccepted,
|
||||
@Default(false) bool crashlyticsTip,
|
||||
@Default(false) bool crashlytics,
|
||||
@Default(true) bool minimizeOnExit,
|
||||
@Default(false) bool hidden,
|
||||
@Default(false) bool developerMode,
|
||||
|
||||
@@ -4973,7 +4973,7 @@ as Validator?,
|
||||
/// @nodoc
|
||||
mixin _$AndroidState {
|
||||
|
||||
String get currentProfileName; String get stopText; bool get onlyStatisticsProxy;
|
||||
String get currentProfileName; String get stopText; bool get onlyStatisticsProxy; bool get crashlytics;
|
||||
/// Create a copy of AndroidState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -4986,16 +4986,16 @@ $AndroidStateCopyWith<AndroidState> get copyWith => _$AndroidStateCopyWithImpl<A
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AndroidState&&(identical(other.currentProfileName, currentProfileName) || other.currentProfileName == currentProfileName)&&(identical(other.stopText, stopText) || other.stopText == stopText)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AndroidState&&(identical(other.currentProfileName, currentProfileName) || other.currentProfileName == currentProfileName)&&(identical(other.stopText, stopText) || other.stopText == stopText)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,currentProfileName,stopText,onlyStatisticsProxy);
|
||||
int get hashCode => Object.hash(runtimeType,currentProfileName,stopText,onlyStatisticsProxy,crashlytics);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AndroidState(currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy)';
|
||||
return 'AndroidState(currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy, crashlytics: $crashlytics)';
|
||||
}
|
||||
|
||||
|
||||
@@ -5006,7 +5006,7 @@ abstract mixin class $AndroidStateCopyWith<$Res> {
|
||||
factory $AndroidStateCopyWith(AndroidState value, $Res Function(AndroidState) _then) = _$AndroidStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String currentProfileName, String stopText, bool onlyStatisticsProxy
|
||||
String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics
|
||||
});
|
||||
|
||||
|
||||
@@ -5023,11 +5023,12 @@ class _$AndroidStateCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of AndroidState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? currentProfileName = null,Object? stopText = null,Object? onlyStatisticsProxy = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? currentProfileName = null,Object? stopText = null,Object? onlyStatisticsProxy = null,Object? crashlytics = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
currentProfileName: null == currentProfileName ? _self.currentProfileName : currentProfileName // ignore: cast_nullable_to_non_nullable
|
||||
as String,stopText: null == stopText ? _self.stopText : stopText // ignore: cast_nullable_to_non_nullable
|
||||
as String,onlyStatisticsProxy: null == onlyStatisticsProxy ? _self.onlyStatisticsProxy : onlyStatisticsProxy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,crashlytics: null == crashlytics ? _self.crashlytics : crashlytics // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
@@ -5113,10 +5114,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String currentProfileName, String stopText, bool onlyStatisticsProxy)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AndroidState() when $default != null:
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy);case _:
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -5134,10 +5135,10 @@ return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProx
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String currentProfileName, String stopText, bool onlyStatisticsProxy) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AndroidState():
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy);case _:
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -5154,10 +5155,10 @@ return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProx
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String currentProfileName, String stopText, bool onlyStatisticsProxy)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AndroidState() when $default != null:
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy);case _:
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -5169,12 +5170,13 @@ return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProx
|
||||
@JsonSerializable()
|
||||
|
||||
class _AndroidState implements AndroidState {
|
||||
const _AndroidState({required this.currentProfileName, required this.stopText, required this.onlyStatisticsProxy});
|
||||
const _AndroidState({required this.currentProfileName, required this.stopText, required this.onlyStatisticsProxy, required this.crashlytics});
|
||||
factory _AndroidState.fromJson(Map<String, dynamic> json) => _$AndroidStateFromJson(json);
|
||||
|
||||
@override final String currentProfileName;
|
||||
@override final String stopText;
|
||||
@override final bool onlyStatisticsProxy;
|
||||
@override final bool crashlytics;
|
||||
|
||||
/// Create a copy of AndroidState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -5189,16 +5191,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AndroidState&&(identical(other.currentProfileName, currentProfileName) || other.currentProfileName == currentProfileName)&&(identical(other.stopText, stopText) || other.stopText == stopText)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AndroidState&&(identical(other.currentProfileName, currentProfileName) || other.currentProfileName == currentProfileName)&&(identical(other.stopText, stopText) || other.stopText == stopText)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,currentProfileName,stopText,onlyStatisticsProxy);
|
||||
int get hashCode => Object.hash(runtimeType,currentProfileName,stopText,onlyStatisticsProxy,crashlytics);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AndroidState(currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy)';
|
||||
return 'AndroidState(currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy, crashlytics: $crashlytics)';
|
||||
}
|
||||
|
||||
|
||||
@@ -5209,7 +5211,7 @@ abstract mixin class _$AndroidStateCopyWith<$Res> implements $AndroidStateCopyWi
|
||||
factory _$AndroidStateCopyWith(_AndroidState value, $Res Function(_AndroidState) _then) = __$AndroidStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String currentProfileName, String stopText, bool onlyStatisticsProxy
|
||||
String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics
|
||||
});
|
||||
|
||||
|
||||
@@ -5226,11 +5228,12 @@ class __$AndroidStateCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of AndroidState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? currentProfileName = null,Object? stopText = null,Object? onlyStatisticsProxy = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? currentProfileName = null,Object? stopText = null,Object? onlyStatisticsProxy = null,Object? crashlytics = null,}) {
|
||||
return _then(_AndroidState(
|
||||
currentProfileName: null == currentProfileName ? _self.currentProfileName : currentProfileName // ignore: cast_nullable_to_non_nullable
|
||||
as String,stopText: null == stopText ? _self.stopText : stopText // ignore: cast_nullable_to_non_nullable
|
||||
as String,onlyStatisticsProxy: null == onlyStatisticsProxy ? _self.onlyStatisticsProxy : onlyStatisticsProxy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,crashlytics: null == crashlytics ? _self.crashlytics : crashlytics // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -244,6 +244,7 @@ _AndroidState _$AndroidStateFromJson(Map<String, dynamic> json) =>
|
||||
currentProfileName: json['currentProfileName'] as String,
|
||||
stopText: json['stopText'] as String,
|
||||
onlyStatisticsProxy: json['onlyStatisticsProxy'] as bool,
|
||||
crashlytics: json['crashlytics'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AndroidStateToJson(_AndroidState instance) =>
|
||||
@@ -251,6 +252,7 @@ Map<String, dynamic> _$AndroidStateToJson(_AndroidState instance) =>
|
||||
'currentProfileName': instance.currentProfileName,
|
||||
'stopText': instance.stopText,
|
||||
'onlyStatisticsProxy': instance.onlyStatisticsProxy,
|
||||
'crashlytics': instance.crashlytics,
|
||||
};
|
||||
|
||||
_Script _$ScriptFromJson(Map<String, dynamic> json) => _Script(
|
||||
|
||||
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$AppSettingProps {
|
||||
|
||||
String? get locale;@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List<DashboardWidget> get dashboardWidgets; bool get onlyStatisticsProxy; bool get autoLaunch; bool get silentLaunch; bool get autoRun; bool get openLogs; bool get closeConnections; String get testUrl; bool get isAnimateToPage; bool get autoCheckUpdate; bool get showLabel; bool get disclaimerAccepted; bool get minimizeOnExit; bool get hidden; bool get developerMode; RecoveryStrategy get recoveryStrategy;
|
||||
String? get locale;@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List<DashboardWidget> get dashboardWidgets; bool get onlyStatisticsProxy; bool get autoLaunch; bool get silentLaunch; bool get autoRun; bool get openLogs; bool get closeConnections; String get testUrl; bool get isAnimateToPage; bool get autoCheckUpdate; bool get showLabel; bool get disclaimerAccepted; bool get crashlyticsTip; bool get crashlytics; bool get minimizeOnExit; bool get hidden; bool get developerMode; RecoveryStrategy get recoveryStrategy;
|
||||
/// Create a copy of AppSettingProps
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -28,16 +28,16 @@ $AppSettingPropsCopyWith<AppSettingProps> get copyWith => _$AppSettingPropsCopyW
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettingProps&&(identical(other.locale, locale) || other.locale == locale)&&const DeepCollectionEquality().equals(other.dashboardWidgets, dashboardWidgets)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.autoLaunch, autoLaunch) || other.autoLaunch == autoLaunch)&&(identical(other.silentLaunch, silentLaunch) || other.silentLaunch == silentLaunch)&&(identical(other.autoRun, autoRun) || other.autoRun == autoRun)&&(identical(other.openLogs, openLogs) || other.openLogs == openLogs)&&(identical(other.closeConnections, closeConnections) || other.closeConnections == closeConnections)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.isAnimateToPage, isAnimateToPage) || other.isAnimateToPage == isAnimateToPage)&&(identical(other.autoCheckUpdate, autoCheckUpdate) || other.autoCheckUpdate == autoCheckUpdate)&&(identical(other.showLabel, showLabel) || other.showLabel == showLabel)&&(identical(other.disclaimerAccepted, disclaimerAccepted) || other.disclaimerAccepted == disclaimerAccepted)&&(identical(other.minimizeOnExit, minimizeOnExit) || other.minimizeOnExit == minimizeOnExit)&&(identical(other.hidden, hidden) || other.hidden == hidden)&&(identical(other.developerMode, developerMode) || other.developerMode == developerMode)&&(identical(other.recoveryStrategy, recoveryStrategy) || other.recoveryStrategy == recoveryStrategy));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettingProps&&(identical(other.locale, locale) || other.locale == locale)&&const DeepCollectionEquality().equals(other.dashboardWidgets, dashboardWidgets)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.autoLaunch, autoLaunch) || other.autoLaunch == autoLaunch)&&(identical(other.silentLaunch, silentLaunch) || other.silentLaunch == silentLaunch)&&(identical(other.autoRun, autoRun) || other.autoRun == autoRun)&&(identical(other.openLogs, openLogs) || other.openLogs == openLogs)&&(identical(other.closeConnections, closeConnections) || other.closeConnections == closeConnections)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.isAnimateToPage, isAnimateToPage) || other.isAnimateToPage == isAnimateToPage)&&(identical(other.autoCheckUpdate, autoCheckUpdate) || other.autoCheckUpdate == autoCheckUpdate)&&(identical(other.showLabel, showLabel) || other.showLabel == showLabel)&&(identical(other.disclaimerAccepted, disclaimerAccepted) || other.disclaimerAccepted == disclaimerAccepted)&&(identical(other.crashlyticsTip, crashlyticsTip) || other.crashlyticsTip == crashlyticsTip)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics)&&(identical(other.minimizeOnExit, minimizeOnExit) || other.minimizeOnExit == minimizeOnExit)&&(identical(other.hidden, hidden) || other.hidden == hidden)&&(identical(other.developerMode, developerMode) || other.developerMode == developerMode)&&(identical(other.recoveryStrategy, recoveryStrategy) || other.recoveryStrategy == recoveryStrategy));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,locale,const DeepCollectionEquality().hash(dashboardWidgets),onlyStatisticsProxy,autoLaunch,silentLaunch,autoRun,openLogs,closeConnections,testUrl,isAnimateToPage,autoCheckUpdate,showLabel,disclaimerAccepted,minimizeOnExit,hidden,developerMode,recoveryStrategy);
|
||||
int get hashCode => Object.hashAll([runtimeType,locale,const DeepCollectionEquality().hash(dashboardWidgets),onlyStatisticsProxy,autoLaunch,silentLaunch,autoRun,openLogs,closeConnections,testUrl,isAnimateToPage,autoCheckUpdate,showLabel,disclaimerAccepted,crashlyticsTip,crashlytics,minimizeOnExit,hidden,developerMode,recoveryStrategy]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, recoveryStrategy: $recoveryStrategy)';
|
||||
return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, crashlyticsTip: $crashlyticsTip, crashlytics: $crashlytics, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, recoveryStrategy: $recoveryStrategy)';
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ abstract mixin class $AppSettingPropsCopyWith<$Res> {
|
||||
factory $AppSettingPropsCopyWith(AppSettingProps value, $Res Function(AppSettingProps) _then) = _$AppSettingPropsCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String? locale,@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List<DashboardWidget> dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool minimizeOnExit, bool hidden, bool developerMode, RecoveryStrategy recoveryStrategy
|
||||
String? locale,@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List<DashboardWidget> dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RecoveryStrategy recoveryStrategy
|
||||
});
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ class _$AppSettingPropsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of AppSettingProps
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? locale = freezed,Object? dashboardWidgets = null,Object? onlyStatisticsProxy = null,Object? autoLaunch = null,Object? silentLaunch = null,Object? autoRun = null,Object? openLogs = null,Object? closeConnections = null,Object? testUrl = null,Object? isAnimateToPage = null,Object? autoCheckUpdate = null,Object? showLabel = null,Object? disclaimerAccepted = null,Object? minimizeOnExit = null,Object? hidden = null,Object? developerMode = null,Object? recoveryStrategy = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? locale = freezed,Object? dashboardWidgets = null,Object? onlyStatisticsProxy = null,Object? autoLaunch = null,Object? silentLaunch = null,Object? autoRun = null,Object? openLogs = null,Object? closeConnections = null,Object? testUrl = null,Object? isAnimateToPage = null,Object? autoCheckUpdate = null,Object? showLabel = null,Object? disclaimerAccepted = null,Object? crashlyticsTip = null,Object? crashlytics = null,Object? minimizeOnExit = null,Object? hidden = null,Object? developerMode = null,Object? recoveryStrategy = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
locale: freezed == locale ? _self.locale : locale // ignore: cast_nullable_to_non_nullable
|
||||
as String?,dashboardWidgets: null == dashboardWidgets ? _self.dashboardWidgets : dashboardWidgets // ignore: cast_nullable_to_non_nullable
|
||||
@@ -80,6 +80,8 @@ as String,isAnimateToPage: null == isAnimateToPage ? _self.isAnimateToPage : isA
|
||||
as bool,autoCheckUpdate: null == autoCheckUpdate ? _self.autoCheckUpdate : autoCheckUpdate // ignore: cast_nullable_to_non_nullable
|
||||
as bool,showLabel: null == showLabel ? _self.showLabel : showLabel // ignore: cast_nullable_to_non_nullable
|
||||
as bool,disclaimerAccepted: null == disclaimerAccepted ? _self.disclaimerAccepted : disclaimerAccepted // ignore: cast_nullable_to_non_nullable
|
||||
as bool,crashlyticsTip: null == crashlyticsTip ? _self.crashlyticsTip : crashlyticsTip // ignore: cast_nullable_to_non_nullable
|
||||
as bool,crashlytics: null == crashlytics ? _self.crashlytics : crashlytics // ignore: cast_nullable_to_non_nullable
|
||||
as bool,minimizeOnExit: null == minimizeOnExit ? _self.minimizeOnExit : minimizeOnExit // ignore: cast_nullable_to_non_nullable
|
||||
as bool,hidden: null == hidden ? _self.hidden : hidden // ignore: cast_nullable_to_non_nullable
|
||||
as bool,developerMode: null == developerMode ? _self.developerMode : developerMode // ignore: cast_nullable_to_non_nullable
|
||||
@@ -169,10 +171,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List<DashboardWidget> dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool minimizeOnExit, bool hidden, bool developerMode, RecoveryStrategy recoveryStrategy)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List<DashboardWidget> dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RecoveryStrategy recoveryStrategy)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppSettingProps() when $default != null:
|
||||
return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.recoveryStrategy);case _:
|
||||
return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.recoveryStrategy);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -190,10 +192,10 @@ return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_t
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List<DashboardWidget> dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool minimizeOnExit, bool hidden, bool developerMode, RecoveryStrategy recoveryStrategy) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List<DashboardWidget> dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RecoveryStrategy recoveryStrategy) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppSettingProps():
|
||||
return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.recoveryStrategy);case _:
|
||||
return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.recoveryStrategy);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -210,10 +212,10 @@ return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_t
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List<DashboardWidget> dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool minimizeOnExit, bool hidden, bool developerMode, RecoveryStrategy recoveryStrategy)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List<DashboardWidget> dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RecoveryStrategy recoveryStrategy)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppSettingProps() when $default != null:
|
||||
return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.recoveryStrategy);case _:
|
||||
return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.recoveryStrategy);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -225,7 +227,7 @@ return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_t
|
||||
@JsonSerializable()
|
||||
|
||||
class _AppSettingProps implements AppSettingProps {
|
||||
const _AppSettingProps({this.locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) final List<DashboardWidget> dashboardWidgets = defaultDashboardWidgets, this.onlyStatisticsProxy = false, this.autoLaunch = false, this.silentLaunch = false, this.autoRun = false, this.openLogs = false, this.closeConnections = true, this.testUrl = defaultTestUrl, this.isAnimateToPage = true, this.autoCheckUpdate = true, this.showLabel = false, this.disclaimerAccepted = false, this.minimizeOnExit = true, this.hidden = false, this.developerMode = false, this.recoveryStrategy = RecoveryStrategy.compatible}): _dashboardWidgets = dashboardWidgets;
|
||||
const _AppSettingProps({this.locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) final List<DashboardWidget> dashboardWidgets = defaultDashboardWidgets, this.onlyStatisticsProxy = false, this.autoLaunch = false, this.silentLaunch = false, this.autoRun = false, this.openLogs = false, this.closeConnections = true, this.testUrl = defaultTestUrl, this.isAnimateToPage = true, this.autoCheckUpdate = true, this.showLabel = false, this.disclaimerAccepted = false, this.crashlyticsTip = false, this.crashlytics = false, this.minimizeOnExit = true, this.hidden = false, this.developerMode = false, this.recoveryStrategy = RecoveryStrategy.compatible}): _dashboardWidgets = dashboardWidgets;
|
||||
factory _AppSettingProps.fromJson(Map<String, dynamic> json) => _$AppSettingPropsFromJson(json);
|
||||
|
||||
@override final String? locale;
|
||||
@@ -247,6 +249,8 @@ class _AppSettingProps implements AppSettingProps {
|
||||
@override@JsonKey() final bool autoCheckUpdate;
|
||||
@override@JsonKey() final bool showLabel;
|
||||
@override@JsonKey() final bool disclaimerAccepted;
|
||||
@override@JsonKey() final bool crashlyticsTip;
|
||||
@override@JsonKey() final bool crashlytics;
|
||||
@override@JsonKey() final bool minimizeOnExit;
|
||||
@override@JsonKey() final bool hidden;
|
||||
@override@JsonKey() final bool developerMode;
|
||||
@@ -265,16 +269,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettingProps&&(identical(other.locale, locale) || other.locale == locale)&&const DeepCollectionEquality().equals(other._dashboardWidgets, _dashboardWidgets)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.autoLaunch, autoLaunch) || other.autoLaunch == autoLaunch)&&(identical(other.silentLaunch, silentLaunch) || other.silentLaunch == silentLaunch)&&(identical(other.autoRun, autoRun) || other.autoRun == autoRun)&&(identical(other.openLogs, openLogs) || other.openLogs == openLogs)&&(identical(other.closeConnections, closeConnections) || other.closeConnections == closeConnections)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.isAnimateToPage, isAnimateToPage) || other.isAnimateToPage == isAnimateToPage)&&(identical(other.autoCheckUpdate, autoCheckUpdate) || other.autoCheckUpdate == autoCheckUpdate)&&(identical(other.showLabel, showLabel) || other.showLabel == showLabel)&&(identical(other.disclaimerAccepted, disclaimerAccepted) || other.disclaimerAccepted == disclaimerAccepted)&&(identical(other.minimizeOnExit, minimizeOnExit) || other.minimizeOnExit == minimizeOnExit)&&(identical(other.hidden, hidden) || other.hidden == hidden)&&(identical(other.developerMode, developerMode) || other.developerMode == developerMode)&&(identical(other.recoveryStrategy, recoveryStrategy) || other.recoveryStrategy == recoveryStrategy));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettingProps&&(identical(other.locale, locale) || other.locale == locale)&&const DeepCollectionEquality().equals(other._dashboardWidgets, _dashboardWidgets)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.autoLaunch, autoLaunch) || other.autoLaunch == autoLaunch)&&(identical(other.silentLaunch, silentLaunch) || other.silentLaunch == silentLaunch)&&(identical(other.autoRun, autoRun) || other.autoRun == autoRun)&&(identical(other.openLogs, openLogs) || other.openLogs == openLogs)&&(identical(other.closeConnections, closeConnections) || other.closeConnections == closeConnections)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.isAnimateToPage, isAnimateToPage) || other.isAnimateToPage == isAnimateToPage)&&(identical(other.autoCheckUpdate, autoCheckUpdate) || other.autoCheckUpdate == autoCheckUpdate)&&(identical(other.showLabel, showLabel) || other.showLabel == showLabel)&&(identical(other.disclaimerAccepted, disclaimerAccepted) || other.disclaimerAccepted == disclaimerAccepted)&&(identical(other.crashlyticsTip, crashlyticsTip) || other.crashlyticsTip == crashlyticsTip)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics)&&(identical(other.minimizeOnExit, minimizeOnExit) || other.minimizeOnExit == minimizeOnExit)&&(identical(other.hidden, hidden) || other.hidden == hidden)&&(identical(other.developerMode, developerMode) || other.developerMode == developerMode)&&(identical(other.recoveryStrategy, recoveryStrategy) || other.recoveryStrategy == recoveryStrategy));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,locale,const DeepCollectionEquality().hash(_dashboardWidgets),onlyStatisticsProxy,autoLaunch,silentLaunch,autoRun,openLogs,closeConnections,testUrl,isAnimateToPage,autoCheckUpdate,showLabel,disclaimerAccepted,minimizeOnExit,hidden,developerMode,recoveryStrategy);
|
||||
int get hashCode => Object.hashAll([runtimeType,locale,const DeepCollectionEquality().hash(_dashboardWidgets),onlyStatisticsProxy,autoLaunch,silentLaunch,autoRun,openLogs,closeConnections,testUrl,isAnimateToPage,autoCheckUpdate,showLabel,disclaimerAccepted,crashlyticsTip,crashlytics,minimizeOnExit,hidden,developerMode,recoveryStrategy]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, recoveryStrategy: $recoveryStrategy)';
|
||||
return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, crashlyticsTip: $crashlyticsTip, crashlytics: $crashlytics, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, recoveryStrategy: $recoveryStrategy)';
|
||||
}
|
||||
|
||||
|
||||
@@ -285,7 +289,7 @@ abstract mixin class _$AppSettingPropsCopyWith<$Res> implements $AppSettingProps
|
||||
factory _$AppSettingPropsCopyWith(_AppSettingProps value, $Res Function(_AppSettingProps) _then) = __$AppSettingPropsCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String? locale,@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List<DashboardWidget> dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool minimizeOnExit, bool hidden, bool developerMode, RecoveryStrategy recoveryStrategy
|
||||
String? locale,@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List<DashboardWidget> dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RecoveryStrategy recoveryStrategy
|
||||
});
|
||||
|
||||
|
||||
@@ -302,7 +306,7 @@ class __$AppSettingPropsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of AppSettingProps
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? locale = freezed,Object? dashboardWidgets = null,Object? onlyStatisticsProxy = null,Object? autoLaunch = null,Object? silentLaunch = null,Object? autoRun = null,Object? openLogs = null,Object? closeConnections = null,Object? testUrl = null,Object? isAnimateToPage = null,Object? autoCheckUpdate = null,Object? showLabel = null,Object? disclaimerAccepted = null,Object? minimizeOnExit = null,Object? hidden = null,Object? developerMode = null,Object? recoveryStrategy = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? locale = freezed,Object? dashboardWidgets = null,Object? onlyStatisticsProxy = null,Object? autoLaunch = null,Object? silentLaunch = null,Object? autoRun = null,Object? openLogs = null,Object? closeConnections = null,Object? testUrl = null,Object? isAnimateToPage = null,Object? autoCheckUpdate = null,Object? showLabel = null,Object? disclaimerAccepted = null,Object? crashlyticsTip = null,Object? crashlytics = null,Object? minimizeOnExit = null,Object? hidden = null,Object? developerMode = null,Object? recoveryStrategy = null,}) {
|
||||
return _then(_AppSettingProps(
|
||||
locale: freezed == locale ? _self.locale : locale // ignore: cast_nullable_to_non_nullable
|
||||
as String?,dashboardWidgets: null == dashboardWidgets ? _self._dashboardWidgets : dashboardWidgets // ignore: cast_nullable_to_non_nullable
|
||||
@@ -317,6 +321,8 @@ as String,isAnimateToPage: null == isAnimateToPage ? _self.isAnimateToPage : isA
|
||||
as bool,autoCheckUpdate: null == autoCheckUpdate ? _self.autoCheckUpdate : autoCheckUpdate // ignore: cast_nullable_to_non_nullable
|
||||
as bool,showLabel: null == showLabel ? _self.showLabel : showLabel // ignore: cast_nullable_to_non_nullable
|
||||
as bool,disclaimerAccepted: null == disclaimerAccepted ? _self.disclaimerAccepted : disclaimerAccepted // ignore: cast_nullable_to_non_nullable
|
||||
as bool,crashlyticsTip: null == crashlyticsTip ? _self.crashlyticsTip : crashlyticsTip // ignore: cast_nullable_to_non_nullable
|
||||
as bool,crashlytics: null == crashlytics ? _self.crashlytics : crashlytics // ignore: cast_nullable_to_non_nullable
|
||||
as bool,minimizeOnExit: null == minimizeOnExit ? _self.minimizeOnExit : minimizeOnExit // ignore: cast_nullable_to_non_nullable
|
||||
as bool,hidden: null == hidden ? _self.hidden : hidden // ignore: cast_nullable_to_non_nullable
|
||||
as bool,developerMode: null == developerMode ? _self.developerMode : developerMode // ignore: cast_nullable_to_non_nullable
|
||||
|
||||
@@ -23,6 +23,8 @@ _AppSettingProps _$AppSettingPropsFromJson(Map<String, dynamic> json) =>
|
||||
autoCheckUpdate: json['autoCheckUpdate'] as bool? ?? true,
|
||||
showLabel: json['showLabel'] as bool? ?? false,
|
||||
disclaimerAccepted: json['disclaimerAccepted'] as bool? ?? false,
|
||||
crashlyticsTip: json['crashlyticsTip'] as bool? ?? false,
|
||||
crashlytics: json['crashlytics'] as bool? ?? false,
|
||||
minimizeOnExit: json['minimizeOnExit'] as bool? ?? true,
|
||||
hidden: json['hidden'] as bool? ?? false,
|
||||
developerMode: json['developerMode'] as bool? ?? false,
|
||||
@@ -51,6 +53,8 @@ Map<String, dynamic> _$AppSettingPropsToJson(_AppSettingProps instance) =>
|
||||
'autoCheckUpdate': instance.autoCheckUpdate,
|
||||
'showLabel': instance.showLabel,
|
||||
'disclaimerAccepted': instance.disclaimerAccepted,
|
||||
'crashlyticsTip': instance.crashlyticsTip,
|
||||
'crashlytics': instance.crashlytics,
|
||||
'minimizeOnExit': instance.minimizeOnExit,
|
||||
'hidden': instance.hidden,
|
||||
'developerMode': instance.developerMode,
|
||||
|
||||
@@ -86,7 +86,11 @@ class Service {
|
||||
}
|
||||
|
||||
Future<String> init() async {
|
||||
return await methodChannel.invokeMethod<String>('init') ?? '';
|
||||
return await methodChannel.invokeMethod<String>(
|
||||
'init',
|
||||
!globalState.isService,
|
||||
) ??
|
||||
'';
|
||||
}
|
||||
|
||||
Future<bool> shutdown() async {
|
||||
|
||||
@@ -6,6 +6,9 @@ part of '../app.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(RealTunEnable)
|
||||
const realTunEnableProvider = RealTunEnableProvider._();
|
||||
|
||||
@@ -1486,6 +1489,3 @@ abstract class _$QueryMap extends $Notifier<Map<QueryTag, String>> {
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
||||
@@ -6,6 +6,9 @@ part of '../config.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(AppSetting)
|
||||
const appSettingProvider = AppSettingProvider._();
|
||||
|
||||
@@ -693,6 +696,3 @@ abstract class _$PatchClashConfig extends $Notifier<ClashConfig> {
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
||||
@@ -6,6 +6,9 @@ part of '../state.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(configState)
|
||||
const configStateProvider = ConfigStateProvider._();
|
||||
|
||||
@@ -431,7 +434,7 @@ final class ContentWidthProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$contentWidthHash() => r'9ac0dc2bcdc957160f59d6065cd7f846f41a95b1';
|
||||
String _$contentWidthHash() => r'4710fee8f91b08dc88520798fb6b1d4f61f090e6';
|
||||
|
||||
@ProviderFor(dashboardState)
|
||||
const dashboardStateProvider = DashboardStateProvider._();
|
||||
@@ -1952,8 +1955,14 @@ final class GetProfileOverrideDataFamily extends $Family
|
||||
@ProviderFor(layoutChange)
|
||||
const layoutChangeProvider = LayoutChangeProvider._();
|
||||
|
||||
final class LayoutChangeProvider extends $FunctionalProvider<VM2?, VM2?, VM2?>
|
||||
with $Provider<VM2?> {
|
||||
final class LayoutChangeProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
VM2<dynamic, dynamic>?,
|
||||
VM2<dynamic, dynamic>?,
|
||||
VM2<dynamic, dynamic>?
|
||||
>
|
||||
with $Provider<VM2<dynamic, dynamic>?> {
|
||||
const LayoutChangeProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
@@ -1970,19 +1979,20 @@ final class LayoutChangeProvider extends $FunctionalProvider<VM2?, VM2?, VM2?>
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<VM2?> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
$ProviderElement<VM2<dynamic, dynamic>?> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
VM2? create(Ref ref) {
|
||||
VM2<dynamic, dynamic>? create(Ref ref) {
|
||||
return layoutChange(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(VM2? value) {
|
||||
Override overrideWithValue(VM2<dynamic, dynamic>? value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<VM2?>(value),
|
||||
providerOverride: $SyncValueProvider<VM2<dynamic, dynamic>?>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2342,7 +2352,7 @@ final class AndroidStateProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$androidStateHash() => r'bdf7baeb4ed4041233f06f0aede8d4d41489ebd6';
|
||||
String _$androidStateHash() => r'9f527fbb00c7e0c177f023e77d2f23458543d72f';
|
||||
|
||||
@ProviderFor(Query)
|
||||
const queryProvider = QueryFamily._();
|
||||
@@ -2432,6 +2442,3 @@ abstract class _$Query extends $Notifier<String> {
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
||||
@@ -629,10 +629,14 @@ AndroidState androidState(Ref ref) {
|
||||
appSettingProvider.select((state) => state.onlyStatisticsProxy),
|
||||
);
|
||||
ref.watch((appSettingProvider).select((state) => state.locale));
|
||||
final crashlytics = ref.watch(
|
||||
(appSettingProvider).select((state) => state.crashlytics),
|
||||
);
|
||||
return AndroidState(
|
||||
currentProfileName: currentProfileName,
|
||||
onlyStatisticsProxy: onlyStatisticsProxy,
|
||||
stopText: appLocalizations.stop,
|
||||
crashlytics: crashlytics,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ class GlobalState {
|
||||
AppController? _appController;
|
||||
bool isInit = false;
|
||||
bool isUserDisconnected = false;
|
||||
bool isService = false;
|
||||
|
||||
bool get isStart => startTime != null && startTime!.isBeforeNow;
|
||||
|
||||
@@ -188,9 +189,11 @@ class GlobalState {
|
||||
String? title,
|
||||
String? confirmText,
|
||||
bool cancelable = true,
|
||||
bool? dismissible,
|
||||
}) async {
|
||||
return await showCommonDialog<bool>(
|
||||
context: context,
|
||||
dismissible: dismissible,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return CommonDialog(
|
||||
@@ -249,14 +252,14 @@ class GlobalState {
|
||||
Future<T?> showCommonDialog<T>({
|
||||
required Widget child,
|
||||
BuildContext? context,
|
||||
bool dismissible = true,
|
||||
bool? dismissible,
|
||||
}) async {
|
||||
return await showModal<T>(
|
||||
useRootNavigator: false,
|
||||
context: context ?? globalState.navigatorKey.currentContext!,
|
||||
configuration: FadeScaleTransitionConfiguration(
|
||||
barrierColor: Colors.black38,
|
||||
barrierDismissible: dismissible,
|
||||
barrierDismissible: dismissible ?? true,
|
||||
),
|
||||
builder: (_) => child,
|
||||
filter: commonFilter,
|
||||
@@ -325,6 +328,7 @@ class GlobalState {
|
||||
currentProfileName: config.currentProfile?.label ?? '',
|
||||
onlyStatisticsProxy: config.appSetting.onlyStatisticsProxy,
|
||||
stopText: appLocalizations.stop,
|
||||
crashlytics: config.appSetting.crashlytics,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +214,29 @@ class OpenLogsItem extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class CrashlyticsItem extends ConsumerWidget {
|
||||
const CrashlyticsItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final crashlytics = ref.watch(
|
||||
appSettingProvider.select((state) => state.crashlytics),
|
||||
);
|
||||
return ListItem.switchItem(
|
||||
title: Text(appLocalizations.crashlytics),
|
||||
subtitle: Text(appLocalizations.crashlyticsTip),
|
||||
delegate: SwitchDelegate(
|
||||
value: crashlytics,
|
||||
onChanged: (bool value) {
|
||||
ref
|
||||
.read(appSettingProvider.notifier)
|
||||
.updateState((state) => state.copyWith(crashlytics: value));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AutoCheckUpdateItem extends ConsumerWidget {
|
||||
const AutoCheckUpdateItem({super.key});
|
||||
|
||||
@@ -256,6 +279,7 @@ class ApplicationSettingView extends StatelessWidget {
|
||||
OpenLogsItem(),
|
||||
CloseConnectionsItem(),
|
||||
UsageItem(),
|
||||
if (system.isAndroid) CrashlyticsItem(),
|
||||
AutoCheckUpdateItem(),
|
||||
];
|
||||
return ListView.separated(
|
||||
|
||||
@@ -51,7 +51,7 @@ class UaItem extends ConsumerWidget {
|
||||
subtitle: Text(globalUa ?? appLocalizations.defaultText),
|
||||
delegate: OptionsDelegate<String?>(
|
||||
title: 'UA',
|
||||
options: [null, 'clash-verge/v1.6.6', 'ClashforWindows/0.19.23'],
|
||||
options: [null, 'clash-verge/v2.4.2', 'ClashforWindows/0.19.23'],
|
||||
value: globalUa,
|
||||
onChanged: (value) {
|
||||
ref
|
||||
|
||||
@@ -249,6 +249,7 @@ class TrackerInfoDetailView extends StatelessWidget {
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 20,
|
||||
children: [
|
||||
Text(appLocalizations.proxyChains),
|
||||
Flexible(child: chains),
|
||||
|
||||
@@ -6,6 +6,7 @@ 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';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
@@ -37,7 +38,9 @@ class DeveloperView extends ConsumerWidget {
|
||||
ListItem(
|
||||
title: Text(appLocalizations.crashTest),
|
||||
onTap: () {
|
||||
coreController.crash();
|
||||
if (kDebugMode) {
|
||||
coreController.crash();
|
||||
}
|
||||
},
|
||||
),
|
||||
ListItem(
|
||||
|
||||
@@ -29,10 +29,10 @@ class ResourcesView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const geoItems = <GeoItem>[
|
||||
GeoItem(label: 'GeoIp', fileName: geoIpFileName, key: 'geoip'),
|
||||
GeoItem(label: 'GeoSite', fileName: geoSiteFileName, key: 'geosite'),
|
||||
GeoItem(label: 'MMDB', fileName: mmdbFileName, key: 'mmdb'),
|
||||
GeoItem(label: 'ASN', fileName: asnFileName, key: 'asn'),
|
||||
GeoItem(label: 'GEOIP', fileName: GEOIP, key: 'geoip'),
|
||||
GeoItem(label: 'GEOSITE', fileName: GEOSITE, key: 'geosite'),
|
||||
GeoItem(label: 'MMDB', fileName: MMDB, key: 'mmdb'),
|
||||
GeoItem(label: 'ASN', fileName: ASN, key: 'asn'),
|
||||
];
|
||||
|
||||
return CommonScaffold(
|
||||
|
||||
118
pubspec.lock
118
pubspec.lock
@@ -17,6 +17,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.6.0"
|
||||
analyzer_buffer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer_buffer
|
||||
sha256: f7833bee67c03c37241c67f8741b17cc501b69d9758df7a5a4a13ed6c947be43
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.10"
|
||||
analyzer_plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -101,10 +109,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "6439a9c71a4e6eca8d9490c1b380a25b02675aa688137dfbe66d2062884a23ac"
|
||||
sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "3.1.0"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -125,26 +133,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "2b21a125d66a86b9511cc3fb6c668c42e9a1185083922bf60e46d483a81a9712"
|
||||
sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "3.0.3"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: fd3c09f4bbff7fa6e8d8ef688a0b2e8a6384e6483a25af0dac75fef362bcfe6f
|
||||
sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
version: "2.7.1"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: ab27e46c8aa233e610cf6084ee6d8a22c6f873a0a9929241d8855b7a72978ae7
|
||||
sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.3.0"
|
||||
version: "9.3.1"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -157,10 +165,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb
|
||||
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.11.1"
|
||||
version: "8.12.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -213,10 +221,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
|
||||
sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.10.1"
|
||||
version: "4.11.0"
|
||||
collection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -413,10 +421,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22
|
||||
sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.3.2"
|
||||
version: "10.3.3"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -496,26 +504,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab"
|
||||
sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.29"
|
||||
version: "2.0.30"
|
||||
flutter_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: "56c3cc75d04c34fc824ce1d52ec9076c431e3c47ed55fd8cbf9756ca6d50479e"
|
||||
sha256: ca2480512a8e840291325249f4857e363ffa5d1b77b132e189c9313a9d9fb9e0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0-dev.17"
|
||||
version: "3.0.0"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845
|
||||
sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.2.1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -530,10 +538,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: freezed
|
||||
sha256: da32f8ba8cfcd4ec71d9decc8cbf28bd2c31b5283d9887eb51eb4a0659d8110c
|
||||
sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "3.2.3"
|
||||
freezed_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -658,10 +666,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: e83b2b05141469c5e19d77e1dfa11096b6b1567d09065b2265d7c6904560050c
|
||||
sha256: a45bef33deb24839a51fb85a4d9e504ead2b1ad1c4779d02d09bf6a8857cdd52
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13"
|
||||
version: "0.8.13+2"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -762,10 +770,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: json_serializable
|
||||
sha256: "3f2913b7c2430afe8ac5afe6fb15c1de4a60af4f630625e6e238f80ba4b80cbd"
|
||||
sha256: "33a040668b31b320aafa4822b7b1e177e163fc3c1e835c6750319d4ab23aa6fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.11.0"
|
||||
version: "6.11.1"
|
||||
launch_at_startup:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -778,10 +786,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.1"
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -938,10 +946,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
|
||||
sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.17"
|
||||
version: "2.2.18"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -978,10 +986,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
|
||||
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
version: "7.0.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1058,42 +1066,42 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: "82507cfb140c044f12e929c054dcdfc478359f473bcd2976af26908318e91b8e"
|
||||
sha256: "135723ec44dfba141bc4696224048a408336e794228a0117439e7ad0a8be6d05"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0-dev.17"
|
||||
version: "3.0.0"
|
||||
riverpod_analyzer_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod_analyzer_utils
|
||||
sha256: ce9dfa8dccc5029535a09d1582681c894c8853613aaca5869d372348cf432114
|
||||
sha256: c971e50f678d55c87d9e3b5015df7fcaadb2302a84ea101247cf99387b4554fe
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0-dev.4"
|
||||
version: "1.0.0-dev.6"
|
||||
riverpod_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: riverpod_annotation
|
||||
sha256: d4449ce911fe1e211a2f6fbc110c907859b01419f720f604791fe8583a06620e
|
||||
sha256: "1f9c1bc10b13f68ebc83ab391e77a865ff796880461d2a60c5ce301e1fb65f51"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0-dev.17"
|
||||
version: "3.0.0"
|
||||
riverpod_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: riverpod_generator
|
||||
sha256: "16569af989111e5087da6cfd71660eb0dbcfb87e5395cfa5181ce089ff4f7729"
|
||||
sha256: "178d6bfa6de66d7db97db2466e5fad2da91a678ab376a7309235eec731755046"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0-dev.17"
|
||||
version: "3.0.0"
|
||||
riverpod_lint:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: riverpod_lint
|
||||
sha256: f9403db89a399bbdf50a56883cf0eaa42b69e3fef005cd967ff5072d7e90b838
|
||||
sha256: "89bce8d40bed52aa89b43ef45cfe473a9c7736629d61fc659c95e4e9f4d571a6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0-dev.17"
|
||||
version: "3.0.0"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1154,10 +1162,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e"
|
||||
sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.11"
|
||||
version: "2.4.12"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1255,10 +1263,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca
|
||||
sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.7"
|
||||
version: "1.3.8"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1463,10 +1471,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "0aedad096a85b49df2e4725fa32118f9fa580f3b14af7a2d2221896a02cd5656"
|
||||
sha256: "07cffecb7d68cbc6437cd803d5f11a86fe06736735c3dfe46ff73bcb0f958eed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.17"
|
||||
version: "6.3.21"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1543,10 +1551,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: ca81fdfaf62a5ab45d7296614aea108d2c7d0efca8393e96174bf4d51e6725b0
|
||||
sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.18"
|
||||
version: "1.1.19"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1567,10 +1575,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a"
|
||||
sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.1.3"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1654,10 +1662,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
version: "6.6.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1668,4 +1676,4 @@ packages:
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.29.0"
|
||||
flutter: ">=3.35.0"
|
||||
|
||||
22
pubspec.yaml
22
pubspec.yaml
@@ -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+2025090701
|
||||
version: 0.8.88+2025092001
|
||||
environment:
|
||||
sdk: '>=3.8.0 <4.0.0'
|
||||
|
||||
@@ -22,17 +22,17 @@ dependencies:
|
||||
path: plugins/window_ext
|
||||
launch_at_startup: ^0.5.1
|
||||
json_annotation: ^4.9.0
|
||||
file_picker: ^10.3.1
|
||||
mobile_scanner: ^7.0.0
|
||||
file_picker: ^10.3.3
|
||||
mobile_scanner: ^7.0.1
|
||||
app_links: ^6.4.0
|
||||
win32_registry: ^2.0.0
|
||||
tray_manager: ^0.5.0
|
||||
collection: ^1.18.0
|
||||
collection: ^1.19.1
|
||||
animations: ^2.0.11
|
||||
package_info_plus: ^8.0.0
|
||||
url_launcher: ^6.2.6
|
||||
url_launcher: ^6.3.2
|
||||
freezed_annotation: ^3.1.0
|
||||
image_picker: ^1.1.2
|
||||
image_picker: ^1.2.0
|
||||
ffi: ^2.1.4
|
||||
webdav_client: ^1.2.2
|
||||
dio: ^5.8.0+1
|
||||
@@ -50,9 +50,9 @@ dependencies:
|
||||
connectivity_plus: ^6.1.0
|
||||
screen_retriever: ^0.2.0
|
||||
defer_pointer: ^0.0.2
|
||||
flutter_riverpod: ^3.0.0-dev.17
|
||||
riverpod_annotation: ^3.0.0-dev.17
|
||||
riverpod: ^3.0.0-dev.17
|
||||
flutter_riverpod: ^3.0.0
|
||||
riverpod_annotation: ^3.0.0
|
||||
riverpod: ^3.0.0
|
||||
material_color_utilities: ^0.11.1
|
||||
flutter_js:
|
||||
git:
|
||||
@@ -69,8 +69,8 @@ dev_dependencies:
|
||||
build_runner: ^2.7.0
|
||||
args: ^2.4.2
|
||||
freezed: ^3.2.0
|
||||
riverpod_generator: ^3.0.0-dev.17
|
||||
riverpod_lint: ^3.0.0-dev.17
|
||||
riverpod_generator: ^3.0.0
|
||||
riverpod_lint: ^3.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
Reference in New Issue
Block a user