Compare commits
1 Commits
v0.8.88-pr
...
v0.8.88-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92bf322668 |
3
.github/workflows/build.yaml
vendored
3
.github/workflows/build.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- platform: android
|
||||
os: ubuntu-latest
|
||||
- platform: windows
|
||||
os: Windows-2022
|
||||
os: windows-latest
|
||||
arch: amd64
|
||||
- platform: linux
|
||||
os: ubuntu-22.04
|
||||
@@ -52,7 +52,6 @@ jobs:
|
||||
if: startsWith(matrix.platform,'android')
|
||||
run: |
|
||||
echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/app/keystore.jks
|
||||
echo "${{ secrets.SERVICE_JSON }}" | base64 --decode > android/app/google-services.json
|
||||
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties
|
||||
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties
|
||||
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties
|
||||
|
||||
@@ -5,8 +5,6 @@ plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
id("com.google.gms.google-services")
|
||||
id("com.google.firebase.crashlytics")
|
||||
}
|
||||
|
||||
val localPropertiesFile = rootProject.file("local.properties")
|
||||
@@ -20,9 +18,10 @@ val mStoreFile: File = file("keystore.jks")
|
||||
val mStorePassword: String? = localProperties.getProperty("storePassword")
|
||||
val mKeyAlias: String? = localProperties.getProperty("keyAlias")
|
||||
val mKeyPassword: String? = localProperties.getProperty("keyPassword")
|
||||
val isRelease =
|
||||
mStoreFile.exists() && mStorePassword != null && mKeyAlias != null && mKeyPassword != null
|
||||
|
||||
val isRelease = mStoreFile.exists()
|
||||
&& mStorePassword != null
|
||||
&& mKeyAlias != null
|
||||
&& mKeyPassword != null
|
||||
|
||||
android {
|
||||
namespace = "com.follow.clash"
|
||||
@@ -77,7 +76,8 @@ android {
|
||||
}
|
||||
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,6 @@ flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation(project(":service"))
|
||||
implementation(project(":common"))
|
||||
@@ -102,7 +101,4 @@ dependencies {
|
||||
implementation(libs.smali.dexlib2) {
|
||||
exclude(group = "com.google.guava", module = "guava")
|
||||
}
|
||||
implementation(platform(libs.firebase.bom))
|
||||
implementation(libs.firebase.crashlytics.ndk)
|
||||
implementation(libs.firebase.analytics)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "000000000000",
|
||||
"project_id": "dev"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
|
||||
"android_client_info": {
|
||||
"package_name": "com.follow.clash"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "0"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
|
||||
"android_client_info": {
|
||||
"package_name": "com.follow.clash.debug"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "0"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
<service
|
||||
android:name=".TileService"
|
||||
android:label="FlClash Debug"
|
||||
tools:replace="android:label" />
|
||||
tools:replace="android:label"
|
||||
tools:targetApi="24" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -103,8 +103,9 @@
|
||||
android:exported="true"
|
||||
android:permission="${applicationId}.permission.RECEIVE_BROADCASTS">
|
||||
<intent-filter>
|
||||
<action android:name="${applicationId}.intent.action.SERVICE_CREATED" />
|
||||
<action android:name="${applicationId}.intent.action.SERVICE_DESTROYED" />
|
||||
<action android:name="${applicationId}.intent.action.START" />
|
||||
<action android:name="${applicationId}.intent.action.STOP" />
|
||||
<action android:name="${applicationId}.intent.action.TOGGLE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
||||
@@ -4,24 +4,31 @@ 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() {
|
||||
class BroadcastReceiver : BroadcastReceiver(),
|
||||
CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
BroadcastAction.SERVICE_CREATED.action -> {
|
||||
GlobalState.log("Receiver service created")
|
||||
GlobalState.launch {
|
||||
BroadcastAction.START.action -> {
|
||||
launch {
|
||||
State.handleStartServiceAction()
|
||||
}
|
||||
}
|
||||
|
||||
BroadcastAction.SERVICE_DESTROYED.action -> {
|
||||
GlobalState.log("Receiver service destroyed")
|
||||
BroadcastAction.STOP.action -> {
|
||||
State.handleStopServiceAction()
|
||||
}
|
||||
|
||||
BroadcastAction.TOGGLE.action -> {
|
||||
launch {
|
||||
State.handleToggleAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,9 @@ suspend fun PackageManager.getPackageIconPath(packageName: String): String =
|
||||
if (iconFile.exists() && !isExpired(iconFile)) {
|
||||
return@withContext iconFile.absolutePath
|
||||
}
|
||||
iconDir.listFiles { f -> f.name.startsWith("${packageName}_") }?.forEach(File::delete)
|
||||
|
||||
iconDir.listFiles()?.forEach { file ->
|
||||
if (file.name.startsWith(packageName + "_")) file.delete()
|
||||
}
|
||||
val icon = getApplicationIcon(packageName)
|
||||
saveDrawableToFile(icon, iconFile)
|
||||
iconFile.absolutePath
|
||||
@@ -46,10 +47,8 @@ suspend fun PackageManager.getPackageIconPath(packageName: String): String =
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveDrawableToFile(drawable: Drawable, file: File) {
|
||||
val bitmap = withContext(Dispatchers.Default) {
|
||||
drawable.toBitmap(width = 128, height = 128)
|
||||
}
|
||||
private fun saveDrawableToFile(drawable: Drawable, file: File) {
|
||||
val bitmap = drawable.toBitmap()
|
||||
try {
|
||||
val format = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||
@@ -98,6 +97,7 @@ 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,7 +2,6 @@ package com.follow.clash
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.plugins.AppPlugin
|
||||
import com.follow.clash.plugins.ServicePlugin
|
||||
import com.follow.clash.plugins.TilePlugin
|
||||
@@ -32,9 +31,6 @@ class MainActivity : FlutterActivity(),
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
GlobalState.launch {
|
||||
Service.setEventListener(null)
|
||||
}
|
||||
State.flutterEngine = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
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.IEventInterface
|
||||
import com.follow.clash.service.IMessageInterface
|
||||
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 {
|
||||
@@ -39,46 +34,15 @@ object Service {
|
||||
delegate.unbind()
|
||||
}
|
||||
|
||||
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) {
|
||||
res.add(result ?: byteArrayOf())
|
||||
if (isSuccess) {
|
||||
cb(res.formatString())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setEventListener(
|
||||
cb: ((result: String?) -> Unit)?
|
||||
suspend fun invokeAction(
|
||||
data: String, cb: (result: ByteArray?, isSuccess: Boolean) -> 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
|
||||
it.invokeAction(data, object : ICallbackInterface.Stub() {
|
||||
override fun onResult(result: ByteArray?, isSuccess: Boolean) {
|
||||
cb(result, isSuccess)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,54 +54,23 @@ object Service {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setCrashlytics(
|
||||
enable: Boolean
|
||||
suspend fun setMessageCallback(
|
||||
cb: (result: String?) -> Unit
|
||||
): Result<Unit> {
|
||||
return delegate.useService {
|
||||
it.setCrashlytics(enable)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
it.setMessageCallback(object : IMessageInterface.Stub() {
|
||||
override fun onResult(result: String?) {
|
||||
cb(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
block(callback)
|
||||
} catch (e: Exception) {
|
||||
if (continuation.isActive) {
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun startService(options: VpnOptions, runTime: Long): Long {
|
||||
return delegate.useService {
|
||||
awaitIResultInterface { callback ->
|
||||
it.startService(options, runTime, callback)
|
||||
}
|
||||
}.getOrNull() ?: 0L
|
||||
suspend fun startService(options: VpnOptions, inApp: Boolean) {
|
||||
delegate.useService { it.startService(options, inApp) }
|
||||
}
|
||||
|
||||
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
|
||||
suspend fun stopService() {
|
||||
delegate.useService { it.stopService() }
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ object State {
|
||||
var runTime: Long = 0
|
||||
|
||||
val runStateFlow: MutableStateFlow<RunState> = MutableStateFlow(RunState.STOP)
|
||||
|
||||
var flutterEngine: FlutterEngine? = null
|
||||
var serviceFlutterEngine: FlutterEngine? = null
|
||||
|
||||
@@ -52,18 +51,6 @@ object State {
|
||||
action?.invoke()
|
||||
}
|
||||
|
||||
suspend fun handleSyncState() {
|
||||
runLock.withLock {
|
||||
Service.bind()
|
||||
runTime = Service.getRunTime()
|
||||
val runState = when (runTime == 0L) {
|
||||
true -> RunState.STOP
|
||||
false -> RunState.START
|
||||
}
|
||||
runStateFlow.tryEmit(runState)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun handleStartServiceAction() {
|
||||
tilePlugin?.handleStart()
|
||||
if (flutterEngine != null) {
|
||||
@@ -103,9 +90,6 @@ object State {
|
||||
|
||||
suspend fun startServiceWithEngine() {
|
||||
runLock.withLock {
|
||||
if (serviceFlutterEngine != null || runStateFlow.value == RunState.PENDING || runStateFlow.value == RunState.START) {
|
||||
return
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
serviceFlutterEngine = FlutterEngine(GlobalState.application)
|
||||
serviceFlutterEngine?.plugins?.add(ServicePlugin())
|
||||
@@ -116,6 +100,7 @@ object State {
|
||||
)
|
||||
serviceFlutterEngine?.dartExecutor?.executeDartEntrypoint(dartEntrypoint)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +119,8 @@ object State {
|
||||
return@launch
|
||||
}
|
||||
appPlugin?.prepare(options.enable) {
|
||||
runTime = Service.startService(options, runTime)
|
||||
runTime = System.currentTimeMillis()
|
||||
Service.startService(options, true)
|
||||
runStateFlow.tryEmit(RunState.START)
|
||||
}
|
||||
}
|
||||
@@ -149,13 +135,14 @@ object State {
|
||||
return@launch
|
||||
}
|
||||
runStateFlow.tryEmit(RunState.PENDING)
|
||||
runTime = Service.stopService()
|
||||
Service.stopService()
|
||||
runStateFlow.tryEmit(RunState.STOP)
|
||||
runTime = 0
|
||||
}
|
||||
destroyServiceEngine()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ class TileService : TileService() {
|
||||
scope?.cancel()
|
||||
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
scope?.launch {
|
||||
State.handleSyncState()
|
||||
State.runStateFlow.collect {
|
||||
updateTile(it)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ package com.follow.clash.models
|
||||
|
||||
|
||||
data class AppState(
|
||||
val crashlytics: Boolean = true,
|
||||
val currentProfileName: String = "FlClash",
|
||||
val stopText: String = "Stop",
|
||||
val onlyStatisticsProxy: Boolean = false,
|
||||
val currentProfileName: String,
|
||||
val stopText: String,
|
||||
val onlyStatisticsProxy: Boolean,
|
||||
)
|
||||
|
||||
@@ -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.GlobalState
|
||||
import com.follow.clash.common.formatString
|
||||
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(call, result)
|
||||
handleInit(result)
|
||||
}
|
||||
|
||||
"shutdown" -> {
|
||||
@@ -73,8 +73,12 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
private fun handleInvokeAction(call: MethodCall, result: MethodChannel.Result) {
|
||||
launch {
|
||||
val data = call.arguments<String>()!!
|
||||
Service.invokeAction(data) {
|
||||
result.success(it)
|
||||
val res = mutableListOf<ByteArray>()
|
||||
Service.invokeAction(data) { byteArray, isSuccess ->
|
||||
res.add(byteArray ?: byteArrayOf())
|
||||
if (isSuccess) {
|
||||
result.success(res.formatString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,32 +119,28 @@ 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
|
||||
)
|
||||
)
|
||||
Service.setCrashlytics(params.crashlytics)
|
||||
result.success("")
|
||||
).onSuccess {
|
||||
result.success("")
|
||||
}.onFailure {
|
||||
result.success(it.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleInit(call: MethodCall, result: MethodChannel.Result) {
|
||||
fun handleInit(result: MethodChannel.Result) {
|
||||
Service.bind()
|
||||
launch {
|
||||
val needSetEventListener = call.arguments<Boolean>() ?: false
|
||||
when (needSetEventListener) {
|
||||
true -> Service.setEventListener {
|
||||
handleSendEvent(it)
|
||||
}
|
||||
|
||||
false -> Service.setEventListener(null)
|
||||
Service.setMessageCallback {
|
||||
handleSendEvent(it)
|
||||
}.onSuccess {
|
||||
result.success("")
|
||||
}.onFailure {
|
||||
@@ -152,9 +152,6 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
}
|
||||
|
||||
private fun handleGetRunTime(result: MethodChannel.Result) {
|
||||
launch {
|
||||
State.handleSyncState()
|
||||
result.success(State.runTime)
|
||||
}
|
||||
return result.success(State.runTime)
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,4 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.gson)
|
||||
implementation(platform(libs.firebase.bom))
|
||||
implementation(libs.firebase.crashlytics.ndk)
|
||||
implementation(libs.firebase.analytics)
|
||||
}
|
||||
@@ -10,8 +10,9 @@ enum class QuickAction {
|
||||
}
|
||||
|
||||
enum class BroadcastAction {
|
||||
SERVICE_CREATED,
|
||||
SERVICE_DESTROYED,
|
||||
START,
|
||||
STOP,
|
||||
TOGGLE,
|
||||
}
|
||||
|
||||
enum class AccessControlMode {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.follow.clash.common
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
@@ -21,7 +20,6 @@ import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import androidx.core.content.getSystemService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -69,16 +67,6 @@ val QuickAction.quickIntent: Intent
|
||||
val BroadcastAction.action: String
|
||||
get() = "${GlobalState.application.packageName}.intent.action.${this.name}"
|
||||
|
||||
val Context.processName: String?
|
||||
get() {
|
||||
val pid = android.os.Process.myPid()
|
||||
val activityManager = getSystemService<ActivityManager>()
|
||||
activityManager?.runningAppProcesses?.find { it.pid == pid }?.let {
|
||||
return it.processName
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
val BroadcastAction.quickIntent: Intent
|
||||
get() = Components.BROADCAST_RECEIVER.intent.apply {
|
||||
action = this@quickIntent.action
|
||||
@@ -149,7 +137,7 @@ fun Context.receiveBroadcastFlow(
|
||||
inline fun <reified T : IBinder> Context.bindServiceFlow(
|
||||
intent: Intent,
|
||||
flags: Int = Context.BIND_AUTO_CREATE,
|
||||
maxRetries: Int = 10,
|
||||
maxRetries: Int = 5,
|
||||
retryDelayMillis: Long = 200L
|
||||
): Flow<Pair<IBinder?, String>> = callbackFlow {
|
||||
val connection = object : ServiceConnection {
|
||||
@@ -186,7 +174,6 @@ inline fun <reified T : IBinder> Context.bindServiceFlow(
|
||||
awaitClose {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
unbindService(connection)
|
||||
trySend(Pair(null, ""))
|
||||
}
|
||||
}
|
||||
}.retryWhen { cause, attempt ->
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
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
|
||||
|
||||
@@ -15,16 +12,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 var _application: Application? = null
|
||||
private lateinit var _application: Application
|
||||
|
||||
val application: Application
|
||||
get() = _application!!
|
||||
get() = _application
|
||||
|
||||
|
||||
fun log(text: String) {
|
||||
@@ -34,14 +31,4 @@ 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,16 +44,13 @@ class ServiceDelegate<T>(
|
||||
job = null
|
||||
_serviceState.value = null
|
||||
job = launch {
|
||||
runCatching {
|
||||
GlobalState.application.bindServiceFlow<IBinder>(intent)
|
||||
.collect { handleBind(it) }
|
||||
}
|
||||
GlobalState.application.bindServiceFlow<IBinder>(intent).collect { handleBind(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <R> useService(
|
||||
timeoutMillis: Long = 5000, crossinline block: suspend (T) -> R
|
||||
timeoutMillis: Long = 5000, crossinline block: (T) -> R
|
||||
): Result<R> {
|
||||
return runCatching {
|
||||
withTimeout(timeoutMillis) {
|
||||
|
||||
@@ -41,13 +41,9 @@ Java_com_follow_clash_core_Core_invokeAction(JNIEnv *env, jobject thiz, jstring
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_setEventListener(JNIEnv *env, jobject thiz, jobject cb) {
|
||||
if (cb != nullptr) {
|
||||
const auto interface = new_global(cb);
|
||||
setEventListener(interface);
|
||||
} else {
|
||||
setEventListener(nullptr);
|
||||
}
|
||||
Java_com_follow_clash_core_Core_setMessageCallback(JNIEnv *env, jobject thiz, jobject cb) {
|
||||
const auto interface = new_global(cb);
|
||||
setMessageCallback(interface);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
@@ -173,7 +169,7 @@ Java_com_follow_clash_core_Core_updateDNS(JNIEnv *env, jobject thiz, jstring dns
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_follow_clash_core_Core_setEventListener(JNIEnv *env, jobject thiz, jobject cb) {
|
||||
Java_com_follow_clash_core_Core_setMessageCallback(JNIEnv *env, jobject thiz, jobject cb) {
|
||||
}
|
||||
|
||||
extern "C"
|
||||
|
||||
@@ -84,22 +84,18 @@ data object Core {
|
||||
)
|
||||
}
|
||||
|
||||
private external fun setEventListener(cb: InvokeInterface?)
|
||||
private external fun setMessageCallback(cb: InvokeInterface)
|
||||
|
||||
fun callSetEventListener(
|
||||
cb: ((result: String?) -> Unit)?
|
||||
fun setMessageCallback(
|
||||
cb: (result: String?) -> Unit
|
||||
) {
|
||||
when (cb != null) {
|
||||
true -> setEventListener(
|
||||
object : InvokeInterface {
|
||||
override fun onResult(result: String?) {
|
||||
cb(result)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
false -> setEventListener(null)
|
||||
}
|
||||
setMessageCallback(
|
||||
object : InvokeInterface {
|
||||
override fun onResult(result: String?) {
|
||||
cb(result)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
external fun stopTun()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
[versions]
|
||||
#agp = "8.10.1"
|
||||
firebaseBom = "34.2.0"
|
||||
minSdk = "23"
|
||||
targetSdk = "36"
|
||||
compileSdk = "36"
|
||||
@@ -11,18 +10,11 @@ coreSplashscreen = "1.0.1"
|
||||
gson = "2.13.1"
|
||||
kotlin = "2.2.10"
|
||||
smaliDexlib2 = "3.0.9"
|
||||
firebaseCrashlyticsKtx = "20.0.1"
|
||||
firebaseCommonKtx = "22.0.0"
|
||||
|
||||
[libraries]
|
||||
build-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||
androidx-core = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
|
||||
annotation-jvm = { module = "androidx.annotation:annotation-jvm", version.ref = "annotationJvm" }
|
||||
core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
|
||||
firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
|
||||
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
|
||||
firebase-crashlytics-ndk = { module = "com.google.firebase:firebase-crashlytics-ndk" }
|
||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||
smali-dexlib2 = { module = "com.android.tools.smali:smali-dexlib2", version.ref = "smaliDexlib2" }
|
||||
firebase-crashlytics-ktx = { group = "com.google.firebase", name = "firebase-crashlytics-ktx", version.ref = "firebaseCrashlyticsKtx" }
|
||||
firebase-common-ktx = { group = "com.google.firebase", name = "firebase-common-ktx", version.ref = "firebaseCommonKtx" }
|
||||
smali-dexlib2 = { module = "com.android.tools.smali:smali-dexlib2", version.ref = "smaliDexlib2" }
|
||||
@@ -25,7 +25,7 @@
|
||||
android:process=":remote">
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="proxy" />
|
||||
android:value="service" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface ICallbackInterface {
|
||||
oneway void onResult(in byte[] data,in boolean isSuccess);
|
||||
void onResult(in byte[] result, boolean isSuccess);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// IEventInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface IEventInterface {
|
||||
oneway void onEvent(in String id, in byte[] data,in boolean isSuccess);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// IMessageInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface IMessageInterface {
|
||||
void onResult(String result);
|
||||
}
|
||||
@@ -2,17 +2,14 @@
|
||||
package com.follow.clash.service;
|
||||
|
||||
import com.follow.clash.service.ICallbackInterface;
|
||||
import com.follow.clash.service.IEventInterface;
|
||||
import com.follow.clash.service.IResultInterface;
|
||||
import com.follow.clash.service.IMessageInterface;
|
||||
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 long runTime, in IResultInterface result);
|
||||
void stopService(in IResultInterface result);
|
||||
void setEventListener(in IEventInterface event);
|
||||
void setCrashlytics(in boolean enable);
|
||||
long getRunTime();
|
||||
void startService(in VpnOptions options,in boolean inApp);
|
||||
void stopService();
|
||||
void setMessageCallback(in IMessageInterface messageCallback);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// IResultInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
interface IResultInterface {
|
||||
oneway void onResult(in long runTime);
|
||||
}
|
||||
@@ -29,11 +29,6 @@ class CommonService : Service(), IBaseService,
|
||||
handleCreate()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
handleDestroy()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
Core.forceGC()
|
||||
super.onLowMemory()
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
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() {
|
||||
GlobalState.log("Service create")
|
||||
BroadcastAction.SERVICE_CREATED.sendBroadcast()
|
||||
}
|
||||
|
||||
fun handleDestroy() {
|
||||
GlobalState.log("Service destroy")
|
||||
BroadcastAction.SERVICE_DESTROYED.sendBroadcast()
|
||||
if (!State.inApp) {
|
||||
BroadcastAction.START.sendBroadcast()
|
||||
} else {
|
||||
State.inApp = false
|
||||
}
|
||||
}
|
||||
|
||||
fun start()
|
||||
|
||||
@@ -3,71 +3,56 @@ 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 fun handleStopService(result: IResultInterface) {
|
||||
private var delegate: ServiceDelegate<IBaseService>? = null
|
||||
private var intent: Intent? = null
|
||||
|
||||
private fun handleStopService() {
|
||||
launch {
|
||||
runLock.withLock {
|
||||
delegate?.useService { service ->
|
||||
service.stop()
|
||||
delegate?.unbind()
|
||||
}
|
||||
State.runTime = 0
|
||||
result.onResult(0)
|
||||
delegate?.useService { service ->
|
||||
service.stop()
|
||||
delegate?.unbind()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleServiceDisconnected(message: String) {
|
||||
GlobalState.log("Background service disconnected: $message")
|
||||
intent = null
|
||||
delegate = null
|
||||
}
|
||||
|
||||
private fun handleStartService(runTime: Long, result: IResultInterface) {
|
||||
private fun handleStartService() {
|
||||
launch {
|
||||
runLock.withLock {
|
||||
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")
|
||||
}
|
||||
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")
|
||||
}
|
||||
intent = nextIntent
|
||||
delegate?.bind()
|
||||
}
|
||||
delegate?.useService { service ->
|
||||
service.start()
|
||||
}
|
||||
State.runTime = when (runTime != 0L) {
|
||||
true -> runTime
|
||||
false -> System.currentTimeMillis()
|
||||
}
|
||||
result.onResult(State.runTime)
|
||||
intent = nextIntent
|
||||
delegate?.bind()
|
||||
}
|
||||
delegate?.useService { service ->
|
||||
service.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,12 +60,10 @@ class RemoteService : Service(),
|
||||
private val binder = object : IRemoteInterface.Stub() {
|
||||
override fun invokeAction(data: String, callback: ICallbackInterface) {
|
||||
Core.invokeAction(data) {
|
||||
runCatching {
|
||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||
val totalSize = chunks.size
|
||||
chunks.forEachIndexed { index, chunk ->
|
||||
callback.onResult(chunk, totalSize - 1 == index)
|
||||
}
|
||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||
val totalSize = chunks.size
|
||||
chunks.forEachIndexed { index, chunk ->
|
||||
callback.onResult(chunk, totalSize - 1 == index)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,51 +73,27 @@ class RemoteService : Service(),
|
||||
}
|
||||
|
||||
override fun startService(
|
||||
options: VpnOptions,
|
||||
runtime: Long,
|
||||
result: IResultInterface,
|
||||
options: VpnOptions, inApp: Boolean
|
||||
) {
|
||||
State.options = options
|
||||
handleStartService(runtime, result)
|
||||
State.inApp = inApp
|
||||
handleStartService()
|
||||
}
|
||||
|
||||
override fun stopService(result: IResultInterface) {
|
||||
handleStopService(result)
|
||||
override fun stopService() {
|
||||
handleStopService()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false -> Core.callSetEventListener(null)
|
||||
}
|
||||
override fun setMessageCallback(messageCallback: IMessageInterface) {
|
||||
setMessageCallback(messageCallback::onResult)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setCrashlytics(enable: Boolean) {
|
||||
GlobalState.setCrashlytics(enable)
|
||||
}
|
||||
|
||||
override fun getRunTime(): Long {
|
||||
return State.runTime
|
||||
}
|
||||
private fun setMessageCallback(cb: (result: String?) -> Unit) {
|
||||
Core.setMessageCallback(cb)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
GlobalState.log("Remote service destroy")
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,13 @@
|
||||
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,7 +11,8 @@ import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import androidx.core.content.getSystemService
|
||||
import com.follow.clash.common.AccessControlMode
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.common.BroadcastAction
|
||||
import com.follow.clash.common.sendBroadcast
|
||||
import com.follow.clash.core.Core
|
||||
import com.follow.clash.service.models.VpnOptions
|
||||
import com.follow.clash.service.models.getIpv4RouteAddress
|
||||
@@ -43,11 +44,6 @@ class VpnService : SystemVpnService(), IBaseService,
|
||||
handleCreate()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
handleDestroy()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private val connectivity by lazy {
|
||||
getSystemService<ConnectivityManager>()
|
||||
}
|
||||
@@ -111,13 +107,11 @@ class VpnService : SystemVpnService(), IBaseService,
|
||||
try {
|
||||
val isSuccess = super.onTransact(code, data, reply, flags)
|
||||
if (!isSuccess) {
|
||||
GlobalState.log("VpnService disconnected")
|
||||
handleDestroy()
|
||||
BroadcastAction.STOP.sendBroadcast()
|
||||
}
|
||||
return isSuccess
|
||||
} catch (e: RemoteException) {
|
||||
GlobalState.log("VpnService onTransact $e")
|
||||
return false
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,7 @@ val Traffic.speedText: String
|
||||
get() = "${up.formatBytes}/s↑ ${down.formatBytes}/s↓"
|
||||
|
||||
fun Core.getSpeedTrafficText(onlyStatisticsProxy: Boolean): String {
|
||||
try {
|
||||
val res = getTraffic(onlyStatisticsProxy)
|
||||
val traffic = Gson().fromJson(res, Traffic::class.java)
|
||||
return traffic.speedText
|
||||
} catch (_: Exception) {
|
||||
return ""
|
||||
}
|
||||
val res = getTraffic(onlyStatisticsProxy)
|
||||
val traffic = Gson().fromJson(res, Traffic::class.java)
|
||||
return traffic.speedText
|
||||
}
|
||||
@@ -90,13 +90,12 @@ 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(true)
|
||||
setShowWhen(false)
|
||||
setOnlyAlertOnce(true)
|
||||
}
|
||||
}
|
||||
@@ -106,6 +105,7 @@ 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
|
||||
|
||||
@@ -18,10 +18,8 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.12.2" apply false
|
||||
id("com.android.application") version "8.12.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.2.10" apply false
|
||||
id("com.google.gms.google-services") version ("4.3.15") apply false
|
||||
id("com.google.firebase.crashlytics") version ("2.8.1") apply false
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -409,7 +409,7 @@
|
||||
"autoSetSystemDns": "Auto set system DNS",
|
||||
"details": "{label} details",
|
||||
"creationTime": "Creation time",
|
||||
"process": "Process",
|
||||
"progress": "Progress",
|
||||
"host": "Host",
|
||||
"destination": "Destination",
|
||||
"destinationGeoIP": "Destination GeoIP",
|
||||
@@ -428,9 +428,5 @@
|
||||
"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",
|
||||
"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"
|
||||
"coreStatus": "Core status"
|
||||
}
|
||||
@@ -410,7 +410,7 @@
|
||||
"autoSetSystemDns": "オートセットシステムDNS",
|
||||
"details": "{label}詳細",
|
||||
"creationTime": "作成時間",
|
||||
"process": "プロセス",
|
||||
"progress": "進捗",
|
||||
"host": "ホスト",
|
||||
"destination": "宛先",
|
||||
"destinationGeoIP": "宛先地理情報",
|
||||
@@ -429,9 +429,5 @@
|
||||
"restartCoreTip": "コアを再起動してもよろしいですか?",
|
||||
"forceRestartCoreTip": "コアを強制再起動してもよろしいですか?",
|
||||
"dnsHijacking": "DNSハイジャッキング",
|
||||
"coreStatus": "コアステータス",
|
||||
"dataCollectionTip": "データ収集説明",
|
||||
"dataCollectionContent": "本アプリはFirebase Crashlyticsを使用してクラッシュ情報を収集し、アプリの安定性を向上させます。\n収集されるデータにはデバイス情報とクラッシュ詳細が含まれますが、個人の機密データは含まれません。\n設定でこの機能を無効にすることができます。",
|
||||
"crashlytics": "クラッシュ分析",
|
||||
"crashlyticsTip": "有効にすると、アプリがクラッシュした際に機密情報を含まないクラッシュログを自動的にアップロードします"
|
||||
"coreStatus": "コアステータス"
|
||||
}
|
||||
@@ -410,7 +410,7 @@
|
||||
"autoSetSystemDns": "Автоматическая настройка системного DNS",
|
||||
"details": "Детали {}",
|
||||
"creationTime": "Время создания",
|
||||
"process": "процесс",
|
||||
"progress": "Прогресс",
|
||||
"host": "Хост",
|
||||
"destination": "Назначение",
|
||||
"destinationGeoIP": "Геолокация назначения",
|
||||
@@ -429,9 +429,5 @@
|
||||
"restartCoreTip": "Вы уверены, что хотите перезапустить ядро?",
|
||||
"forceRestartCoreTip": "Вы уверены, что хотите принудительно перезапустить ядро?",
|
||||
"dnsHijacking": "DNS-перехват",
|
||||
"coreStatus": "Основной статус",
|
||||
"dataCollectionTip": "Уведомление о сборе данных",
|
||||
"dataCollectionContent": "Это приложение использует Firebase Crashlytics для сбора информации о сбоях nhằm улучшения стабильности приложения.\nСобираемые данные включают информацию об устройстве и подробности о сбоях, но не содержат персональных конфиденциальных данных.\nВы можете отключить эту функцию в настройках.",
|
||||
"crashlytics": "Анализ сбоев",
|
||||
"crashlyticsTip": "При включении автоматически загружает журналы сбоев без конфиденциальной информации, когда приложение выходит из строя"
|
||||
"coreStatus": "Основной статус"
|
||||
}
|
||||
@@ -410,7 +410,7 @@
|
||||
"autoSetSystemDns": "自动设置系统DNS",
|
||||
"details": "{label}详情",
|
||||
"creationTime": "创建时间",
|
||||
"process": "进程",
|
||||
"progress": "进度",
|
||||
"host": "主机",
|
||||
"destination": "目标地址",
|
||||
"destinationGeoIP": "目标地理定位",
|
||||
@@ -429,9 +429,5 @@
|
||||
"restartCoreTip": "您确定要重启核心吗?",
|
||||
"forceRestartCoreTip": "您确定要强制重启核心吗?",
|
||||
"dnsHijacking": "DNS劫持",
|
||||
"coreStatus": "核心状态",
|
||||
"dataCollectionTip": "数据收集说明",
|
||||
"dataCollectionContent": "本应用使用 Firebase Crashlytics 收集崩溃信息以改进应用稳定性。\n收集的数据包括设备信息和崩溃详情,不包含个人敏感数据。\n您可以在设置中关闭此功能。",
|
||||
"crashlytics": "崩溃分析",
|
||||
"crashlyticsTip": "开启后,应用崩溃时自动上传不包含敏感信息的崩溃日志"
|
||||
"coreStatus": "核心状态"
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
45523
assets/data/GEOSITE.dat
45523
assets/data/GEOSITE.dat
File diff suppressed because one or more lines are too long
Binary file not shown.
35960
assets/data/GeoSite.dat
Normal file
35960
assets/data/GeoSite.dat
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/data/geoip.metadb
Normal file
BIN
assets/data/geoip.metadb
Normal file
Binary file not shown.
Submodule core/Clash.Meta updated: 573489787b...ef78ae3278
@@ -1,6 +1,6 @@
|
||||
module core
|
||||
|
||||
go 1.25
|
||||
go 1.20
|
||||
|
||||
replace github.com/metacubex/mihomo => ./Clash.Meta
|
||||
|
||||
|
||||
11
core/hub.go
11
core/hub.go
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
"github.com/metacubex/mihomo/common/observable"
|
||||
@@ -144,7 +145,7 @@ func handleGetTraffic(onlyStatisticsProxy bool) string {
|
||||
}
|
||||
data, err := json.Marshal(traffic)
|
||||
if err != nil {
|
||||
log.Errorln("Error: %s", err)
|
||||
fmt.Println("Error:", err)
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
@@ -158,7 +159,7 @@ func handleGetTotalTraffic(onlyStatisticsProxy bool) string {
|
||||
}
|
||||
data, err := json.Marshal(traffic)
|
||||
if err != nil {
|
||||
log.Errorln("Error: %s", err)
|
||||
fmt.Println("Error:", err)
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
@@ -228,7 +229,7 @@ func handleGetConnections() string {
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
data, err := json.Marshal(snapshot)
|
||||
if err != nil {
|
||||
log.Errorln("Error: %s", err)
|
||||
fmt.Println("Error:", err)
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
@@ -323,13 +324,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 eventListener unsafe.Pointer
|
||||
var messageCallback 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 {
|
||||
releaseObject(result.callback)
|
||||
defer releaseObject(result.callback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,12 +202,12 @@ func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar
|
||||
return true
|
||||
}
|
||||
|
||||
//export setEventListener
|
||||
func setEventListener(listener unsafe.Pointer) {
|
||||
if eventListener != nil || listener == nil {
|
||||
releaseObject(eventListener)
|
||||
//export setMessageCallback
|
||||
func setMessageCallback(callback unsafe.Pointer) {
|
||||
if messageCallback != nil {
|
||||
releaseObject(messageCallback)
|
||||
}
|
||||
eventListener = listener
|
||||
messageCallback = callback
|
||||
}
|
||||
|
||||
//export getTotalTraffic
|
||||
@@ -225,12 +225,12 @@ func getTraffic(onlyStatisticsProxy bool) *C.char {
|
||||
}
|
||||
|
||||
func sendMessage(message Message) {
|
||||
if eventListener == nil {
|
||||
if messageCallback == nil {
|
||||
return
|
||||
}
|
||||
result := ActionResult{
|
||||
Method: messageMethod,
|
||||
callback: eventListener,
|
||||
callback: messageCallback,
|
||||
Data: message,
|
||||
}
|
||||
result.send()
|
||||
|
||||
@@ -125,8 +125,10 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
builder: (_, child) {
|
||||
return AppEnvManager(
|
||||
child: _buildApp(
|
||||
child: _buildPlatformState(
|
||||
child: _buildState(child: _buildPlatformApp(child: child!)),
|
||||
child: AppSidebarContainer(
|
||||
child: _buildPlatformState(
|
||||
child: _buildState(child: _buildPlatformApp(child: child!)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
13
lib/common/android.dart
Normal file
13
lib/common/android.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
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 = 'ImageCaches';
|
||||
static const key = 'LocalImageCacheData';
|
||||
|
||||
static final LocalImageCacheManager _instance = LocalImageCacheManager._();
|
||||
|
||||
@@ -14,7 +14,14 @@ class LocalImageCacheManager extends CacheManager {
|
||||
}
|
||||
|
||||
LocalImageCacheManager._()
|
||||
: super(Config(key, fileService: _LocalImageCacheFileService()));
|
||||
: super(
|
||||
Config(
|
||||
key,
|
||||
stalePeriod: Duration(days: 30),
|
||||
maxNrOfCacheObjects: 1000,
|
||||
fileService: _LocalImageCacheFileService(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _LocalImageCacheFileService extends FileService {
|
||||
@@ -40,31 +47,28 @@ 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);
|
||||
_response.data?.stream.transform(uint8ListToListIntConverter) ??
|
||||
Stream.empty();
|
||||
|
||||
@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 = _header(HttpHeaders.cacheControlHeader);
|
||||
final controlHeader = _response.headers.value(
|
||||
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;
|
||||
@@ -74,7 +78,6 @@ class _LocalImageResponse implements FileServiceResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ageDuration > const Duration(days: 7)) {
|
||||
return _receivedTime.add(ageDuration);
|
||||
}
|
||||
@@ -82,12 +85,14 @@ class _LocalImageResponse implements FileServiceResponse {
|
||||
}
|
||||
|
||||
@override
|
||||
String? get eTag => _header(HttpHeaders.etagHeader);
|
||||
String? get eTag => _response.headers.value(HttpHeaders.etagHeader);
|
||||
|
||||
@override
|
||||
String get fileExtension {
|
||||
var fileExtension = '';
|
||||
final contentTypeHeader = _header(HttpHeaders.contentTypeHeader);
|
||||
final contentTypeHeader = _response.headers.value(
|
||||
HttpHeaders.contentTypeHeader,
|
||||
);
|
||||
if (contentTypeHeader != null) {
|
||||
final contentType = ContentType.parse(contentTypeHeader);
|
||||
fileExtension = contentType.fileExtension;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export 'android.dart';
|
||||
export 'app_localizations.dart';
|
||||
export 'cache.dart';
|
||||
export 'color.dart';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
@@ -32,10 +30,10 @@ const animateDuration = Duration(milliseconds: 100);
|
||||
const midDuration = Duration(milliseconds: 200);
|
||||
const commonDuration = Duration(milliseconds: 300);
|
||||
const defaultUpdateDuration = Duration(days: 1);
|
||||
const MMDB = 'GEOIP.metadb';
|
||||
const ASN = 'ASN.mmdb';
|
||||
const GEOIP = 'GEOIP.dat';
|
||||
const GEOSITE = 'GEOSITE.dat';
|
||||
const mmdbFileName = 'geoip.metadb';
|
||||
const asnFileName = 'ASN.mmdb';
|
||||
const geoIpFileName = 'GeoIP.dat';
|
||||
const geoSiteFileName = 'GeoSite.dat';
|
||||
final double kHeaderHeight = system.isDesktop
|
||||
? !system.isMacOS
|
||||
? 40
|
||||
|
||||
@@ -7,17 +7,28 @@ extension BuildContextExtension on BuildContext {
|
||||
return findAncestorStateOfType<CommonScaffoldState>();
|
||||
}
|
||||
|
||||
void showNotifier(String text) {
|
||||
Future<void>? showNotifier(String text) {
|
||||
return findAncestorStateOfType<MessageManagerState>()?.message(text);
|
||||
}
|
||||
|
||||
void showSnackBar(String message, {SnackBarAction? action}) {
|
||||
void showSnackBar(
|
||||
String message, {
|
||||
SnackBarAction? action,
|
||||
}) {
|
||||
final width = viewWidth;
|
||||
EdgeInsets margin;
|
||||
if (width < 600) {
|
||||
margin = const EdgeInsets.only(bottom: 16, right: 16, left: 16);
|
||||
margin = const EdgeInsets.only(
|
||||
bottom: 16,
|
||||
right: 16,
|
||||
left: 16,
|
||||
);
|
||||
} else {
|
||||
margin = EdgeInsets.only(bottom: 16, left: 16, right: width - 316);
|
||||
margin = EdgeInsets.only(
|
||||
bottom: 16,
|
||||
left: 16,
|
||||
right: width - 316,
|
||||
);
|
||||
}
|
||||
ScaffoldMessenger.of(this).showSnackBar(
|
||||
SnackBar(
|
||||
@@ -65,11 +76,8 @@ extension BuildContextExtension on BuildContext {
|
||||
class BackHandleInherited extends InheritedWidget {
|
||||
final Function handleBack;
|
||||
|
||||
const BackHandleInherited({
|
||||
super.key,
|
||||
required this.handleBack,
|
||||
required super.child,
|
||||
});
|
||||
const BackHandleInherited(
|
||||
{super.key, required this.handleBack, required super.child});
|
||||
|
||||
static BackHandleInherited? of(BuildContext context) =>
|
||||
context.dependOnInheritedWidgetOfExactType<BackHandleInherited>();
|
||||
|
||||
@@ -36,25 +36,16 @@ class Throttler {
|
||||
Function func, {
|
||||
List<dynamic>? args,
|
||||
Duration duration = const Duration(milliseconds: 600),
|
||||
bool fire = false,
|
||||
}) {
|
||||
final timer = _operations[tag];
|
||||
if (timer != null) {
|
||||
return true;
|
||||
}
|
||||
if (fire) {
|
||||
_operations[tag] = Timer(duration, () {
|
||||
_operations[tag]?.cancel();
|
||||
_operations.remove(tag);
|
||||
Function.apply(func, args);
|
||||
_operations[tag] = Timer(duration, () {
|
||||
_operations[tag]?.cancel();
|
||||
_operations.remove(tag);
|
||||
});
|
||||
} else {
|
||||
_operations[tag] = Timer(duration, () {
|
||||
Function.apply(func, args);
|
||||
_operations[tag]?.cancel();
|
||||
_operations.remove(tag);
|
||||
});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -134,15 +134,11 @@ class CommonPageTransition extends StatefulWidget {
|
||||
bool allowSnapshotting,
|
||||
Widget? child,
|
||||
) {
|
||||
final CurvedAnimation animation = CurvedAnimation(
|
||||
final Animation<Offset> delegatedPositionAnimation = CurvedAnimation(
|
||||
parent: secondaryAnimation,
|
||||
curve: Curves.linearToEaseOut,
|
||||
reverseCurve: Curves.easeInToLinear,
|
||||
);
|
||||
final Animation<Offset> delegatedPositionAnimation = animation.drive(
|
||||
_kMiddleLeftTween,
|
||||
);
|
||||
animation.dispose();
|
||||
).drive(_kMiddleLeftTween);
|
||||
|
||||
assert(debugCheckHasDirectionality(context));
|
||||
final TextDirection textDirection = Directionality.of(context);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -13,14 +12,14 @@ class CommonPrint {
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
void log(String? text, {LogLevel logLevel = LogLevel.info}) {
|
||||
void log(String? text) {
|
||||
final payload = '[APP] $text';
|
||||
debugPrint(payload);
|
||||
if (!globalState.isInit) {
|
||||
return;
|
||||
}
|
||||
globalState.appController.addLog(
|
||||
Log.app(payload).copyWith(logLevel: logLevel),
|
||||
Log.app(payload),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,13 +73,13 @@ class Request {
|
||||
}
|
||||
|
||||
final Map<String, IpInfo Function(Map<String, dynamic>)> _ipInfoSources = {
|
||||
'https://ipwho.is': IpInfo.fromIpWhoIsJson,
|
||||
'https://api.myip.com': IpInfo.fromMyIpJson,
|
||||
'https://ipapi.co/json': IpInfo.fromIpApiCoJson,
|
||||
'https://ident.me/json': IpInfo.fromIdentMeJson,
|
||||
'http://ip-api.com/json': IpInfo.fromIpAPIJson,
|
||||
'https://api.ip.sb/geoip': IpInfo.fromIpSbJson,
|
||||
'https://ipinfo.io/json': IpInfo.fromIpInfoIoJson,
|
||||
'https://ipwho.is/': IpInfo.fromIpWhoIsJson,
|
||||
'https://api.myip.com/': IpInfo.fromMyIpJson,
|
||||
'https://ipapi.co/json/': IpInfo.fromIpApiCoJson,
|
||||
'https://ident.me/json/': IpInfo.fromIdentMeJson,
|
||||
'http://ip-api.com/json/': IpInfo.fromIpAPIJson,
|
||||
'https://api.ip.sb/geoip/': IpInfo.fromIpSbJson,
|
||||
'https://ipinfo.io/json/': IpInfo.fromIpInfoIoJson,
|
||||
};
|
||||
|
||||
Future<Result<IpInfo?>> checkIp({CancelToken? cancelToken}) async {
|
||||
@@ -92,13 +92,11 @@ class Request {
|
||||
}
|
||||
}
|
||||
|
||||
final future = dio
|
||||
.get<Map<String, dynamic>>(
|
||||
source.key,
|
||||
cancelToken: cancelToken,
|
||||
options: Options(responseType: ResponseType.json),
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
final future = dio.get<Map<String, dynamic>>(
|
||||
source.key,
|
||||
cancelToken: cancelToken,
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
future
|
||||
.then((res) {
|
||||
if (res.statusCode == HttpStatus.ok && res.data != null) {
|
||||
|
||||
@@ -11,15 +11,16 @@ extension StringExtension on String {
|
||||
}
|
||||
|
||||
dynamic get splitByMultipleSeparators {
|
||||
final parts = split(
|
||||
RegExp(r'[, ;]+'),
|
||||
).where((part) => part.isNotEmpty).toList();
|
||||
final parts =
|
||||
split(RegExp(r'[, ;]+')).where((part) => part.isNotEmpty).toList();
|
||||
|
||||
return parts.length > 1 ? parts : this;
|
||||
}
|
||||
|
||||
int compareToLower(String other) {
|
||||
return toLowerCase().compareTo(other.toLowerCase());
|
||||
return toLowerCase().compareTo(
|
||||
other.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
List<int> get encodeUtf16LeWithBom {
|
||||
@@ -65,9 +66,9 @@ extension StringExtension on String {
|
||||
return md5.convert(bytes).toString();
|
||||
}
|
||||
|
||||
// bool containsToLower(String target) {
|
||||
// return toLowerCase().contains(target);
|
||||
// }
|
||||
// bool containsToLower(String target) {
|
||||
// return toLowerCase().contains(target);
|
||||
// }
|
||||
}
|
||||
|
||||
extension StringExtensionSafe on String? {
|
||||
|
||||
@@ -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,25 +148,21 @@ 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,
|
||||
@@ -181,10 +177,7 @@ class Windows {
|
||||
calloc.free(argumentsPtr);
|
||||
calloc.free(operationPtr);
|
||||
|
||||
commonPrint.log(
|
||||
'windows runas: $command $arguments resultCode:$result',
|
||||
logLevel: LogLevel.warning,
|
||||
);
|
||||
commonPrint.log('windows runas: $command $arguments resultCode:$result');
|
||||
|
||||
if (result < 42) {
|
||||
return false;
|
||||
@@ -255,14 +248,15 @@ 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>
|
||||
@@ -301,9 +295,8 @@ 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',
|
||||
@@ -312,7 +305,10 @@ class Windows {
|
||||
'%s',
|
||||
'/F',
|
||||
].join(' ');
|
||||
return runas('schtasks', commandLine.replaceFirst('%s', taskPath));
|
||||
return runas(
|
||||
'schtasks',
|
||||
commandLine.replaceFirst('%s', taskPath),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,25 +337,23 @@ 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;
|
||||
}
|
||||
@@ -371,10 +365,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 = [];
|
||||
@@ -406,12 +400,15 @@ 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,6 +8,7 @@ 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';
|
||||
@@ -353,7 +354,7 @@ class AppController {
|
||||
try {
|
||||
await updateProfile(profile);
|
||||
} catch (e) {
|
||||
commonPrint.log(e.toString(), logLevel: LogLevel.warning);
|
||||
commonPrint.log(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -527,10 +528,11 @@ class AppController {
|
||||
|
||||
Future<void> init() async {
|
||||
FlutterError.onError = (details) {
|
||||
commonPrint.log(
|
||||
'exception: ${details.exception} stack: ${details.stack}',
|
||||
logLevel: LogLevel.warning,
|
||||
);
|
||||
if (kDebugMode) {
|
||||
commonPrint.log(
|
||||
'exception: ${details.exception} stack: ${details.stack}',
|
||||
);
|
||||
}
|
||||
};
|
||||
updateTray(true);
|
||||
autoUpdateProfiles();
|
||||
@@ -543,8 +545,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;
|
||||
@@ -552,12 +554,7 @@ class AppController {
|
||||
|
||||
Future<void> _connectCore() async {
|
||||
_ref.read(coreStatusProvider.notifier).value = CoreStatus.connecting;
|
||||
final result = await Future.wait([
|
||||
coreController.preload(),
|
||||
if (!globalState.isService) Future.delayed(Duration(milliseconds: 300)),
|
||||
]);
|
||||
final String message = result[0];
|
||||
await Future.delayed(commonDuration);
|
||||
final message = await coreController.preload();
|
||||
if (message.isNotEmpty) {
|
||||
_ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
|
||||
if (context.mounted) {
|
||||
@@ -637,47 +634,30 @@ 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: Text(appLocalizations.disclaimerDesc),
|
||||
child: SelectableText(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.select((state) => state.disclaimerAccepted),
|
||||
)) {
|
||||
if (_ref.read(appSettingProvider).disclaimerAccepted) {
|
||||
return;
|
||||
}
|
||||
final isDisclaimerAccepted = await showDisclaimer();
|
||||
if (!isDisclaimerAccepted) {
|
||||
await handleExit();
|
||||
}
|
||||
_ref
|
||||
.read(appSettingProvider.notifier)
|
||||
.updateState((state) => state.copyWith(disclaimerAccepted: true));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -962,7 +942,7 @@ class AppController {
|
||||
final res = await futureFunction();
|
||||
return res;
|
||||
} catch (e) {
|
||||
commonPrint.log('$futureFunction ===> $e', logLevel: LogLevel.warning);
|
||||
commonPrint.log('$futureFunction ===> $e');
|
||||
if (realSilence) {
|
||||
globalState.showNotifier(e.toString());
|
||||
} else {
|
||||
|
||||
@@ -40,7 +40,12 @@ class CoreController {
|
||||
if (!isExists) {
|
||||
await homeDir.create(recursive: true);
|
||||
}
|
||||
const geoFileNameList = [MMDB, GEOIP, GEOSITE, ASN];
|
||||
const geoFileNameList = [
|
||||
mmdbFileName,
|
||||
geoIpFileName,
|
||||
geoSiteFileName,
|
||||
asnFileName,
|
||||
];
|
||||
try {
|
||||
for (final geoFileName in geoFileNameList) {
|
||||
final geoFile = File(join(homePath, geoFileName));
|
||||
|
||||
@@ -4,7 +4,6 @@ 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';
|
||||
|
||||
@@ -18,14 +17,10 @@ class CoreLib extends CoreHandlerInterface {
|
||||
@override
|
||||
Future<String> preload() async {
|
||||
final res = await service?.init();
|
||||
if (res?.isEmpty != true) {
|
||||
return res ?? '';
|
||||
if (res?.isEmpty == true) {
|
||||
_connectedCompleter.complete(true);
|
||||
}
|
||||
_connectedCompleter.complete(true);
|
||||
final syncRes = await service?.syncAndroidState(
|
||||
globalState.getAndroidState(),
|
||||
);
|
||||
return syncRes ?? '';
|
||||
return res ?? '';
|
||||
}
|
||||
|
||||
factory CoreLib() {
|
||||
|
||||
@@ -52,7 +52,7 @@ class CoreService extends CoreHandlerInterface {
|
||||
}
|
||||
},
|
||||
(error, stack) async {
|
||||
commonPrint.log('Service error: $error', logLevel: LogLevel.warning);
|
||||
commonPrint.log('Service error: $error');
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -97,10 +97,9 @@ class CoreService extends CoreHandlerInterface {
|
||||
_process?.stderr.listen((e) {
|
||||
final error = utf8.decode(e);
|
||||
if (error.isNotEmpty) {
|
||||
commonPrint.log(error, logLevel: LogLevel.warning);
|
||||
commonPrint.log(error);
|
||||
}
|
||||
});
|
||||
await _socketCompleter.future;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -127,9 +126,9 @@ class CoreService extends CoreHandlerInterface {
|
||||
|
||||
Future<void> _destroySocket() async {
|
||||
if (_socketCompleter.isCompleted) {
|
||||
final socket = await _socketCompleter.future;
|
||||
final lastSocket = await _socketCompleter.future;
|
||||
_socketCompleter = Completer();
|
||||
socket.close();
|
||||
lastSocket.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,9 +179,7 @@ class CoreService extends CoreHandlerInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Future get connected {
|
||||
return _socketCompleter.future;
|
||||
}
|
||||
Future get connected => _socketCompleter.future;
|
||||
}
|
||||
|
||||
final coreService = system.isDesktop ? CoreService() : null;
|
||||
|
||||
@@ -199,21 +199,11 @@ 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",
|
||||
@@ -531,7 +521,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Please press the keyboard.",
|
||||
),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("Preview"),
|
||||
"process": MessageLookupByLibrary.simpleMessage("Process"),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("Profile"),
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
@@ -558,6 +547,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("Profiles"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("Profiles sort"),
|
||||
"progress": MessageLookupByLibrary.simpleMessage("Progress"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("Project"),
|
||||
"providers": MessageLookupByLibrary.simpleMessage("Providers"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("Proxies"),
|
||||
|
||||
@@ -151,19 +151,11 @@ 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(
|
||||
@@ -403,7 +395,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage("DOHのHTTP/3を優先使用"),
|
||||
"pressKeyboard": MessageLookupByLibrary.simpleMessage("キーボードを押してください"),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("プレビュー"),
|
||||
"process": MessageLookupByLibrary.simpleMessage("プロセス"),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("プロファイル"),
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage("有効な間隔形式を入力してください"),
|
||||
@@ -426,6 +417,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("プロファイル一覧"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("プロファイルの並び替え"),
|
||||
"progress": MessageLookupByLibrary.simpleMessage("進捗"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("プロジェクト"),
|
||||
"providers": MessageLookupByLibrary.simpleMessage("プロバイダー"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("プロキシ"),
|
||||
|
||||
@@ -204,21 +204,11 @@ 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(
|
||||
"Сервер имен по умолчанию",
|
||||
@@ -558,7 +548,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Пожалуйста, нажмите клавишу.",
|
||||
),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("Предпросмотр"),
|
||||
"process": MessageLookupByLibrary.simpleMessage("процесс"),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("Профиль"),
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
@@ -585,6 +574,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("Профили"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("Сортировка профилей"),
|
||||
"progress": MessageLookupByLibrary.simpleMessage("Прогресс"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("Проект"),
|
||||
"providers": MessageLookupByLibrary.simpleMessage("Провайдеры"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("Прокси"),
|
||||
|
||||
@@ -141,19 +141,11 @@ 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服务器"),
|
||||
@@ -355,7 +347,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage("优先使用DOH的http/3"),
|
||||
"pressKeyboard": MessageLookupByLibrary.simpleMessage("请按下按键"),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("预览"),
|
||||
"process": MessageLookupByLibrary.simpleMessage("进程"),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("配置"),
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage("请输入有效间隔时间格式"),
|
||||
@@ -376,6 +367,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("配置"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("配置排序"),
|
||||
"progress": MessageLookupByLibrary.simpleMessage("进度"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("项目"),
|
||||
"providers": MessageLookupByLibrary.simpleMessage("提供者"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("代理"),
|
||||
|
||||
@@ -3159,9 +3159,9 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Process`
|
||||
String get process {
|
||||
return Intl.message('Process', name: 'process', desc: '', args: []);
|
||||
/// `Progress`
|
||||
String get progress {
|
||||
return Intl.message('Progress', name: 'progress', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Host`
|
||||
@@ -3318,46 +3318,6 @@ 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,6 +2,7 @@ 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';
|
||||
@@ -22,9 +23,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 {
|
||||
|
||||
@@ -181,34 +181,39 @@ class AppSidebarContainer extends ConsumerWidget {
|
||||
return child;
|
||||
}
|
||||
final currentIndex = navigationState.currentIndex;
|
||||
final showLabel = ref.watch(appSettingProvider).showLabel;
|
||||
final isExtend = ref.watch(appSettingProvider).showLabel;
|
||||
return Row(
|
||||
children: [
|
||||
_buildBackground(
|
||||
context: context,
|
||||
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(
|
||||
Stack(
|
||||
alignment: Alignment.topRight,
|
||||
children: [
|
||||
_buildBackground(
|
||||
context: context,
|
||||
child: SafeArea(
|
||||
child: AnimatedSize(
|
||||
curve: Curves.easeOut,
|
||||
alignment: Alignment.centerLeft,
|
||||
duration: commonDuration,
|
||||
child: 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: SingleChildScrollView(
|
||||
child: IntrinsicHeight(
|
||||
child: NavigationRail(
|
||||
scrollable: true,
|
||||
minExtendedWidth: 200,
|
||||
backgroundColor: Colors.transparent,
|
||||
selectedLabelTextStyle: context
|
||||
.textTheme
|
||||
@@ -235,39 +240,43 @@ class AppSidebarContainer extends ConsumerWidget {
|
||||
navigationItems[index].label,
|
||||
);
|
||||
},
|
||||
extended: false,
|
||||
extended: isExtend,
|
||||
selectedIndex: currentIndex,
|
||||
labelType: showLabel
|
||||
? NavigationRailLabelType.all
|
||||
: NavigationRailLabelType.none,
|
||||
// labelType: showLabel
|
||||
// ? NavigationRailLabelType.all
|
||||
// : NavigationRailLabelType.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(appSettingProvider.notifier)
|
||||
.updateState(
|
||||
(state) =>
|
||||
state.copyWith(showLabel: !state.showLabel),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.menu,
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(appSettingProvider.notifier)
|
||||
.updateState(
|
||||
(state) => state.copyWith(
|
||||
showLabel: !state.showLabel,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.menu,
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildLoading(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildLoading(),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
@@ -18,9 +17,8 @@ class MessageManager extends StatefulWidget {
|
||||
|
||||
class MessageManagerState extends State<MessageManager> {
|
||||
final _messagesNotifier = ValueNotifier<List<CommonMessage>>([]);
|
||||
final _bufferMessages = Queue<CommonMessage>();
|
||||
final _activeTimers = <String, Timer>{};
|
||||
bool _isDisplayingMessage = false;
|
||||
final List<CommonMessage> _bufferMessages = [];
|
||||
bool _pushing = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -30,48 +28,38 @@ class MessageManagerState extends State<MessageManager> {
|
||||
@override
|
||||
void dispose() {
|
||||
_messagesNotifier.dispose();
|
||||
for (final timer in _activeTimers.values) {
|
||||
timer.cancel();
|
||||
}
|
||||
_activeTimers.clear();
|
||||
_bufferMessages.clear();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void message(String text) {
|
||||
Future<void> message(String text) async {
|
||||
final commonMessage = CommonMessage(id: utils.uuidV4, text: text);
|
||||
commonPrint.log(text);
|
||||
_bufferMessages.add(commonMessage);
|
||||
commonPrint.log('message: $text');
|
||||
_processQueue();
|
||||
await _showMessage();
|
||||
}
|
||||
|
||||
void _cancelMessage(String id) {
|
||||
_bufferMessages.removeWhere((msg) => msg.id == id);
|
||||
if (_activeTimers.containsKey(id)) {
|
||||
_removeMessage(id);
|
||||
}
|
||||
}
|
||||
|
||||
void _processQueue() {
|
||||
if (_isDisplayingMessage || _bufferMessages.isEmpty) {
|
||||
Future<void> _showMessage() async {
|
||||
if (_pushing == true) {
|
||||
return;
|
||||
}
|
||||
_isDisplayingMessage = true;
|
||||
final message = _bufferMessages.removeFirst();
|
||||
_messagesNotifier.value = List.from(_messagesNotifier.value)..add(message);
|
||||
final timer = Timer(message.duration, () {
|
||||
_removeMessage(message.id);
|
||||
});
|
||||
_activeTimers[message.id] = timer;
|
||||
_pushing = true;
|
||||
while (_bufferMessages.isNotEmpty) {
|
||||
final commonMessage = _bufferMessages.removeAt(0);
|
||||
_messagesNotifier.value = List.from(_messagesNotifier.value)
|
||||
..add(commonMessage);
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
Future.delayed(commonMessage.duration, () {
|
||||
_handleRemove(commonMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _removeMessage(String id) {
|
||||
_activeTimers.remove(id)?.cancel();
|
||||
final currentMessages = List<CommonMessage>.from(_messagesNotifier.value);
|
||||
currentMessages.removeWhere((msg) => msg.id == id);
|
||||
_messagesNotifier.value = currentMessages;
|
||||
_isDisplayingMessage = false;
|
||||
_processQueue();
|
||||
Future<void> _handleRemove(CommonMessage commonMessage) async {
|
||||
_messagesNotifier.value = List<CommonMessage>.from(_messagesNotifier.value)
|
||||
..remove(commonMessage);
|
||||
if (_bufferMessages.isEmpty) {
|
||||
_pushing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -95,47 +83,35 @@ class MessageManagerState extends State<MessageManager> {
|
||||
: LayoutBuilder(
|
||||
key: Key(messages.last.id),
|
||||
builder: (_, constraints) {
|
||||
return Dismissible(
|
||||
key: ValueKey(messages.last.id),
|
||||
onDismissed: (_) {
|
||||
_cancelMessage(messages.last.id);
|
||||
},
|
||||
child: Card(
|
||||
shape: const RoundedSuperellipseBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(14),
|
||||
),
|
||||
return Card(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12.0),
|
||||
),
|
||||
elevation: 10,
|
||||
color: context.colorScheme.surfaceContainerHigh,
|
||||
child: Container(
|
||||
width: min(constraints.maxWidth, 500),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
messages.last.text,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
IconButton(
|
||||
padding: EdgeInsets.all(2),
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: () {
|
||||
_cancelMessage(messages.last.id);
|
||||
},
|
||||
icon: Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
elevation: 10,
|
||||
color: context.colorScheme.surfaceContainerHigh,
|
||||
child: Container(
|
||||
width: min(constraints.maxWidth, 500),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 10,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(child: Text(messages.last.text)),
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
iconSize: 18,
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
_handleRemove(messages.last);
|
||||
},
|
||||
icon: Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -87,23 +87,13 @@ class ThemeManager extends ConsumerWidget {
|
||||
top: padding.top > height * 0.3 ? 20.0 : padding.top,
|
||||
),
|
||||
),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
floatingActionButtonTheme: Theme.of(context).floatingActionButtonTheme
|
||||
.copyWith(
|
||||
shape: const RoundedSuperellipseBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16.0)),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (_, container) {
|
||||
globalState.appController.updateViewSize(
|
||||
Size(container.maxWidth, container.maxHeight),
|
||||
);
|
||||
return _buildSystemUi(child);
|
||||
},
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (_, container) {
|
||||
globalState.appController.updateViewSize(
|
||||
Size(container.maxWidth, container.maxHeight),
|
||||
);
|
||||
return _buildSystemUi(child);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
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';
|
||||
@@ -6,7 +5,10 @@ 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();
|
||||
@@ -20,18 +22,12 @@ 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();
|
||||
}
|
||||
|
||||
@@ -24,16 +24,11 @@ class _VpnContainerState extends ConsumerState<VpnManager> {
|
||||
}
|
||||
|
||||
void showTip() {
|
||||
throttler.call(
|
||||
FunctionTag.vpnTip,
|
||||
() {
|
||||
if (ref.read(isStartProvider)) {
|
||||
globalState.showNotifier(appLocalizations.vpnTip);
|
||||
}
|
||||
},
|
||||
duration: const Duration(seconds: 6),
|
||||
fire: true,
|
||||
);
|
||||
debouncer.call(FunctionTag.vpnTip, () {
|
||||
if (ref.read(isStartProvider)) {
|
||||
globalState.showNotifier(appLocalizations.vpnTip);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -271,16 +271,18 @@ class AppIcon extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: ShapeDecoration(
|
||||
color: context.colorScheme.surfaceContainerHighest,
|
||||
shape: RoundedSuperellipseBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, -1),
|
||||
child: Image.asset('assets/images/icon.png', width: 34, height: 34),
|
||||
padding: EdgeInsets.all(6),
|
||||
child: SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: CircleAvatar(
|
||||
foregroundImage: AssetImage('assets/images/icon.png'),
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -445,7 +445,6 @@ 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,8 +77,6 @@ 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; bool get crashlytics;
|
||||
String get currentProfileName; String get stopText; bool get onlyStatisticsProxy;
|
||||
/// 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)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics));
|
||||
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));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,currentProfileName,stopText,onlyStatisticsProxy,crashlytics);
|
||||
int get hashCode => Object.hash(runtimeType,currentProfileName,stopText,onlyStatisticsProxy);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AndroidState(currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy, crashlytics: $crashlytics)';
|
||||
return 'AndroidState(currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy)';
|
||||
}
|
||||
|
||||
|
||||
@@ -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, bool crashlytics
|
||||
String currentProfileName, String stopText, bool onlyStatisticsProxy
|
||||
});
|
||||
|
||||
|
||||
@@ -5023,12 +5023,11 @@ 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,Object? crashlytics = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? currentProfileName = null,Object? stopText = null,Object? onlyStatisticsProxy = 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,
|
||||
));
|
||||
}
|
||||
@@ -5114,10 +5113,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String currentProfileName, String stopText, bool onlyStatisticsProxy)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AndroidState() when $default != null:
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics);case _:
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -5135,10 +5134,10 @@ return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProx
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String currentProfileName, String stopText, bool onlyStatisticsProxy) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AndroidState():
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics);case _:
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -5155,10 +5154,10 @@ return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProx
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String currentProfileName, String stopText, bool onlyStatisticsProxy)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AndroidState() when $default != null:
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics);case _:
|
||||
return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -5170,13 +5169,12 @@ return $default(_that.currentProfileName,_that.stopText,_that.onlyStatisticsProx
|
||||
@JsonSerializable()
|
||||
|
||||
class _AndroidState implements AndroidState {
|
||||
const _AndroidState({required this.currentProfileName, required this.stopText, required this.onlyStatisticsProxy, required this.crashlytics});
|
||||
const _AndroidState({required this.currentProfileName, required this.stopText, required this.onlyStatisticsProxy});
|
||||
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.
|
||||
@@ -5191,16 +5189,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)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics));
|
||||
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));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,currentProfileName,stopText,onlyStatisticsProxy,crashlytics);
|
||||
int get hashCode => Object.hash(runtimeType,currentProfileName,stopText,onlyStatisticsProxy);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AndroidState(currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy, crashlytics: $crashlytics)';
|
||||
return 'AndroidState(currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy)';
|
||||
}
|
||||
|
||||
|
||||
@@ -5211,7 +5209,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, bool crashlytics
|
||||
String currentProfileName, String stopText, bool onlyStatisticsProxy
|
||||
});
|
||||
|
||||
|
||||
@@ -5228,12 +5226,11 @@ 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,Object? crashlytics = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? currentProfileName = null,Object? stopText = null,Object? onlyStatisticsProxy = 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,7 +244,6 @@ _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) =>
|
||||
@@ -252,7 +251,6 @@ 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 crashlyticsTip; bool get crashlytics; 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 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.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));
|
||||
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));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
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]);
|
||||
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);
|
||||
|
||||
@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, crashlyticsTip: $crashlyticsTip, crashlytics: $crashlytics, 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, 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 crashlyticsTip, bool crashlytics, 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 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? crashlyticsTip = null,Object? crashlytics = 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? 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,8 +80,6 @@ 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
|
||||
@@ -171,10 +169,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 crashlyticsTip, bool crashlytics, 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 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.crashlyticsTip,_that.crashlytics,_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.minimizeOnExit,_that.hidden,_that.developerMode,_that.recoveryStrategy);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -192,10 +190,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 crashlyticsTip, bool crashlytics, 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 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.crashlyticsTip,_that.crashlytics,_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.minimizeOnExit,_that.hidden,_that.developerMode,_that.recoveryStrategy);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -212,10 +210,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 crashlyticsTip, bool crashlytics, 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 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.crashlyticsTip,_that.crashlytics,_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.minimizeOnExit,_that.hidden,_that.developerMode,_that.recoveryStrategy);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -227,7 +225,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.crashlyticsTip = false, this.crashlytics = 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.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;
|
||||
@@ -249,8 +247,6 @@ 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;
|
||||
@@ -269,16 +265,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.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));
|
||||
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));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
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]);
|
||||
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);
|
||||
|
||||
@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, crashlyticsTip: $crashlyticsTip, crashlytics: $crashlytics, 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, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, recoveryStrategy: $recoveryStrategy)';
|
||||
}
|
||||
|
||||
|
||||
@@ -289,7 +285,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 crashlyticsTip, bool crashlytics, 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 minimizeOnExit, bool hidden, bool developerMode, RecoveryStrategy recoveryStrategy
|
||||
});
|
||||
|
||||
|
||||
@@ -306,7 +302,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? crashlyticsTip = null,Object? crashlytics = 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? 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
|
||||
@@ -321,8 +317,6 @@ 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,8 +23,6 @@ _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,
|
||||
@@ -53,8 +51,6 @@ 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,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/manager/app_manager.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
@@ -17,85 +16,83 @@ class HomePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return HomeBackScopeContainer(
|
||||
child: AppSidebarContainer(
|
||||
child: Material(
|
||||
color: context.colorScheme.surface,
|
||||
child: Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final state = ref.watch(navigationStateProvider);
|
||||
final isMobile = state.viewMode == ViewMode.mobile;
|
||||
final navigationItems = state.navigationItems;
|
||||
final pageView = _HomePageView(
|
||||
pageBuilder: (_, index) {
|
||||
final navigationItem = state.navigationItems[index];
|
||||
final navigationView = navigationItem.builder(context);
|
||||
final view = KeepScope(
|
||||
keep: navigationItem.keep,
|
||||
child: isMobile
|
||||
? navigationView
|
||||
: Navigator(
|
||||
pages: [MaterialPage(child: navigationView)],
|
||||
onDidRemovePage: (_) {},
|
||||
),
|
||||
);
|
||||
return view;
|
||||
},
|
||||
);
|
||||
final currentIndex = state.currentIndex;
|
||||
final bottomNavigationBar = NavigationBarTheme(
|
||||
data: _NavigationBarDefaultsM3(context),
|
||||
child: NavigationBar(
|
||||
destinations: navigationItems
|
||||
.map(
|
||||
(e) => NavigationDestination(
|
||||
icon: e.icon,
|
||||
label: Intl.message(e.label.name),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onDestinationSelected: (index) {
|
||||
globalState.appController.toPage(
|
||||
navigationItems[index].label,
|
||||
);
|
||||
},
|
||||
selectedIndex: currentIndex,
|
||||
),
|
||||
);
|
||||
if (isMobile) {
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: globalState.appState.systemUiOverlayStyle.copyWith(
|
||||
systemNavigationBarColor:
|
||||
context.colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: MediaQuery.removePadding(
|
||||
removeTop: false,
|
||||
removeBottom: true,
|
||||
removeLeft: true,
|
||||
removeRight: true,
|
||||
context: context,
|
||||
child: pageView,
|
||||
child: Material(
|
||||
color: context.colorScheme.surface,
|
||||
child: Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final state = ref.watch(navigationStateProvider);
|
||||
final isMobile = state.viewMode == ViewMode.mobile;
|
||||
final navigationItems = state.navigationItems;
|
||||
final pageView = _HomePageView(
|
||||
pageBuilder: (_, index) {
|
||||
final navigationItem = state.navigationItems[index];
|
||||
final navigationView = navigationItem.builder(context);
|
||||
final view = KeepScope(
|
||||
keep: navigationItem.keep,
|
||||
child: isMobile
|
||||
? navigationView
|
||||
: Navigator(
|
||||
pages: [MaterialPage(child: navigationView)],
|
||||
onDidRemovePage: (_) {},
|
||||
),
|
||||
);
|
||||
return view;
|
||||
},
|
||||
);
|
||||
final currentIndex = state.currentIndex;
|
||||
final bottomNavigationBar = NavigationBarTheme(
|
||||
data: _NavigationBarDefaultsM3(context),
|
||||
child: NavigationBar(
|
||||
destinations: navigationItems
|
||||
.map(
|
||||
(e) => NavigationDestination(
|
||||
icon: e.icon,
|
||||
label: Intl.message(e.label.name),
|
||||
),
|
||||
MediaQuery.removePadding(
|
||||
removeTop: true,
|
||||
removeBottom: false,
|
||||
)
|
||||
.toList(),
|
||||
onDestinationSelected: (index) {
|
||||
globalState.appController.toPage(
|
||||
navigationItems[index].label,
|
||||
);
|
||||
},
|
||||
selectedIndex: currentIndex,
|
||||
),
|
||||
);
|
||||
if (isMobile) {
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: globalState.appState.systemUiOverlayStyle.copyWith(
|
||||
systemNavigationBarColor:
|
||||
context.colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: MediaQuery.removePadding(
|
||||
removeTop: false,
|
||||
removeBottom: true,
|
||||
removeLeft: true,
|
||||
removeRight: true,
|
||||
context: context,
|
||||
child: bottomNavigationBar,
|
||||
child: pageView,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return pageView;
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
MediaQuery.removePadding(
|
||||
removeTop: true,
|
||||
removeBottom: false,
|
||||
removeLeft: true,
|
||||
removeRight: true,
|
||||
context: context,
|
||||
child: bottomNavigationBar,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return pageView;
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -169,8 +169,8 @@ class ScannerOverlay extends CustomPainter {
|
||||
final backgroundPath = Path()..addRect(Rect.largest);
|
||||
|
||||
final cutoutPath = Path()
|
||||
..addRSuperellipse(
|
||||
RSuperellipse.fromRectAndCorners(
|
||||
..addRRect(
|
||||
RRect.fromRectAndCorners(
|
||||
scanWindow,
|
||||
topLeft: Radius.circular(borderRadius),
|
||||
topRight: Radius.circular(borderRadius),
|
||||
@@ -195,7 +195,7 @@ class ScannerOverlay extends CustomPainter {
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 4.0;
|
||||
|
||||
final border = RSuperellipse.fromRectAndCorners(
|
||||
final borderRect = RRect.fromRectAndCorners(
|
||||
scanWindow,
|
||||
topLeft: Radius.circular(borderRadius),
|
||||
topRight: Radius.circular(borderRadius),
|
||||
@@ -204,7 +204,7 @@ class ScannerOverlay extends CustomPainter {
|
||||
);
|
||||
|
||||
canvas.drawPath(backgroundWithCutout, backgroundPaint);
|
||||
canvas.drawRSuperellipse(border, borderPaint);
|
||||
canvas.drawRRect(borderRect, borderPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -86,11 +86,7 @@ class Service {
|
||||
}
|
||||
|
||||
Future<String> init() async {
|
||||
return await methodChannel.invokeMethod<String>(
|
||||
'init',
|
||||
!globalState.isService,
|
||||
) ??
|
||||
'';
|
||||
return await methodChannel.invokeMethod<String>('init') ?? '';
|
||||
}
|
||||
|
||||
Future<bool> shutdown() async {
|
||||
|
||||
@@ -6,9 +6,6 @@ part of '../app.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(RealTunEnable)
|
||||
const realTunEnableProvider = RealTunEnableProvider._();
|
||||
|
||||
@@ -1489,3 +1486,6 @@ 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,9 +6,6 @@ part of '../config.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(AppSetting)
|
||||
const appSettingProvider = AppSettingProvider._();
|
||||
|
||||
@@ -696,3 +693,6 @@ 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,9 +6,6 @@ part of '../state.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(configState)
|
||||
const configStateProvider = ConfigStateProvider._();
|
||||
|
||||
@@ -434,7 +431,7 @@ final class ContentWidthProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$contentWidthHash() => r'4710fee8f91b08dc88520798fb6b1d4f61f090e6';
|
||||
String _$contentWidthHash() => r'9ac0dc2bcdc957160f59d6065cd7f846f41a95b1';
|
||||
|
||||
@ProviderFor(dashboardState)
|
||||
const dashboardStateProvider = DashboardStateProvider._();
|
||||
@@ -1955,14 +1952,8 @@ final class GetProfileOverrideDataFamily extends $Family
|
||||
@ProviderFor(layoutChange)
|
||||
const layoutChangeProvider = LayoutChangeProvider._();
|
||||
|
||||
final class LayoutChangeProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
VM2<dynamic, dynamic>?,
|
||||
VM2<dynamic, dynamic>?,
|
||||
VM2<dynamic, dynamic>?
|
||||
>
|
||||
with $Provider<VM2<dynamic, dynamic>?> {
|
||||
final class LayoutChangeProvider extends $FunctionalProvider<VM2?, VM2?, VM2?>
|
||||
with $Provider<VM2?> {
|
||||
const LayoutChangeProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
@@ -1979,20 +1970,19 @@ final class LayoutChangeProvider
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<VM2<dynamic, dynamic>?> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
$ProviderElement<VM2?> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
VM2<dynamic, dynamic>? create(Ref ref) {
|
||||
VM2? create(Ref ref) {
|
||||
return layoutChange(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(VM2<dynamic, dynamic>? value) {
|
||||
Override overrideWithValue(VM2? value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<VM2<dynamic, dynamic>?>(value),
|
||||
providerOverride: $SyncValueProvider<VM2?>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2352,7 +2342,7 @@ final class AndroidStateProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$androidStateHash() => r'9f527fbb00c7e0c177f023e77d2f23458543d72f';
|
||||
String _$androidStateHash() => r'bdf7baeb4ed4041233f06f0aede8d4d41489ebd6';
|
||||
|
||||
@ProviderFor(Query)
|
||||
const queryProvider = QueryFamily._();
|
||||
@@ -2442,3 +2432,6 @@ 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,14 +629,10 @@ 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,7 +48,6 @@ class GlobalState {
|
||||
AppController? _appController;
|
||||
bool isInit = false;
|
||||
bool isUserDisconnected = false;
|
||||
bool isService = false;
|
||||
|
||||
bool get isStart => startTime != null && startTime!.isBeforeNow;
|
||||
|
||||
@@ -184,16 +183,12 @@ class GlobalState {
|
||||
}
|
||||
|
||||
Future<bool?> showMessage({
|
||||
required InlineSpan message,
|
||||
BuildContext? context,
|
||||
String? title,
|
||||
required InlineSpan message,
|
||||
String? confirmText,
|
||||
bool cancelable = true,
|
||||
bool? dismissible,
|
||||
}) async {
|
||||
return await showCommonDialog<bool>(
|
||||
context: context,
|
||||
dismissible: dismissible,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return CommonDialog(
|
||||
@@ -251,15 +246,13 @@ class GlobalState {
|
||||
|
||||
Future<T?> showCommonDialog<T>({
|
||||
required Widget child,
|
||||
BuildContext? context,
|
||||
bool? dismissible,
|
||||
bool dismissible = true,
|
||||
}) async {
|
||||
return await showModal<T>(
|
||||
useRootNavigator: false,
|
||||
context: context ?? globalState.navigatorKey.currentContext!,
|
||||
context: navigatorKey.currentState!.context,
|
||||
configuration: FadeScaleTransitionConfiguration(
|
||||
barrierColor: Colors.black38,
|
||||
barrierDismissible: dismissible ?? true,
|
||||
barrierDismissible: dismissible,
|
||||
),
|
||||
builder: (_) => child,
|
||||
filter: commonFilter,
|
||||
@@ -328,7 +321,6 @@ class GlobalState {
|
||||
currentProfileName: config.currentProfile?.label ?? '',
|
||||
onlyStatisticsProxy: config.appSetting.onlyStatisticsProxy,
|
||||
stopText: appLocalizations.stop,
|
||||
crashlytics: config.appSetting.crashlytics,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -214,29 +214,6 @@ 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});
|
||||
|
||||
@@ -279,7 +256,6 @@ 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/v2.4.2', 'ClashforWindows/0.19.23'],
|
||||
options: [null, 'clash-verge/v1.6.6', 'ClashforWindows/0.19.23'],
|
||||
value: globalUa,
|
||||
onChanged: (value) {
|
||||
ref
|
||||
|
||||
@@ -202,7 +202,7 @@ class TrackerInfoDetailView extends StatelessWidget {
|
||||
return rule;
|
||||
}
|
||||
|
||||
String _getProcessText() {
|
||||
String _getProgressText() {
|
||||
final process = trackerInfo.metadata.process;
|
||||
final uid = trackerInfo.metadata.uid;
|
||||
if (uid != 0) {
|
||||
@@ -249,7 +249,6 @@ class TrackerInfoDetailView extends StatelessWidget {
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 20,
|
||||
children: [
|
||||
Text(appLocalizations.proxyChains),
|
||||
Flexible(child: chains),
|
||||
@@ -298,8 +297,8 @@ class TrackerInfoDetailView extends StatelessWidget {
|
||||
title: appLocalizations.creationTime,
|
||||
desc: trackerInfo.start.showFull,
|
||||
),
|
||||
if (_getProcessText().isNotEmpty)
|
||||
_buildItem(title: appLocalizations.process, desc: _getProcessText()),
|
||||
if (_getProgressText().isNotEmpty)
|
||||
_buildItem(title: appLocalizations.progress, desc: _getProgressText()),
|
||||
_buildItem(
|
||||
title: appLocalizations.networkType,
|
||||
desc: trackerInfo.metadata.network,
|
||||
|
||||
@@ -14,7 +14,7 @@ class StartButton extends ConsumerStatefulWidget {
|
||||
|
||||
class _StartButtonState extends ConsumerState<StartButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
AnimationController? _controller;
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
bool isStart = false;
|
||||
|
||||
@@ -28,7 +28,7 @@ class _StartButtonState extends ConsumerState<StartButton>
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
_animation = CurvedAnimation(
|
||||
parent: _controller!,
|
||||
parent: _controller,
|
||||
curve: Curves.easeOutBack,
|
||||
);
|
||||
ref.listenManual(runTimeProvider.select((state) => state != null), (
|
||||
@@ -44,8 +44,7 @@ class _StartButtonState extends ConsumerState<StartButton>
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
_controller = null;
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -60,9 +59,9 @@ class _StartButtonState extends ConsumerState<StartButton>
|
||||
void updateController() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (isStart && mounted) {
|
||||
_controller?.forward();
|
||||
_controller.forward();
|
||||
} else {
|
||||
_controller?.reverse();
|
||||
_controller.reverse();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -75,13 +74,12 @@ class _StartButtonState extends ConsumerState<StartButton>
|
||||
}
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
floatingActionButtonTheme: Theme.of(context).floatingActionButtonTheme
|
||||
.copyWith(
|
||||
sizeConstraints: BoxConstraints(minWidth: 56, maxWidth: 200),
|
||||
),
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||
sizeConstraints: BoxConstraints(minWidth: 56, maxWidth: 200),
|
||||
),
|
||||
),
|
||||
child: AnimatedBuilder(
|
||||
animation: _controller!.view,
|
||||
animation: _controller.view,
|
||||
builder: (_, child) {
|
||||
final textWidth =
|
||||
globalState.measure
|
||||
|
||||
@@ -128,11 +128,10 @@ class TrafficUsage extends StatelessWidget {
|
||||
Container(
|
||||
width: 20,
|
||||
height: 8,
|
||||
decoration: ShapeDecoration(
|
||||
decoration: BoxDecoration(
|
||||
color: primaryColor,
|
||||
shape: RoundedSuperellipseBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(3),
|
||||
borderRadius: BorderRadius.circular(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -152,11 +151,10 @@ class TrafficUsage extends StatelessWidget {
|
||||
Container(
|
||||
width: 20,
|
||||
height: 8,
|
||||
decoration: ShapeDecoration(
|
||||
decoration: BoxDecoration(
|
||||
color: secondaryColor,
|
||||
shape: RoundedSuperellipseBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(3),
|
||||
borderRadius: BorderRadius.circular(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,10 +2,10 @@ import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/core/controller.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/common.dart';
|
||||
import 'package:fl_clash/providers/app.dart';
|
||||
import 'package:fl_clash/providers/config.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
@@ -18,14 +18,12 @@ class DeveloperView extends ConsumerWidget {
|
||||
items: [
|
||||
ListItem(
|
||||
title: Text(appLocalizations.messageTest),
|
||||
minVerticalPadding: 14,
|
||||
onTap: () {
|
||||
context.showNotifier(appLocalizations.messageTestTip);
|
||||
},
|
||||
),
|
||||
ListItem(
|
||||
title: Text(appLocalizations.logsTest),
|
||||
minVerticalPadding: 14,
|
||||
onTap: () {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
globalState.appController.addLog(
|
||||
@@ -38,29 +36,24 @@ class DeveloperView extends ConsumerWidget {
|
||||
),
|
||||
ListItem(
|
||||
title: Text(appLocalizations.crashTest),
|
||||
minVerticalPadding: 14,
|
||||
onTap: () {
|
||||
if (kDebugMode) {
|
||||
coreController.crash();
|
||||
}
|
||||
coreController.crash();
|
||||
},
|
||||
),
|
||||
ListItem(
|
||||
title: Text(appLocalizations.clearData),
|
||||
minVerticalPadding: 14,
|
||||
onTap: () async {
|
||||
await globalState.appController.handleClear();
|
||||
},
|
||||
),
|
||||
// ListItem(
|
||||
// title: Text('Loading'),
|
||||
// minVerticalPadding: 14,
|
||||
// onTap: () {
|
||||
// ref.read(loadingProvider.notifier).value = !ref.read(
|
||||
// loadingProvider,
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
ListItem(
|
||||
title: Text('Loading'),
|
||||
onTap: () {
|
||||
ref.read(loadingProvider.notifier).value = !ref.read(
|
||||
loadingProvider,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@ class DelayTestButton extends StatefulWidget {
|
||||
class _DelayTestButtonState extends State<DelayTestButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
late Animation<double> _scale;
|
||||
|
||||
Future<void> _healthcheck() async {
|
||||
if (_controller.isAnimating) {
|
||||
@@ -378,10 +378,10 @@ class _DelayTestButtonState extends State<DelayTestButton>
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
_animation = Tween<double>(begin: 1.0, end: 0.0).animate(
|
||||
CurvedAnimation(parent: _controller, curve: Curves.easeInOutBack),
|
||||
_scale = Tween<double>(begin: 1.0, end: 0.0).animate(
|
||||
CurvedAnimation(parent: _controller, curve: const Interval(0, 1)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -399,10 +399,7 @@ class _DelayTestButtonState extends State<DelayTestButton>
|
||||
return SizedBox(
|
||||
width: 56,
|
||||
height: 56,
|
||||
child: FadeTransition(
|
||||
opacity: _animation,
|
||||
child: ScaleTransition(scale: _animation, child: child),
|
||||
),
|
||||
child: Transform.scale(scale: _scale.value, child: child),
|
||||
);
|
||||
},
|
||||
child: FloatingActionButton(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user