Compare commits

..

1 Commits

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

Optimize proxies page and access page

Update flutter and pub dependencies
2025-09-05 19:55:08 +08:00
85 changed files with 36742 additions and 46485 deletions

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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": []
}
}
}
]
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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()
}
}
}
}
}

View File

@@ -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
) {

View File

@@ -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,38 +34,13 @@ 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(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)
}
it.invokeAction(data, object : ICallbackInterface.Stub() {
override fun onResult(result: ByteArray?, isSuccess: Boolean) {
cb(result, isSuccess)
}
})
}
@@ -84,48 +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
): Unit = suspendCancellableCoroutine { continuation ->
val callback = object : IResultInterface.Stub() {
override fun onResult() {
if (continuation.isActive) {
continuation.resume(Unit)
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) {
delegate.useService {
awaitIResultInterface { callback ->
it.startService(options, callback)
}
}
suspend fun startService(options: VpnOptions, inApp: Boolean) {
delegate.useService { it.startService(options, inApp) }
}
suspend fun stopService() {
delegate.useService {
awaitIResultInterface { callback ->
it.stopService(callback)
}
}
delegate.useService { it.stopService() }
}
}

View File

@@ -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
@@ -101,6 +100,7 @@ object State {
)
serviceFlutterEngine?.dartExecutor?.executeDartEntrypoint(dartEntrypoint)
}
}
}
@@ -119,8 +119,8 @@ object State {
return@launch
}
appPlugin?.prepare(options.enable) {
Service.startService(options)
runTime = System.currentTimeMillis()
Service.startService(options, true)
runStateFlow.tryEmit(RunState.START)
}
}
@@ -141,8 +141,8 @@ object State {
}
destroyServiceEngine()
}
}
}

View File

@@ -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,
)

View File

@@ -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(

View File

@@ -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
@@ -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,26 +119,27 @@ 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(result: MethodChannel.Result) {
Service.bind()
launch {
Service.setEventListener {
Service.setMessageCallback {
handleSendEvent(it)
}.onSuccess {
result.success("")

View File

@@ -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)
}

View File

@@ -10,8 +10,9 @@ enum class QuickAction {
}
enum class BroadcastAction {
SERVICE_CREATED,
SERVICE_DESTROYED,
START,
STOP,
TOGGLE,
}
enum class AccessControlMode {

View File

@@ -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 ->

View File

@@ -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}")
}
}
}
}

View File

@@ -50,7 +50,7 @@ class ServiceDelegate<T>(
}
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) {

View File

@@ -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" }

View File

@@ -2,5 +2,5 @@
package com.follow.clash.service;
interface ICallbackInterface {
void onResult(in byte[] data,in boolean isSuccess);
void onResult(in byte[] result, boolean isSuccess);
}

View File

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

View File

@@ -0,0 +1,6 @@
// IMessageInterface.aidl
package com.follow.clash.service;
interface IMessageInterface {
void onResult(String result);
}

View File

@@ -2,16 +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 IResultInterface result);
void stopService(in IResultInterface result);
void setEventListener(in IEventInterface event);
void setCrashlytics(in boolean enable);
void startService(in VpnOptions options,in boolean inApp);
void stopService();
void setMessageCallback(in IMessageInterface messageCallback);
}

View File

@@ -1,6 +0,0 @@
// IResultInterface.aidl
package com.follow.clash.service;
interface IResultInterface {
void onResult();
}

View File

@@ -29,11 +29,6 @@ class CommonService : Service(), IBaseService,
handleCreate()
}
override fun onDestroy() {
handleDestroy()
super.onDestroy()
}
override fun onLowMemory() {
Core.forceGC()
super.onLowMemory()

View File

@@ -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()

View File

@@ -3,7 +3,6 @@ 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
@@ -14,58 +13,46 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.UUID
class RemoteService : Service(),
CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
private var delegate: ServiceDelegate<IBaseService>? = null
private var intent: Intent? = null
val runLock = Mutex()
private fun handleStopService(result: IResultInterface) {
private fun handleStopService() {
launch {
runLock.withLock {
delegate?.useService { service ->
service.stop()
delegate?.unbind()
}
result.onResult()
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(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()
}
result.onResult()
intent = nextIntent
delegate?.bind()
}
delegate?.useService { service ->
service.start()
}
}
}
@@ -73,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)
}
}
}
@@ -88,33 +73,24 @@ class RemoteService : Service(),
}
override fun startService(
options: VpnOptions,
result: IResultInterface,
options: VpnOptions, inApp: Boolean
) {
State.options = options
handleStartService(result)
State.inApp = inApp
handleStartService()
}
override fun stopService(result: IResultInterface) {
handleStopService(result)
override fun stopService() {
handleStopService()
}
override fun setEventListener(event: IEventInterface) {
Core.setMessageCallback {
runCatching {
val id = UUID.randomUUID().toString()
val chunks = it?.chunkedForAidl() ?: listOf()
val totalSize = chunks.size
chunks.forEachIndexed { index, chunk ->
event.onEvent(id, chunk, totalSize - 1 == index)
}
}
}
override fun setMessageCallback(messageCallback: IMessageInterface) {
setMessageCallback(messageCallback::onResult)
}
}
override fun setCrashlytics(enable: Boolean) {
GlobalState.setCrashlytics(enable)
}
private fun setMessageCallback(cb: (result: String?) -> Unit) {
Core.setMessageCallback(cb)
}
override fun onBind(intent: Intent?): IBinder {

View File

@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
object State {
var options: VpnOptions? = null
var inApp: Boolean = false
var notificationParamsFlow: MutableStateFlow<NotificationParams?> = MutableStateFlow(
NotificationParams()
)

View File

@@ -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
}
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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"
}

View File

@@ -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": "コアステータス"
}

View File

@@ -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": "Основной статус"
}

View File

@@ -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.

File diff suppressed because one or more lines are too long

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

Binary file not shown.

View File

@@ -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())

View File

@@ -164,7 +164,7 @@ func (result ActionResult) send() {
}
invokeResult(result.callback, string(data))
if result.Method != messageMethod {
releaseObject(result.callback)
defer releaseObject(result.callback)
}
}

View File

@@ -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
View 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;

View File

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

View File

@@ -1,3 +1,4 @@
export 'android.dart';
export 'app_localizations.dart';
export 'cache.dart';
export 'color.dart';

View File

@@ -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

View File

@@ -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>();

View File

@@ -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,
@@ -252,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>
@@ -298,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',
@@ -309,7 +305,10 @@ class Windows {
'%s',
'/F',
].join(' ');
return runas('schtasks', commandLine.replaceFirst('%s', taskPath));
return runas(
'schtasks',
commandLine.replaceFirst('%s', taskPath),
);
}
}
@@ -338,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;
}
@@ -368,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 = [];
@@ -403,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',
],
);
}
}

View File

@@ -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';
@@ -527,9 +528,11 @@ class AppController {
Future<void> init() async {
FlutterError.onError = (details) {
commonPrint.log(
'exception: ${details.exception} stack: ${details.stack}',
);
if (kDebugMode) {
commonPrint.log(
'exception: ${details.exception} stack: ${details.stack}',
);
}
};
updateTray(true);
autoUpdateProfiles();
@@ -542,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;
@@ -631,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;
}

View File

@@ -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));

View File

@@ -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() {

View File

@@ -100,7 +100,6 @@ class CoreService extends CoreHandlerInterface {
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;

View File

@@ -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"),

View File

@@ -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("プロキシ"),

View File

@@ -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("Прокси"),

View File

@@ -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("代理"),

View File

@@ -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> {

View File

@@ -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';
@@ -24,6 +25,7 @@ Future<void> _service(List<String> flags) async {
WidgetsFlutterBinding.ensureInitialized();
await globalState.init();
await coreController.preload();
await service?.syncAndroidState(globalState.getAndroidState());
tile?.addListener(
_TileListenerWithService(
onStop: () async {

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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,46 +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 RoundedRectangleBorder(
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(
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),
),
],
),
),
);

View File

@@ -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();
}

View File

@@ -272,13 +272,17 @@ class AppIcon extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: context.colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(14),
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,
),
),
);
}

View File

@@ -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) =>

View File

@@ -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,

View File

@@ -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,
));
}

View File

@@ -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(

View File

@@ -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

View File

@@ -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,

View File

@@ -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;
}
},
),
),
);

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,
);
}

View File

@@ -183,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(
@@ -250,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,
@@ -327,7 +321,6 @@ class GlobalState {
currentProfileName: config.currentProfile?.label ?? '',
onlyStatisticsProxy: config.appSetting.onlyStatisticsProxy,
stopText: appLocalizations.stop,
crashlytics: config.appSetting.crashlytics,
);
}

View File

@@ -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(

View File

@@ -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,

View File

@@ -6,7 +6,6 @@ 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';
@@ -38,9 +37,7 @@ class DeveloperView extends ConsumerWidget {
ListItem(
title: Text(appLocalizations.crashTest),
onTap: () {
if (kDebugMode) {
coreController.crash();
}
coreController.crash();
},
),
ListItem(

View File

@@ -29,10 +29,10 @@ class ResourcesView extends StatelessWidget {
@override
Widget build(BuildContext context) {
const geoItems = <GeoItem>[
GeoItem(label: 'GEOIP', fileName: GEOIP, key: 'geoip'),
GeoItem(label: 'GEOSITE', fileName: GEOSITE, key: 'geosite'),
GeoItem(label: 'MMDB', fileName: MMDB, key: 'mmdb'),
GeoItem(label: 'ASN', fileName: ASN, key: 'asn'),
GeoItem(label: 'GeoIp', fileName: geoIpFileName, key: 'geoip'),
GeoItem(label: 'GeoSite', fileName: geoSiteFileName, key: 'geosite'),
GeoItem(label: 'MMDB', fileName: mmdbFileName, key: 'mmdb'),
GeoItem(label: 'ASN', fileName: asnFileName, key: 'asn'),
];
return CommonScaffold(

View File

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

View File

@@ -17,14 +17,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.6.0"
analyzer_buffer:
dependency: transitive
description:
name: analyzer_buffer
sha256: f7833bee67c03c37241c67f8741b17cc501b69d9758df7a5a4a13ed6c947be43
url: "https://pub.dev"
source: hosted
version: "0.1.10"
analyzer_plugin:
dependency: transitive
description:
@@ -109,10 +101,10 @@ packages:
dependency: transitive
description:
name: build
sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d
sha256: "6439a9c71a4e6eca8d9490c1b380a25b02675aa688137dfbe66d2062884a23ac"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.0.2"
build_config:
dependency: transitive
description:
@@ -133,26 +125,26 @@ packages:
dependency: transitive
description:
name: build_resolvers
sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46
sha256: "2b21a125d66a86b9511cc3fb6c668c42e9a1185083922bf60e46d483a81a9712"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.2"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30
sha256: fd3c09f4bbff7fa6e8d8ef688a0b2e8a6384e6483a25af0dac75fef362bcfe6f
url: "https://pub.dev"
source: hosted
version: "2.7.1"
version: "2.7.0"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b"
sha256: ab27e46c8aa233e610cf6084ee6d8a22c6f873a0a9929241d8855b7a72978ae7
url: "https://pub.dev"
source: hosted
version: "9.3.1"
version: "9.3.0"
built_collection:
dependency: transitive
description:
@@ -165,10 +157,10 @@ packages:
dependency: transitive
description:
name: built_value
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb
url: "https://pub.dev"
source: hosted
version: "8.12.0"
version: "8.11.1"
characters:
dependency: transitive
description:
@@ -221,10 +213,10 @@ packages:
dependency: transitive
description:
name: code_builder
sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243"
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
url: "https://pub.dev"
source: hosted
version: "4.11.0"
version: "4.10.1"
collection:
dependency: "direct main"
description:
@@ -421,10 +413,10 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f
sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22
url: "https://pub.dev"
source: hosted
version: "10.3.3"
version: "10.3.2"
file_selector_linux:
dependency: transitive
description:
@@ -504,26 +496,26 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31
sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab"
url: "https://pub.dev"
source: hosted
version: "2.0.30"
version: "2.0.29"
flutter_riverpod:
dependency: "direct main"
description:
name: flutter_riverpod
sha256: ca2480512a8e840291325249f4857e363ffa5d1b77b132e189c9313a9d9fb9e0
sha256: "56c3cc75d04c34fc824ce1d52ec9076c431e3c47ed55fd8cbf9756ca6d50479e"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.0-dev.17"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678
sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.2.0"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -538,10 +530,10 @@ packages:
dependency: "direct dev"
description:
name: freezed
sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77"
sha256: da32f8ba8cfcd4ec71d9decc8cbf28bd2c31b5283d9887eb51eb4a0659d8110c
url: "https://pub.dev"
source: hosted
version: "3.2.3"
version: "3.2.0"
freezed_annotation:
dependency: "direct main"
description:
@@ -666,10 +658,10 @@ packages:
dependency: transitive
description:
name: image_picker_android
sha256: a45bef33deb24839a51fb85a4d9e504ead2b1ad1c4779d02d09bf6a8857cdd52
sha256: e83b2b05141469c5e19d77e1dfa11096b6b1567d09065b2265d7c6904560050c
url: "https://pub.dev"
source: hosted
version: "0.8.13+2"
version: "0.8.13"
image_picker_for_web:
dependency: transitive
description:
@@ -770,10 +762,10 @@ packages:
dependency: "direct dev"
description:
name: json_serializable
sha256: "33a040668b31b320aafa4822b7b1e177e163fc3c1e835c6750319d4ab23aa6fe"
sha256: "3f2913b7c2430afe8ac5afe6fb15c1de4a60af4f630625e6e238f80ba4b80cbd"
url: "https://pub.dev"
source: hosted
version: "6.11.1"
version: "6.11.0"
launch_at_startup:
dependency: "direct main"
description:
@@ -786,10 +778,10 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
version: "11.0.1"
leak_tracker_flutter_testing:
dependency: transitive
description:
@@ -946,10 +938,10 @@ packages:
dependency: transitive
description:
name: path_provider_android
sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db"
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev"
source: hosted
version: "2.2.18"
version: "2.2.17"
path_provider_foundation:
dependency: transitive
description:
@@ -986,10 +978,10 @@ packages:
dependency: transitive
description:
name: petitparser
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.dev"
source: hosted
version: "7.0.1"
version: "6.1.0"
platform:
dependency: transitive
description:
@@ -1066,42 +1058,42 @@ packages:
dependency: "direct main"
description:
name: riverpod
sha256: "135723ec44dfba141bc4696224048a408336e794228a0117439e7ad0a8be6d05"
sha256: "82507cfb140c044f12e929c054dcdfc478359f473bcd2976af26908318e91b8e"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.0-dev.17"
riverpod_analyzer_utils:
dependency: transitive
description:
name: riverpod_analyzer_utils
sha256: c971e50f678d55c87d9e3b5015df7fcaadb2302a84ea101247cf99387b4554fe
sha256: ce9dfa8dccc5029535a09d1582681c894c8853613aaca5869d372348cf432114
url: "https://pub.dev"
source: hosted
version: "1.0.0-dev.6"
version: "1.0.0-dev.4"
riverpod_annotation:
dependency: "direct main"
description:
name: riverpod_annotation
sha256: "1f9c1bc10b13f68ebc83ab391e77a865ff796880461d2a60c5ce301e1fb65f51"
sha256: d4449ce911fe1e211a2f6fbc110c907859b01419f720f604791fe8583a06620e
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.0-dev.17"
riverpod_generator:
dependency: "direct dev"
description:
name: riverpod_generator
sha256: "178d6bfa6de66d7db97db2466e5fad2da91a678ab376a7309235eec731755046"
sha256: "16569af989111e5087da6cfd71660eb0dbcfb87e5395cfa5181ce089ff4f7729"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.0-dev.17"
riverpod_lint:
dependency: "direct dev"
description:
name: riverpod_lint
sha256: "89bce8d40bed52aa89b43ef45cfe473a9c7736629d61fc659c95e4e9f4d571a6"
sha256: f9403db89a399bbdf50a56883cf0eaa42b69e3fef005cd967ff5072d7e90b838
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.0-dev.17"
rxdart:
dependency: transitive
description:
@@ -1162,10 +1154,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74
sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e"
url: "https://pub.dev"
source: hosted
version: "2.4.12"
version: "2.4.11"
shared_preferences_foundation:
dependency: transitive
description:
@@ -1263,10 +1255,10 @@ packages:
dependency: transitive
description:
name: source_helper
sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca
url: "https://pub.dev"
source: hosted
version: "1.3.8"
version: "1.3.7"
source_map_stack_trace:
dependency: transitive
description:
@@ -1471,10 +1463,10 @@ packages:
dependency: transitive
description:
name: url_launcher_android
sha256: "07cffecb7d68cbc6437cd803d5f11a86fe06736735c3dfe46ff73bcb0f958eed"
sha256: "0aedad096a85b49df2e4725fa32118f9fa580f3b14af7a2d2221896a02cd5656"
url: "https://pub.dev"
source: hosted
version: "6.3.21"
version: "6.3.17"
url_launcher_ios:
dependency: transitive
description:
@@ -1551,10 +1543,10 @@ packages:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
sha256: ca81fdfaf62a5ab45d7296614aea108d2c7d0efca8393e96174bf4d51e6725b0
url: "https://pub.dev"
source: hosted
version: "1.1.19"
version: "1.1.18"
vector_math:
dependency: transitive
description:
@@ -1575,10 +1567,10 @@ packages:
dependency: transitive
description:
name: watcher
sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c"
sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
version: "1.1.2"
web:
dependency: transitive
description:
@@ -1662,10 +1654,10 @@ packages:
dependency: transitive
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.6.1"
version: "6.5.0"
yaml:
dependency: transitive
description:
@@ -1676,4 +1668,4 @@ packages:
version: "3.1.3"
sdks:
dart: ">=3.9.0 <4.0.0"
flutter: ">=3.35.0"
flutter: ">=3.29.0"

View File

@@ -1,7 +1,7 @@
name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none'
version: 0.8.88+2025091701
version: 0.8.88+2025090501
environment:
sdk: '>=3.8.0 <4.0.0'
@@ -22,17 +22,17 @@ dependencies:
path: plugins/window_ext
launch_at_startup: ^0.5.1
json_annotation: ^4.9.0
file_picker: ^10.3.3
mobile_scanner: ^7.0.1
file_picker: ^10.3.1
mobile_scanner: ^7.0.0
app_links: ^6.4.0
win32_registry: ^2.0.0
tray_manager: ^0.5.0
collection: ^1.19.1
collection: ^1.18.0
animations: ^2.0.11
package_info_plus: ^8.0.0
url_launcher: ^6.3.2
url_launcher: ^6.2.6
freezed_annotation: ^3.1.0
image_picker: ^1.2.0
image_picker: ^1.1.2
ffi: ^2.1.4
webdav_client: ^1.2.2
dio: ^5.8.0+1
@@ -50,9 +50,9 @@ dependencies:
connectivity_plus: ^6.1.0
screen_retriever: ^0.2.0
defer_pointer: ^0.0.2
flutter_riverpod: ^3.0.0
riverpod_annotation: ^3.0.0
riverpod: ^3.0.0
flutter_riverpod: ^3.0.0-dev.17
riverpod_annotation: ^3.0.0-dev.17
riverpod: ^3.0.0-dev.17
material_color_utilities: ^0.11.1
flutter_js:
git:
@@ -69,8 +69,8 @@ dev_dependencies:
build_runner: ^2.7.0
args: ^2.4.2
freezed: ^3.2.0
riverpod_generator: ^3.0.0
riverpod_lint: ^3.0.0
riverpod_generator: ^3.0.0-dev.17
riverpod_lint: ^3.0.0-dev.17
flutter:
uses-material-design: true