Optimize android vpn performance

Add custom primary color and color scheme

Add linux nad windows arm release

Optimize requests and logs page
This commit is contained in:
chen08209
2025-04-09 16:46:14 +08:00
parent a77b3a35e8
commit c9cd80bcb3
100 changed files with 3081 additions and 997 deletions

View File

@@ -27,29 +27,27 @@ jobs:
- platform: macos - platform: macos
os: macos-latest os: macos-latest
arch: arm64 arch: arm64
- platform: windows
os: windows-11-arm
arch: arm64
- platform: linux
os: ubuntu-24.04-arm
arch: arm64
steps: steps:
- name: Setup rust
if: startsWith(matrix.os, 'windows-11-arm')
run: |
Invoke-WebRequest -Uri "https://win.rustup.rs/aarch64" -OutFile rustup-init.exe
.\rustup-init.exe -y --default-toolchain stable
$cargoPath = "$env:USERPROFILE\.cargo\bin"
Add-Content $env:GITHUB_PATH $cargoPath
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Setup JAVA
if: startsWith(matrix.platform,'android')
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Setup NDK
if: startsWith(matrix.platform,'android')
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r26b
add-to-path: true
link-to-sdk: true
- name: Setup Android Signing - name: Setup Android Signing
if: startsWith(matrix.platform,'android') if: startsWith(matrix.platform,'android')
run: | run: |
@@ -58,18 +56,17 @@ jobs:
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: 'stable' go-version: '1.24.0'
cache-dependency-path: | cache-dependency-path: |
core/go.sum core/go.sum
- name: Setup Flutter - name: Setup Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
channel: stable channel: ${{ (startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')) && 'master' || 'stable' }}
cache: true cache: true
- name: Get Flutter Dependency - name: Get Flutter Dependency

4
.gitmodules vendored
View File

@@ -6,9 +6,5 @@
path = plugins/flutter_distributor path = plugins/flutter_distributor
url = git@github.com:chen08209/flutter_distributor.git url = git@github.com:chen08209/flutter_distributor.git
branch = FlClash branch = FlClash
[submodule "plugins/tray_manager"]
path = plugins/tray_manager
url = git@github.com:chen08209/tray_manager.git
branch = main

10
Makefile Normal file
View File

@@ -0,0 +1,10 @@
android_arm64:
dart ./setup.dart android --arch arm64
macos_arm64:
dart ./setup.dart macos --arch arm64
android_app:
dart ./setup.dart android
android_arm64_core:
dart ./setup.dart android --arch arm64 --out core
macos_arm64_core:
dart ./setup.dart macos --arch arm64 --out core

View File

@@ -1,5 +1,3 @@
import com.android.build.gradle.tasks.MergeSourceSetFolders
plugins { plugins {
id "com.android.application" id "com.android.application"
id "kotlin-android" id "kotlin-android"
@@ -33,8 +31,8 @@ def isRelease = defStoreFile.exists() && defStorePassword != null && defKeyAlias
android { android {
namespace "com.follow.clash" namespace "com.follow.clash"
compileSdkVersion 35 compileSdk 35
ndkVersion "27.1.12297006" ndkVersion = "28.0.13004108"
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_17
@@ -48,6 +46,7 @@ android {
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
} }
signingConfigs { signingConfigs {
if (isRelease) { if (isRelease) {
release { release {
@@ -84,31 +83,15 @@ android {
} }
} }
tasks.register('copyNativeLibs', Copy) {
delete('src/main/jniLibs')
from('../../libclash/android')
into('src/main/jniLibs')
}
tasks.withType(MergeSourceSetFolders).configureEach {
dependsOn copyNativeLibs
}
flutter { flutter {
source '../..' source '../..'
} }
dependencies { dependencies {
implementation project(":core")
implementation 'androidx.core:core-splashscreen:1.0.1' implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'com.google.code.gson:gson:2.10' implementation 'com.google.code.gson:gson:2.10.1'
implementation("com.android.tools.smali:smali-dexlib2:3.0.7") { implementation("com.android.tools.smali:smali-dexlib2:3.0.9") {
exclude group: "com.google.guava", module: "guava" exclude group: "com.google.guava", module: "guava"
} }
} }
afterEvaluate {
assembleDebug.dependsOn copyNativeLibs
assembleRelease.dependsOn copyNativeLibs
}

View File

@@ -1,13 +1,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- The INTERNET permission is required for development. Specifically, <!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<application android:label="FlClash Debug" tools:replace="android:label"> <application
android:icon="@mipmap/ic_launcher"
android:label="FlClash Debug"
tools:replace="android:label">
<service <service
android:name=".services.FlClashTileService" android:name=".services.FlClashTileService"
android:label="FlClash Debug" android:label="FlClash Debug"
tools:replace="android:label"> tools:replace="android:label"
</service> tools:targetApi="24" />
</application> </application>
</manifest> </manifest>

View File

@@ -1,5 +1,6 @@
package com.follow.clash package com.follow.clash
import com.follow.clash.core.Core
import com.follow.clash.plugins.AppPlugin import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ServicePlugin import com.follow.clash.plugins.ServicePlugin
import com.follow.clash.plugins.TilePlugin import com.follow.clash.plugins.TilePlugin

View File

@@ -52,7 +52,6 @@ data object ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
} }
private fun handleDestroy() { private fun handleDestroy() {
GlobalState.getCurrentVPNPlugin()?.handleStop()
GlobalState.destroyServiceEngine() GlobalState.destroyServiceEngine()
} }
} }

View File

@@ -14,10 +14,9 @@ import androidx.core.content.getSystemService
import com.follow.clash.FlClashApplication import com.follow.clash.FlClashApplication
import com.follow.clash.GlobalState import com.follow.clash.GlobalState
import com.follow.clash.RunState import com.follow.clash.RunState
import com.follow.clash.core.Core
import com.follow.clash.extensions.awaitResult import com.follow.clash.extensions.awaitResult
import com.follow.clash.extensions.getProtocol
import com.follow.clash.extensions.resolveDns import com.follow.clash.extensions.resolveDns
import com.follow.clash.models.Process
import com.follow.clash.models.StartForegroundParams import com.follow.clash.models.StartForegroundParams
import com.follow.clash.models.VpnOptions import com.follow.clash.models.VpnOptions
import com.follow.clash.services.BaseServiceInterface import com.follow.clash.services.BaseServiceInterface
@@ -40,10 +39,12 @@ import kotlin.concurrent.withLock
data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler { data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private lateinit var flutterMethodChannel: MethodChannel private lateinit var flutterMethodChannel: MethodChannel
private var flClashService: BaseServiceInterface? = null private var flClashService: BaseServiceInterface? = null
private lateinit var options: VpnOptions private var options: VpnOptions? = null
private var isBind: Boolean = false
private lateinit var scope: CoroutineScope private lateinit var scope: CoroutineScope
private var lastStartForegroundParams: StartForegroundParams? = null private var lastStartForegroundParams: StartForegroundParams? = null
private var timerJob: Job? = null private var timerJob: Job? = null
private val uidPageNameMap = mutableMapOf<Int, String>()
private val connectivity by lazy { private val connectivity by lazy {
FlClashApplication.getAppContext().getSystemService<ConnectivityManager>() FlClashApplication.getAppContext().getSystemService<ConnectivityManager>()
@@ -51,6 +52,7 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private val connection = object : ServiceConnection { private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) { override fun onServiceConnected(className: ComponentName, service: IBinder) {
isBind = true
flClashService = when (service) { flClashService = when (service) {
is FlClashVpnService.LocalBinder -> service.getService() is FlClashVpnService.LocalBinder -> service.getService()
is FlClashService.LocalBinder -> service.getService() is FlClashService.LocalBinder -> service.getService()
@@ -60,6 +62,7 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
} }
override fun onServiceDisconnected(arg: ComponentName) { override fun onServiceDisconnected(arg: ComponentName) {
isBind = false
flClashService = null flClashService = null
} }
} }
@@ -90,62 +93,6 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
result.success(true) result.success(true)
} }
"setProtect" -> {
val fd = call.argument<Int>("fd")
if (fd != null && flClashService is FlClashVpnService) {
try {
(flClashService as FlClashVpnService).protect(fd)
result.success(true)
} catch (e: RuntimeException) {
result.success(false)
}
} else {
result.success(false)
}
}
"resolverProcess" -> {
val data = call.argument<String>("data")
val process = if (data != null) Gson().fromJson(
data, Process::class.java
) else null
val metadata = process?.metadata
if (metadata == null) {
result.success(null)
return
}
val protocol = metadata.getProtocol()
if (protocol == null) {
result.success(null)
return
}
scope.launch {
withContext(Dispatchers.Default) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
result.success(null)
return@withContext
}
val src = InetSocketAddress(metadata.sourceIP, metadata.sourcePort)
val dst = InetSocketAddress(
metadata.destinationIP.ifEmpty { metadata.host },
metadata.destinationPort
)
val uid = try {
connectivity?.getConnectionOwnerUid(protocol, src, dst)
} catch (_: Exception) {
null
}
if (uid == null || uid == -1) {
result.success(null)
return@withContext
}
val packages =
FlClashApplication.getAppContext().packageManager?.getPackagesForUid(uid)
result.success(packages?.first())
}
}
}
else -> { else -> {
result.notImplemented() result.notImplemented()
} }
@@ -153,6 +100,9 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
} }
fun handleStart(options: VpnOptions): Boolean { fun handleStart(options: VpnOptions): Boolean {
if (options.enable != this.options?.enable) {
this.flClashService = null
}
this.options = options this.options = options
when (options.enable) { when (options.enable) {
true -> handleStartVpn() true -> handleStartVpn()
@@ -162,10 +112,9 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
} }
private fun handleStartVpn() { private fun handleStartVpn() {
GlobalState.getCurrentAppPlugin() GlobalState.getCurrentAppPlugin()?.requestVpnPermission {
?.requestVpnPermission { handleStartService()
handleStartService() }
}
} }
fun requestGc() { fun requestGc() {
@@ -235,6 +184,7 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
} }
private fun startForegroundJob() { private fun startForegroundJob() {
stopForegroundJob()
timerJob = CoroutineScope(Dispatchers.Main).launch { timerJob = CoroutineScope(Dispatchers.Main).launch {
while (isActive) { while (isActive) {
startForeground() startForeground()
@@ -256,26 +206,58 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
GlobalState.runLock.withLock { GlobalState.runLock.withLock {
if (GlobalState.runState.value == RunState.START) return if (GlobalState.runState.value == RunState.START) return
GlobalState.runState.value = RunState.START GlobalState.runState.value = RunState.START
val fd = flClashService?.start(options) val fd = flClashService?.start(options!!)
flutterMethodChannel.invokeMethod( Core.startTun(
"started", fd fd = fd ?: 0,
protect = this::protect,
resolverProcess = this::resolverProcess,
) )
startForegroundJob(); startForegroundJob()
} }
} }
private fun protect(fd: Int): Boolean {
return (flClashService as? FlClashVpnService)?.protect(fd) == true
}
private fun resolverProcess(
protocol: Int,
source: InetSocketAddress,
target: InetSocketAddress,
uid: Int,
): String {
val nextUid = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
connectivity?.getConnectionOwnerUid(protocol, source, target) ?: -1
} else {
uid
}
if (nextUid == -1) {
return ""
}
if (!uidPageNameMap.containsKey(nextUid)) {
uidPageNameMap[nextUid] =
FlClashApplication.getAppContext().packageManager?.getPackagesForUid(nextUid)
?.first() ?: ""
}
return uidPageNameMap[nextUid] ?: ""
}
fun handleStop() { fun handleStop() {
GlobalState.runLock.withLock { GlobalState.runLock.withLock {
if (GlobalState.runState.value == RunState.STOP) return if (GlobalState.runState.value == RunState.STOP) return
GlobalState.runState.value = RunState.STOP GlobalState.runState.value = RunState.STOP
stopForegroundJob() stopForegroundJob()
Core.stopTun()
flClashService?.stop() flClashService?.stop()
GlobalState.handleTryDestroy() GlobalState.handleTryDestroy()
} }
} }
private fun bindService() { private fun bindService() {
val intent = when (options.enable) { if (isBind) {
FlClashApplication.getAppContext().unbindService(connection)
}
val intent = when (options?.enable == true) {
true -> Intent(FlClashApplication.getAppContext(), FlClashVpnService::class.java) true -> Intent(FlClashApplication.getAppContext(), FlClashVpnService::class.java)
false -> Intent(FlClashApplication.getAppContext(), FlClashService::class.java) false -> Intent(FlClashApplication.getAppContext(), FlClashService::class.java)
} }

View File

@@ -24,6 +24,7 @@ subprojects {
} }
subprojects { subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
project.evaluationDependsOn(':core')
} }
tasks.register("clean", Delete) { tasks.register("clean", Delete) {

1
android/core/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,65 @@
import com.android.build.gradle.tasks.MergeSourceSetFolders
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.follow.clash.core"
compileSdk = 35
ndkVersion = "28.0.13004108"
defaultConfig {
minSdk = 21
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
sourceSets {
getByName("main") {
jniLibs.srcDirs("src/main/jniLibs")
}
}
externalNativeBuild {
cmake {
path("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
}
val copyNativeLibs by tasks.register<Copy>("copyNativeLibs") {
doFirst {
delete("src/main/jniLibs")
}
from("../../libclash/android")
into("src/main/jniLibs")
}
afterEvaluate {
tasks.named("preBuild") {
dependsOn(copyNativeLibs)
}
}
dependencies {
implementation("androidx.core:core-ktx:1.16.0")
}

View File

21
android/core/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@@ -0,0 +1,45 @@
cmake_minimum_required(VERSION 3.22.1)
project("core")
message("CMAKE_SOURCE_DIR ${CMAKE_SOURCE_DIR}")
if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
add_compile_options(-O3)
add_compile_options(-flto)
add_compile_options(-g0)
add_compile_options(-ffunction-sections -fdata-sections)
add_compile_options(-fno-exceptions -fno-rtti)
add_link_options(
-flto
-Wl,--gc-sections
-Wl,--strip-all
-Wl,--exclude-libs=ALL
)
endif ()
set(LIB_CLASH_PATH "${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libclash.so")
message("LIB_CLASH_PATH ${LIB_CLASH_PATH}")
if (EXISTS ${LIB_CLASH_PATH})
message("Found libclash.so for ABI ${ANDROID_ABI}")
add_compile_definitions(LIBCLASH)
include_directories(${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
link_directories(${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
add_library(${CMAKE_PROJECT_NAME} SHARED
jni_helper.cpp
core.cpp)
target_link_libraries(${CMAKE_PROJECT_NAME}
clash)
else ()
message("Not found libclash.so for ABI ${ANDROID_ABI}")
add_library(${CMAKE_PROJECT_NAME} SHARED
jni_helper.cpp
core.cpp)
target_link_libraries(${CMAKE_PROJECT_NAME})
endif ()

View File

@@ -0,0 +1,75 @@
#include <jni.h>
#ifdef LIBCLASH
#include <jni.h>
#include <string>
#include "jni_helper.h"
#include "libclash.h"
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb) {
auto interface = new_global(cb);
startTUN(fd, interface);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_stopTun(JNIEnv *env, jobject thiz) {
stopTun();
}
static jmethodID m_tun_interface_protect;
static jmethodID m_tun_interface_resolve_process;
static void release_jni_object_impl(void *obj) {
ATTACH_JNI();
del_global((jobject) obj);
}
static void call_tun_interface_protect_impl(void *tun_interface, int fd) {
ATTACH_JNI();
env->CallVoidMethod((jobject) tun_interface,
(jmethodID) m_tun_interface_protect,
(jint) fd);
}
static const char*
call_tun_interface_resolve_process_impl(void *tun_interface, int protocol,
const char *source,
const char *target,
int uid) {
ATTACH_JNI();
jstring packageName = (jstring)env->CallObjectMethod((jobject) tun_interface,
(jmethodID) m_tun_interface_resolve_process,
(jint) protocol,
(jstring) new_string(source),
(jstring) new_string(target),
(jint) uid);
return get_string(packageName);
}
extern "C"
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
initialize_jni(vm, env);
jclass c_tun_interface = find_class("com/follow/clash/core/TunInterface");
m_tun_interface_protect = find_method(c_tun_interface, "protect", "(I)V");
m_tun_interface_resolve_process = find_method(c_tun_interface, "resolverProcess",
"(ILjava/lang/String;Ljava/lang/String;I)Ljava/lang/String;");
registerCallbacks(&call_tun_interface_protect_impl,
&call_tun_interface_resolve_process_impl,
&release_jni_object_impl);
return JNI_VERSION_1_6;
}
#endif

View File

@@ -0,0 +1,70 @@
#include "jni_helper.h"
#include <malloc.h>
#include <cstring>
static JavaVM *global_vm;
static jclass c_string;
static jmethodID m_new_string;
static jmethodID m_get_bytes;
void initialize_jni(JavaVM *vm, JNIEnv *env) {
global_vm = vm;
c_string = (jclass) new_global(find_class("java/lang/String"));
m_new_string = find_method(c_string, "<init>", "([B)V");
m_get_bytes = find_method(c_string, "getBytes", "()[B");
}
JavaVM *global_java_vm() {
return global_vm;
}
char *jni_get_string(JNIEnv *env, jstring str) {
auto array = (jbyteArray) env->CallObjectMethod(str, m_get_bytes);
int length = env->GetArrayLength(array);
char *content = (char *) malloc(length + 1);
env->GetByteArrayRegion(array, 0, length, (jbyte *) content);
content[length] = 0;
return content;
}
jstring jni_new_string(JNIEnv *env, const char *str) {
auto length = (int) strlen(str);
jbyteArray array = env->NewByteArray(length);
env->SetByteArrayRegion(array, 0, length, (const jbyte *) str);
return (jstring) env->NewObject(c_string, m_new_string, array);
}
int jni_catch_exception(JNIEnv *env) {
int result = env->ExceptionCheck();
if (result) {
env->ExceptionDescribe();
env->ExceptionClear();
}
return result;
}
void jni_attach_thread(struct scoped_jni *jni) {
JavaVM *vm = global_java_vm();
if (vm->GetEnv((void **) &jni->env, JNI_VERSION_1_6) == JNI_OK) {
jni->require_release = 0;
return;
}
if (vm->AttachCurrentThread(&jni->env, nullptr) != JNI_OK) {
abort();
}
jni->require_release = 1;
}
void jni_detach_thread(struct scoped_jni *jni) {
JavaVM *vm = global_java_vm();
if (jni->require_release) {
vm->DetachCurrentThread();
}
}
void release_string(char **str) {
free(*str);
}

View File

@@ -0,0 +1,39 @@
#pragma once
#include <jni.h>
#include <cstdint>
#include <cstdlib>
#include <malloc.h>
struct scoped_jni {
JNIEnv *env;
int require_release;
};
extern void initialize_jni(JavaVM *vm, JNIEnv *env);
extern jstring jni_new_string(JNIEnv *env, const char *str);
extern char *jni_get_string(JNIEnv *env, jstring str);
extern int jni_catch_exception(JNIEnv *env);
extern void jni_attach_thread(struct scoped_jni *jni);
extern void jni_detach_thread(struct scoped_jni *env);
extern void release_string(char **str);
#define ATTACH_JNI() __attribute__((unused, cleanup(jni_detach_thread))) \
struct scoped_jni _jni; \
jni_attach_thread(&_jni); \
JNIEnv *env = _jni.env
#define scoped_string __attribute__((cleanup(release_string))) char*
#define find_class(name) env->FindClass(name)
#define find_method(cls, name, signature) env->GetMethodID(cls, name, signature)
#define new_global(obj) env->NewGlobalRef(obj)
#define del_global(obj) env->DeleteGlobalRef(obj)
#define get_string(jstr) jni_get_string(env, jstr)
#define new_string(cstr) jni_new_string(env, cstr)

View File

@@ -0,0 +1,50 @@
package com.follow.clash.core
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.URL
data object Core {
private external fun startTun(
fd: Int,
cb: TunInterface
)
private fun parseInetSocketAddress(address: String): InetSocketAddress {
val url = URL("https://$address")
return InetSocketAddress(InetAddress.getByName(url.host), url.port)
}
fun startTun(
fd: Int,
protect: (Int) -> Boolean,
resolverProcess: (protocol: Int, source: InetSocketAddress, target: InetSocketAddress, uid: Int) -> String
) {
startTun(fd, object : TunInterface {
override fun protect(fd: Int) {
protect(fd)
}
override fun resolverProcess(
protocol: Int,
source: String,
target: String,
uid: Int
): String {
return resolverProcess(
protocol,
parseInetSocketAddress(source),
parseInetSocketAddress(target),
uid,
)
}
});
}
external fun stopTun()
init {
System.loadLibrary("core")
}
}

View File

@@ -0,0 +1,9 @@
package com.follow.clash.core
import androidx.annotation.Keep
@Keep
interface TunInterface {
fun protect(fd: Int)
fun resolverProcess(protocol: Int, source: String, target: String, uid: Int): String
}

View File

@@ -24,3 +24,4 @@ plugins {
} }
include ":app" include ":app"
include ':core'

View File

@@ -35,8 +35,8 @@ func (action Action) getResult(data interface{}) []byte {
func handleAction(action *Action, result func(data interface{})) { func handleAction(action *Action, result func(data interface{})) {
switch action.Method { switch action.Method {
case initClashMethod: case initClashMethod:
data := action.Data.(string) paramsString := action.Data.(string)
result(handleInitClash(data)) result(handleInitClash(paramsString))
return return
case getIsInitMethod: case getIsInitMethod:
result(handleGetIsInit()) result(handleGetIsInit())

77
core/android_bride.go Normal file
View File

@@ -0,0 +1,77 @@
//go:build android && cgo
package main
/*
#include <stdlib.h>
typedef void (*release_object_func)(void *obj);
typedef void (*protect_func)(void *tun_interface, int fd);
typedef const char* (*resolve_process_func)(void *tun_interface, int protocol, const char *source, const char *target, int uid);
static void protect(protect_func fn, void *tun_interface, int fd) {
if (fn) {
fn(tun_interface, fd);
}
}
static const char* resolve_process(resolve_process_func fn, void *tun_interface, int protocol, const char *source, const char *target, int uid) {
if (fn) {
return fn(tun_interface, protocol, source, target, uid);
}
return "";
}
static void release_object(release_object_func fn, void *obj) {
if (fn) {
return fn(obj);
}
}
*/
import "C"
import (
"unsafe"
)
var (
globalCallbacks struct {
releaseObjectFunc C.release_object_func
protectFunc C.protect_func
resolveProcessFunc C.resolve_process_func
}
)
func protect(callback unsafe.Pointer, fd int) {
if globalCallbacks.protectFunc != nil {
C.protect(globalCallbacks.protectFunc, callback, C.int(fd))
}
}
func resolveProcess(callback unsafe.Pointer, protocol int, source, target string, uid int) string {
if globalCallbacks.resolveProcessFunc == nil {
return ""
}
s := C.CString(source)
defer C.free(unsafe.Pointer(s))
t := C.CString(target)
defer C.free(unsafe.Pointer(t))
res := C.resolve_process(globalCallbacks.resolveProcessFunc, callback, C.int(protocol), s, t, C.int(uid))
defer C.free(unsafe.Pointer(res))
return C.GoString(res)
}
func releaseObject(callback unsafe.Pointer) {
if globalCallbacks.releaseObjectFunc == nil {
return
}
C.release_object(globalCallbacks.releaseObjectFunc, callback)
}
//export registerCallbacks
func registerCallbacks(markSocketFunc C.protect_func, resolveProcessFunc C.resolve_process_func, releaseObjectFunc C.release_object_func) {
globalCallbacks.protectFunc = markSocketFunc
globalCallbacks.resolveProcessFunc = resolveProcessFunc
globalCallbacks.releaseObjectFunc = releaseObjectFunc
}

View File

@@ -41,6 +41,7 @@ func splitByMultipleSeparators(s string) interface{} {
} }
var ( var (
version = 0
isRunning = false isRunning = false
runLock sync.Mutex runLock sync.Mutex
ips = []string{"ipwho.is", "api.ip.sb", "ipapi.co", "ipinfo.io"} ips = []string{"ipwho.is", "api.ip.sb", "ipapi.co", "ipinfo.io"}
@@ -274,7 +275,6 @@ func patchConfig() {
dialer.DefaultInterface.Store(general.Interface) dialer.DefaultInterface.Store(general.Interface)
adapter.UnifiedDelay.Store(general.UnifiedDelay) adapter.UnifiedDelay.Store(general.UnifiedDelay)
tunnel.SetMode(general.Mode) tunnel.SetMode(general.Mode)
tunnel.UpdateRules(currentConfig.Rules, currentConfig.SubRules, currentConfig.RuleProviders)
log.SetLevel(general.LogLevel) log.SetLevel(general.LogLevel)
resolver.DisableIPv6 = !general.IPv6 resolver.DisableIPv6 = !general.IPv6

View File

@@ -7,6 +7,11 @@ import (
"time" "time"
) )
type InitParams struct {
HomeDir string `json:"home-dir"`
Version int `json:"version"`
}
type ConfigExtendedParams struct { type ConfigExtendedParams struct {
IsPatch bool `json:"is-patch"` IsPatch bool `json:"is-patch"`
IsCompatible bool `json:"is-compatible"` IsCompatible bool `json:"is-compatible"`
@@ -71,11 +76,7 @@ const (
stopLogMethod Method = "stopLog" stopLogMethod Method = "stopLog"
startListenerMethod Method = "startListener" startListenerMethod Method = "startListener"
stopListenerMethod Method = "stopListener" stopListenerMethod Method = "stopListener"
startTunMethod Method = "startTun"
stopTunMethod Method = "stopTun"
updateDnsMethod Method = "updateDns" updateDnsMethod Method = "updateDns"
setProcessMapMethod Method = "setProcessMap"
setFdMapMethod Method = "setFdMap"
setStateMethod Method = "setState" setStateMethod Method = "setState"
getAndroidVpnOptionsMethod Method = "getAndroidVpnOptions" getAndroidVpnOptionsMethod Method = "getAndroidVpnOptions"
getRunTimeMethod Method = "getRunTime" getRunTimeMethod Method = "getRunTime"
@@ -109,20 +110,3 @@ func (message *Message) Json() (string, error) {
data, err := json.Marshal(message) data, err := json.Marshal(message)
return string(data), err return string(data), err
} }
type InvokeMessage struct {
Type InvokeType `json:"type"`
Data interface{} `json:"data"`
}
type InvokeType string
const (
ProtectInvoke InvokeType = "protect"
ProcessInvoke InvokeType = "process"
)
func (message *InvokeMessage) Json() string {
data, _ := json.Marshal(message)
return string(data)
}

View File

@@ -34,9 +34,15 @@ var (
currentConfig *config.Config currentConfig *config.Config
) )
func handleInitClash(homeDirStr string) bool { func handleInitClash(paramsString string) bool {
var params = InitParams{}
err := json.Unmarshal([]byte(paramsString), &params)
if err != nil {
return false
}
version = params.Version
if !isInit { if !isInit {
constant.SetHomeDir(homeDirStr) constant.SetHomeDir(params.HomeDir)
isInit = true isInit = true
} }
return isInit return isInit

View File

@@ -4,6 +4,7 @@ package main
import "C" import "C"
import ( import (
"context"
bridge "core/dart-bridge" bridge "core/dart-bridge"
"core/platform" "core/platform"
"core/state" "core/state"
@@ -11,121 +12,116 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/process" "github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/dns" "github.com/metacubex/mihomo/dns"
"github.com/metacubex/mihomo/listener/sing_tun" "github.com/metacubex/mihomo/listener/sing_tun"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"golang.org/x/sync/semaphore"
"net"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"syscall" "syscall"
"time" "time"
"unsafe"
) )
type Fd struct { type TunHandler struct {
Id string `json:"id"` listener *sing_tun.Listener
Value int64 `json:"value"` callback unsafe.Pointer
limit *semaphore.Weighted
} }
type Process struct { func (t *TunHandler) close() {
Id string `json:"id"` _ = t.limit.Acquire(context.TODO(), 4)
Metadata *constant.Metadata `json:"metadata"` defer t.limit.Release(4)
} removeTunHook()
if t.listener != nil {
type ProcessMapItem struct { _ = t.listener.Close()
Id string `json:"id"`
Value string `json:"value"`
}
type InvokeManager struct {
invokeMap sync.Map
chanMap map[string]chan struct{}
chanLock sync.Mutex
}
func NewInvokeManager() *InvokeManager {
return &InvokeManager{
chanMap: make(map[string]chan struct{}),
} }
if t.callback != nil {
releaseObject(t.callback)
}
t.callback = nil
t.listener = nil
} }
func (m *InvokeManager) completer(id string, value string) { func (t *TunHandler) handleProtect(fd int) {
m.invokeMap.Store(id, value) _ = t.limit.Acquire(context.Background(), 1)
m.chanLock.Lock() defer t.limit.Release(1)
if ch, ok := m.chanMap[id]; ok {
close(ch) if t.listener == nil {
delete(m.chanMap, id) return
} }
m.chanLock.Unlock()
protect(t.callback, fd)
} }
func (m *InvokeManager) await(id string) string { func (t *TunHandler) handleResolveProcess(source, target net.Addr) string {
m.chanLock.Lock() _ = t.limit.Acquire(context.Background(), 1)
if _, ok := m.chanMap[id]; !ok { defer t.limit.Release(1)
m.chanMap[id] = make(chan struct{})
}
ch := m.chanMap[id]
m.chanLock.Unlock()
timeout := time.After(500 * time.Millisecond) if t.listener == nil {
select {
case <-ch:
res, ok := m.invokeMap.Load(id)
m.invokeMap.Delete(id)
if ok {
return res.(string)
} else {
return ""
}
case <-timeout:
m.completer(id, "")
return "" return ""
} }
var protocol int
uid := -1
switch source.Network() {
case "udp", "udp4", "udp6":
protocol = syscall.IPPROTO_UDP
case "tcp", "tcp4", "tcp6":
protocol = syscall.IPPROTO_TCP
}
if version < 29 {
uid = platform.QuerySocketUidFromProcFs(source, target)
}
return resolveProcess(t.callback, protocol, source.String(), target.String(), uid)
} }
var ( var (
invokePort int64 = -1 tunLock sync.Mutex
tunListener *sing_tun.Listener runTime *time.Time
fdInvokeMap = NewInvokeManager() errBlocked = errors.New("blocked")
processInvokeMap = NewInvokeManager() tunHandler *TunHandler
tunLock sync.Mutex
runTime *time.Time
errBlocked = errors.New("blocked")
) )
func handleStartTun(fd int) string {
handleStopTun()
tunLock.Lock()
defer tunLock.Unlock()
if fd == 0 {
now := time.Now()
runTime = &now
} else {
initSocketHook()
tunListener, _ = t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack)
if tunListener != nil {
log.Infoln("TUN address: %v", tunListener.Address())
}
now := time.Now()
runTime = &now
}
return handleGetRunTime()
}
func handleStopTun() { func handleStopTun() {
tunLock.Lock() tunLock.Lock()
defer tunLock.Unlock() defer tunLock.Unlock()
removeSocketHook()
runTime = nil runTime = nil
if tunListener != nil { if tunHandler != nil {
log.Infoln("TUN close") tunHandler.close()
_ = tunListener.Close()
} }
} }
func handleStartTun(fd int, callback unsafe.Pointer) bool {
handleStopTun()
now := time.Now()
runTime = &now
if fd != 0 {
tunLock.Lock()
defer tunLock.Unlock()
tunHandler = &TunHandler{
callback: callback,
limit: semaphore.NewWeighted(4),
}
initTunHook()
tunListener, _ := t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack)
if tunListener != nil {
log.Infoln("TUN address: %v", tunListener.Address())
} else {
removeTunHook()
return false
}
tunHandler.listener = tunListener
}
return true
}
func handleGetRunTime() string { func handleGetRunTime() string {
if runTime == nil { if runTime == nil {
return "" return ""
@@ -133,83 +129,29 @@ func handleGetRunTime() string {
return strconv.FormatInt(runTime.UnixMilli(), 10) return strconv.FormatInt(runTime.UnixMilli(), 10)
} }
func handleSetProcessMap(params string) { func initTunHook() {
var processMapItem = &ProcessMapItem{}
err := json.Unmarshal([]byte(params), processMapItem)
if err == nil {
processInvokeMap.completer(processMapItem.Id, processMapItem.Value)
}
}
//export attachInvokePort
func attachInvokePort(mPort C.longlong) {
invokePort = int64(mPort)
}
func sendInvokeMessage(message InvokeMessage) {
if invokePort == -1 {
return
}
bridge.SendToPort(invokePort, message.Json())
}
func handleMarkSocket(fd Fd) {
sendInvokeMessage(InvokeMessage{
Type: ProtectInvoke,
Data: fd,
})
}
func handleParseProcess(process Process) {
sendInvokeMessage(InvokeMessage{
Type: ProcessInvoke,
Data: process,
})
}
func handleSetFdMap(id string) {
go func() {
fdInvokeMap.completer(id, "")
}()
}
func initSocketHook() {
dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error { dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error {
if platform.ShouldBlockConnection() { if platform.ShouldBlockConnection() {
return errBlocked return errBlocked
} }
return conn.Control(func(fd uintptr) { return conn.Control(func(fd uintptr) {
fdInt := int64(fd) tunHandler.handleProtect(int(fd))
id := utils.NewUUIDV1().String()
handleMarkSocket(Fd{
Id: id,
Value: fdInt,
})
fdInvokeMap.await(id)
}) })
} }
}
func removeSocketHook() {
dialer.DefaultSocketHook = nil
}
func init() {
process.DefaultPackageNameResolver = func(metadata *constant.Metadata) (string, error) { process.DefaultPackageNameResolver = func(metadata *constant.Metadata) (string, error) {
if metadata == nil { src, dst := metadata.RawSrcAddr, metadata.RawDstAddr
if src == nil || dst == nil {
return "", process.ErrInvalidNetwork return "", process.ErrInvalidNetwork
} }
id := utils.NewUUIDV1().String() return tunHandler.handleResolveProcess(src, dst), nil
handleParseProcess(Process{
Id: id,
Metadata: metadata,
})
return processInvokeMap.await(id), nil
} }
} }
func removeTunHook() {
dialer.DefaultSocketHook = nil
process.DefaultPackageNameResolver = nil
}
func handleGetAndroidVpnOptions() string { func handleGetAndroidVpnOptions() string {
tunLock.Lock() tunLock.Lock()
defer tunLock.Unlock() defer tunLock.Unlock()
@@ -250,16 +192,6 @@ func handleGetCurrentProfileName() string {
func nextHandle(action *Action, result func(data interface{})) bool { func nextHandle(action *Action, result func(data interface{})) bool {
switch action.Method { switch action.Method {
case startTunMethod:
data := action.Data.(string)
var fd int
_ = json.Unmarshal([]byte(data), &fd)
result(handleStartTun(fd))
return true
case stopTunMethod:
handleStopTun()
result(true)
return true
case getAndroidVpnOptionsMethod: case getAndroidVpnOptionsMethod:
result(handleGetAndroidVpnOptions()) result(handleGetAndroidVpnOptions())
return true return true
@@ -268,16 +200,6 @@ func nextHandle(action *Action, result func(data interface{})) bool {
handleUpdateDns(data) handleUpdateDns(data)
result(true) result(true)
return true return true
case setFdMapMethod:
fdId := action.Data.(string)
handleSetFdMap(fdId)
result(true)
return true
case setProcessMapMethod:
data := action.Data.(string)
handleSetProcessMap(data)
result(true)
return true
case getRunTimeMethod: case getRunTimeMethod:
result(handleGetRunTime()) result(handleGetRunTime())
return true return true
@@ -289,13 +211,13 @@ func nextHandle(action *Action, result func(data interface{})) bool {
} }
//export quickStart //export quickStart
func quickStart(dirChar *C.char, paramsChar *C.char, stateParamsChar *C.char, port C.longlong) { func quickStart(initParamsChar *C.char, paramsChar *C.char, stateParamsChar *C.char, port C.longlong) {
i := int64(port) i := int64(port)
dir := C.GoString(dirChar) paramsString := C.GoString(initParamsChar)
bytes := []byte(C.GoString(paramsChar)) bytes := []byte(C.GoString(paramsChar))
stateParams := C.GoString(stateParamsChar) stateParams := C.GoString(stateParamsChar)
go func() { go func() {
res := handleInitClash(dir) res := handleInitClash(paramsString)
if res == false { if res == false {
bridge.SendToPort(i, "init error") bridge.SendToPort(i, "init error")
} }
@@ -305,9 +227,8 @@ func quickStart(dirChar *C.char, paramsChar *C.char, stateParamsChar *C.char, po
} }
//export startTUN //export startTUN
func startTUN(fd C.int) *C.char { func startTUN(fd C.int, callback unsafe.Pointer) bool {
f := int(fd) return handleStartTun(int(fd), callback)
return C.CString(handleStartTun(f))
} }
//export getRunTime //export getRunTime
@@ -320,12 +241,6 @@ func stopTun() {
handleStopTun() handleStopTun()
} }
//export setFdMap
func setFdMap(fdIdChar *C.char) {
fdId := C.GoString(fdIdChar)
handleSetFdMap(fdId)
}
//export getCurrentProfileName //export getCurrentProfileName
func getCurrentProfileName() *C.char { func getCurrentProfileName() *C.char {
return C.CString(handleGetCurrentProfileName()) return C.CString(handleGetCurrentProfileName())
@@ -347,12 +262,3 @@ func updateDns(s *C.char) {
dnsList := C.GoString(s) dnsList := C.GoString(s)
handleUpdateDns(dnsList) handleUpdateDns(dnsList)
} }
//export setProcessMap
func setProcessMap(s *C.char) {
if s == nil {
return
}
paramsString := C.GoString(s)
handleSetProcessMap(paramsString)
}

176
core/platform/procfs.go Normal file
View File

@@ -0,0 +1,176 @@
//go:build linux
// +build linux
package platform
import (
"bufio"
"encoding/binary"
"encoding/hex"
"fmt"
"net"
"os"
"strconv"
"strings"
"unsafe"
)
var netIndexOfLocal = -1
var netIndexOfUid = -1
var nativeEndian binary.ByteOrder
func QuerySocketUidFromProcFs(source, _ net.Addr) int {
if netIndexOfLocal < 0 || netIndexOfUid < 0 {
return -1
}
network := source.Network()
if strings.HasSuffix(network, "4") || strings.HasSuffix(network, "6") {
network = network[:len(network)-1]
}
path := "/proc/net/" + network
var sIP net.IP
var sPort int
switch s := source.(type) {
case *net.TCPAddr:
sIP = s.IP
sPort = s.Port
case *net.UDPAddr:
sIP = s.IP
sPort = s.Port
default:
return -1
}
sIP = sIP.To16()
if sIP == nil {
return -1
}
uid := doQuery(path+"6", sIP, sPort)
if uid == -1 {
sIP = sIP.To4()
if sIP == nil {
return -1
}
uid = doQuery(path, sIP, sPort)
}
return uid
}
func doQuery(path string, sIP net.IP, sPort int) int {
file, err := os.Open(path)
if err != nil {
return -1
}
defer func(file *os.File) {
_ = file.Close()
}(file)
reader := bufio.NewReader(file)
var bytes [2]byte
binary.BigEndian.PutUint16(bytes[:], uint16(sPort))
local := fmt.Sprintf("%s:%s", hex.EncodeToString(nativeEndianIP(sIP)), hex.EncodeToString(bytes[:]))
for {
row, _, err := reader.ReadLine()
if err != nil {
return -1
}
fields := strings.Fields(string(row))
if len(fields) <= netIndexOfLocal || len(fields) <= netIndexOfUid {
continue
}
if strings.EqualFold(local, fields[netIndexOfLocal]) {
uid, err := strconv.Atoi(fields[netIndexOfUid])
if err != nil {
return -1
}
return uid
}
}
}
func nativeEndianIP(ip net.IP) []byte {
result := make([]byte, len(ip))
for i := 0; i < len(ip); i += 4 {
value := binary.BigEndian.Uint32(ip[i:])
nativeEndian.PutUint32(result[i:], value)
}
return result
}
func init() {
file, err := os.Open("/proc/net/tcp")
if err != nil {
return
}
defer func(file *os.File) {
_ = file.Close()
}(file)
reader := bufio.NewReader(file)
header, _, err := reader.ReadLine()
if err != nil {
return
}
columns := strings.Fields(string(header))
var txQueue, rxQueue, tr, tmWhen bool
for idx, col := range columns {
offset := 0
if txQueue && rxQueue {
offset--
}
if tr && tmWhen {
offset--
}
switch col {
case "tx_queue":
txQueue = true
case "rx_queue":
rxQueue = true
case "tr":
tr = true
case "tm->when":
tmWhen = true
case "local_address":
netIndexOfLocal = idx + offset
case "uid":
netIndexOfUid = idx + offset
}
}
}
func init() {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
nativeEndian = binary.BigEndian
} else {
nativeEndian = binary.LittleEndian
}
}

View File

@@ -7,7 +7,7 @@ import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/manager/hotkey_manager.dart'; import 'package:fl_clash/manager/hotkey_manager.dart';
import 'package:fl_clash/manager/manager.dart'; import 'package:fl_clash/manager/manager.dart';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/providers/config.dart'; import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
@@ -43,16 +43,8 @@ class ApplicationState extends ConsumerState<Application> {
ColorScheme _getAppColorScheme({ ColorScheme _getAppColorScheme({
required Brightness brightness, required Brightness brightness,
int? primaryColor, int? primaryColor,
required ColorSchemes systemColorSchemes,
}) { }) {
if (primaryColor != null) { return ref.read(genColorSchemeProvider(brightness));
return ColorScheme.fromSeed(
seedColor: Color(primaryColor),
brightness: brightness,
);
} else {
return systemColorSchemes.getColorSchemeForBrightness(brightness);
}
} }
@override @override
@@ -183,7 +175,7 @@ class ApplicationState extends ConsumerState<Application> {
}, },
scrollBehavior: BaseScrollBehavior(), scrollBehavior: BaseScrollBehavior(),
title: appName, title: appName,
locale: other.getLocaleForString(locale), locale: utils.getLocaleForString(locale),
supportedLocales: AppLocalizations.delegate.supportedLocales, supportedLocales: AppLocalizations.delegate.supportedLocales,
themeMode: themeProps.themeMode, themeMode: themeProps.themeMode,
theme: ThemeData( theme: ThemeData(
@@ -191,7 +183,6 @@ class ApplicationState extends ConsumerState<Application> {
pageTransitionsTheme: _pageTransitionsTheme, pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme( colorScheme: _getAppColorScheme(
brightness: Brightness.light, brightness: Brightness.light,
systemColorSchemes: systemColorSchemes,
primaryColor: themeProps.primaryColor, primaryColor: themeProps.primaryColor,
), ),
), ),
@@ -200,7 +191,6 @@ class ApplicationState extends ConsumerState<Application> {
pageTransitionsTheme: _pageTransitionsTheme, pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme( colorScheme: _getAppColorScheme(
brightness: Brightness.dark, brightness: Brightness.dark,
systemColorSchemes: systemColorSchemes,
primaryColor: themeProps.primaryColor, primaryColor: themeProps.primaryColor,
).toPureBlack(themeProps.pureBlack), ).toPureBlack(themeProps.pureBlack),
), ),

View File

@@ -8,6 +8,7 @@ import 'package:fl_clash/clash/interface.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
@@ -66,7 +67,12 @@ class ClashCore {
Future<bool> init() async { Future<bool> init() async {
await initGeo(); await initGeo();
final homeDirPath = await appPath.homeDirPath; final homeDirPath = await appPath.homeDirPath;
return await clashInterface.init(homeDirPath); return await clashInterface.init(
InitParams(
homeDir: homeDirPath,
version: globalState.appState.version,
),
);
} }
Future<bool> setState(CoreState state) async { Future<bool> setState(CoreState state) async {

View File

@@ -2348,6 +2348,97 @@ class ClashFFI {
set suboptarg(ffi.Pointer<ffi.Char> value) => _suboptarg.value = value; set suboptarg(ffi.Pointer<ffi.Char> value) => _suboptarg.value = value;
void protect(
protect_func fn,
ffi.Pointer<ffi.Void> tun_interface,
int fd,
) {
return _protect(
fn,
tun_interface,
fd,
);
}
late final _protectPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(
protect_func, ffi.Pointer<ffi.Void>, ffi.Int)>>('protect');
late final _protect = _protectPtr
.asFunction<void Function(protect_func, ffi.Pointer<ffi.Void>, int)>();
ffi.Pointer<ffi.Char> resolve_process(
resolve_process_func fn,
ffi.Pointer<ffi.Void> tun_interface,
int protocol,
ffi.Pointer<ffi.Char> source,
ffi.Pointer<ffi.Char> target,
int uid,
) {
return _resolve_process(
fn,
tun_interface,
protocol,
source,
target,
uid,
);
}
late final _resolve_processPtr = _lookup<
ffi.NativeFunction<
ffi.Pointer<ffi.Char> Function(
resolve_process_func,
ffi.Pointer<ffi.Void>,
ffi.Int,
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>,
ffi.Int)>>('resolve_process');
late final _resolve_process = _resolve_processPtr.asFunction<
ffi.Pointer<ffi.Char> Function(
resolve_process_func,
ffi.Pointer<ffi.Void>,
int,
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>,
int)>();
void release_object(
release_object_func fn,
ffi.Pointer<ffi.Void> obj,
) {
return _release_object(
fn,
obj,
);
}
late final _release_objectPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(
release_object_func, ffi.Pointer<ffi.Void>)>>('release_object');
late final _release_object = _release_objectPtr
.asFunction<void Function(release_object_func, ffi.Pointer<ffi.Void>)>();
void registerCallbacks(
protect_func markSocketFunc,
resolve_process_func resolveProcessFunc,
release_object_func releaseObjectFunc,
) {
return _registerCallbacks(
markSocketFunc,
resolveProcessFunc,
releaseObjectFunc,
);
}
late final _registerCallbacksPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(protect_func, resolve_process_func,
release_object_func)>>('registerCallbacks');
late final _registerCallbacks = _registerCallbacksPtr.asFunction<
void Function(protect_func, resolve_process_func, release_object_func)>();
void initNativeApiBridge( void initNativeApiBridge(
ffi.Pointer<ffi.Void> api, ffi.Pointer<ffi.Void> api,
) { ) {
@@ -2443,28 +2534,14 @@ class ClashFFI {
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stopListener'); _lookup<ffi.NativeFunction<ffi.Void Function()>>('stopListener');
late final _stopListener = _stopListenerPtr.asFunction<void Function()>(); late final _stopListener = _stopListenerPtr.asFunction<void Function()>();
void attachInvokePort(
int mPort,
) {
return _attachInvokePort(
mPort,
);
}
late final _attachInvokePortPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.LongLong)>>(
'attachInvokePort');
late final _attachInvokePort =
_attachInvokePortPtr.asFunction<void Function(int)>();
void quickStart( void quickStart(
ffi.Pointer<ffi.Char> dirChar, ffi.Pointer<ffi.Char> initParamsChar,
ffi.Pointer<ffi.Char> paramsChar, ffi.Pointer<ffi.Char> paramsChar,
ffi.Pointer<ffi.Char> stateParamsChar, ffi.Pointer<ffi.Char> stateParamsChar,
int port, int port,
) { ) {
return _quickStart( return _quickStart(
dirChar, initParamsChar,
paramsChar, paramsChar,
stateParamsChar, stateParamsChar,
port, port,
@@ -2479,19 +2556,21 @@ class ClashFFI {
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>, int)>(); ffi.Pointer<ffi.Char>, int)>();
ffi.Pointer<ffi.Char> startTUN( int startTUN(
int fd, int fd,
ffi.Pointer<ffi.Void> callback,
) { ) {
return _startTUN( return _startTUN(
fd, fd,
callback,
); );
} }
late final _startTUNPtr = late final _startTUNPtr = _lookup<
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(ffi.Int)>>( ffi.NativeFunction<GoUint8 Function(ffi.Int, ffi.Pointer<ffi.Void>)>>(
'startTUN'); 'startTUN');
late final _startTUN = late final _startTUN =
_startTUNPtr.asFunction<ffi.Pointer<ffi.Char> Function(int)>(); _startTUNPtr.asFunction<int Function(int, ffi.Pointer<ffi.Void>)>();
ffi.Pointer<ffi.Char> getRunTime() { ffi.Pointer<ffi.Char> getRunTime() {
return _getRunTime(); return _getRunTime();
@@ -2511,20 +2590,6 @@ class ClashFFI {
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stopTun'); _lookup<ffi.NativeFunction<ffi.Void Function()>>('stopTun');
late final _stopTun = _stopTunPtr.asFunction<void Function()>(); late final _stopTun = _stopTunPtr.asFunction<void Function()>();
void setFdMap(
ffi.Pointer<ffi.Char> fdIdChar,
) {
return _setFdMap(
fdIdChar,
);
}
late final _setFdMapPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
'setFdMap');
late final _setFdMap =
_setFdMapPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
ffi.Pointer<ffi.Char> getCurrentProfileName() { ffi.Pointer<ffi.Char> getCurrentProfileName() {
return _getCurrentProfileName(); return _getCurrentProfileName();
} }
@@ -2572,20 +2637,6 @@ class ClashFFI {
'updateDns'); 'updateDns');
late final _updateDns = late final _updateDns =
_updateDnsPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>(); _updateDnsPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
void setProcessMap(
ffi.Pointer<ffi.Char> s,
) {
return _setProcessMap(
s,
);
}
late final _setProcessMapPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
'setProcessMap');
late final _setProcessMap =
_setProcessMapPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
} }
final class __mbstate_t extends ffi.Union { final class __mbstate_t extends ffi.Union {
@@ -3738,6 +3789,31 @@ typedef mode_t = __darwin_mode_t;
typedef __darwin_mode_t = __uint16_t; typedef __darwin_mode_t = __uint16_t;
typedef __uint16_t = ffi.UnsignedShort; typedef __uint16_t = ffi.UnsignedShort;
typedef Dart__uint16_t = int; typedef Dart__uint16_t = int;
typedef protect_func = ffi.Pointer<ffi.NativeFunction<protect_funcFunction>>;
typedef protect_funcFunction = ffi.Void Function(
ffi.Pointer<ffi.Void> tun_interface, ffi.Int fd);
typedef Dartprotect_funcFunction = void Function(
ffi.Pointer<ffi.Void> tun_interface, int fd);
typedef resolve_process_func
= ffi.Pointer<ffi.NativeFunction<resolve_process_funcFunction>>;
typedef resolve_process_funcFunction = ffi.Pointer<ffi.Char> Function(
ffi.Pointer<ffi.Void> tun_interface,
ffi.Int protocol,
ffi.Pointer<ffi.Char> source,
ffi.Pointer<ffi.Char> target,
ffi.Int uid);
typedef Dartresolve_process_funcFunction = ffi.Pointer<ffi.Char> Function(
ffi.Pointer<ffi.Void> tun_interface,
int protocol,
ffi.Pointer<ffi.Char> source,
ffi.Pointer<ffi.Char> target,
int uid);
typedef release_object_func
= ffi.Pointer<ffi.NativeFunction<release_object_funcFunction>>;
typedef release_object_funcFunction = ffi.Void Function(
ffi.Pointer<ffi.Void> obj);
typedef Dartrelease_object_funcFunction = void Function(
ffi.Pointer<ffi.Void> obj);
final class GoInterface extends ffi.Struct { final class GoInterface extends ffi.Struct {
external ffi.Pointer<ffi.Void> t; external ffi.Pointer<ffi.Void> t;
@@ -3758,6 +3834,8 @@ final class GoSlice extends ffi.Struct {
typedef GoInt = GoInt64; typedef GoInt = GoInt64;
typedef GoInt64 = ffi.LongLong; typedef GoInt64 = ffi.LongLong;
typedef DartGoInt64 = int; typedef DartGoInt64 = int;
typedef GoUint8 = ffi.UnsignedChar;
typedef DartGoUint8 = int;
const int __has_safe_buffers = 1; const int __has_safe_buffers = 1;
@@ -3973,6 +4051,8 @@ const int __MAC_15_0 = 150000;
const int __MAC_15_1 = 150100; const int __MAC_15_1 = 150100;
const int __MAC_15_2 = 150200;
const int __IPHONE_2_0 = 20000; const int __IPHONE_2_0 = 20000;
const int __IPHONE_2_1 = 20100; const int __IPHONE_2_1 = 20100;
@@ -4135,6 +4215,8 @@ const int __IPHONE_18_0 = 180000;
const int __IPHONE_18_1 = 180100; const int __IPHONE_18_1 = 180100;
const int __IPHONE_18_2 = 180200;
const int __WATCHOS_1_0 = 10000; const int __WATCHOS_1_0 = 10000;
const int __WATCHOS_2_0 = 20000; const int __WATCHOS_2_0 = 20000;
@@ -4233,6 +4315,8 @@ const int __WATCHOS_11_0 = 110000;
const int __WATCHOS_11_1 = 110100; const int __WATCHOS_11_1 = 110100;
const int __WATCHOS_11_2 = 110200;
const int __TVOS_9_0 = 90000; const int __TVOS_9_0 = 90000;
const int __TVOS_9_1 = 90100; const int __TVOS_9_1 = 90100;
@@ -4333,6 +4417,8 @@ const int __TVOS_18_0 = 180000;
const int __TVOS_18_1 = 180100; const int __TVOS_18_1 = 180100;
const int __TVOS_18_2 = 180200;
const int __BRIDGEOS_2_0 = 20000; const int __BRIDGEOS_2_0 = 20000;
const int __BRIDGEOS_3_0 = 30000; const int __BRIDGEOS_3_0 = 30000;
@@ -4389,6 +4475,8 @@ const int __BRIDGEOS_9_0 = 90000;
const int __BRIDGEOS_9_1 = 90100; const int __BRIDGEOS_9_1 = 90100;
const int __BRIDGEOS_9_2 = 90200;
const int __DRIVERKIT_19_0 = 190000; const int __DRIVERKIT_19_0 = 190000;
const int __DRIVERKIT_20_0 = 200000; const int __DRIVERKIT_20_0 = 200000;
@@ -4419,6 +4507,8 @@ const int __DRIVERKIT_24_0 = 240000;
const int __DRIVERKIT_24_1 = 240100; const int __DRIVERKIT_24_1 = 240100;
const int __DRIVERKIT_24_2 = 240200;
const int __VISIONOS_1_0 = 10000; const int __VISIONOS_1_0 = 10000;
const int __VISIONOS_1_1 = 10100; const int __VISIONOS_1_1 = 10100;
@@ -4429,6 +4519,8 @@ const int __VISIONOS_2_0 = 20000;
const int __VISIONOS_2_1 = 20100; const int __VISIONOS_2_1 = 20100;
const int __VISIONOS_2_2 = 20200;
const int MAC_OS_X_VERSION_10_0 = 1000; const int MAC_OS_X_VERSION_10_0 = 1000;
const int MAC_OS_X_VERSION_10_1 = 1010; const int MAC_OS_X_VERSION_10_1 = 1010;
@@ -4555,9 +4647,11 @@ const int MAC_OS_VERSION_15_0 = 150000;
const int MAC_OS_VERSION_15_1 = 150100; const int MAC_OS_VERSION_15_1 = 150100;
const int MAC_OS_VERSION_15_2 = 150200;
const int __MAC_OS_X_VERSION_MIN_REQUIRED = 150000; const int __MAC_OS_X_VERSION_MIN_REQUIRED = 150000;
const int __MAC_OS_X_VERSION_MAX_ALLOWED = 150100; const int __MAC_OS_X_VERSION_MAX_ALLOWED = 150200;
const int __ENABLE_LEGACY_MAC_AVAILABILITY = 1; const int __ENABLE_LEGACY_MAC_AVAILABILITY = 1;

View File

@@ -7,7 +7,7 @@ import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
mixin ClashInterface { mixin ClashInterface {
Future<bool> init(String homeDir); Future<bool> init(InitParams params);
Future<bool> preload(); Future<bool> preload();
@@ -74,12 +74,10 @@ mixin AndroidClashInterface {
Future<bool> setProcessMap(ProcessMapItem item); Future<bool> setProcessMap(ProcessMapItem item);
Future<bool> stopTun(); // Future<bool> stopTun();
Future<bool> updateDns(String value); Future<bool> updateDns(String value);
Future<DateTime?> startTun(int fd);
Future<AndroidVpnOptions?> getAndroidVpnOptions(); Future<AndroidVpnOptions?> getAndroidVpnOptions();
Future<String> getCurrentProfileName(); Future<String> getCurrentProfileName();
@@ -153,7 +151,7 @@ abstract class ClashHandlerInterface with ClashInterface {
Duration? timeout, Duration? timeout,
FutureOr<T> Function()? onTimeout, FutureOr<T> Function()? onTimeout,
}) async { }) async {
final id = "${method.name}#${other.id}"; final id = "${method.name}#${utils.id}";
callbackCompleterMap[id] = Completer<T>(); callbackCompleterMap[id] = Completer<T>();
@@ -191,10 +189,10 @@ abstract class ClashHandlerInterface with ClashInterface {
} }
@override @override
Future<bool> init(String homeDir) { Future<bool> init(InitParams params) {
return invoke<bool>( return invoke<bool>(
method: ActionMethod.initClash, method: ActionMethod.initClash,
data: homeDir, data: json.encode(params),
); );
} }

View File

@@ -122,25 +122,12 @@ class ClashLib extends ClashHandlerInterface with AndroidClashInterface {
); );
} }
@override // @override
Future<DateTime?> startTun(int fd) async { // Future<bool> stopTun() {
final res = await invoke<String>( // return invoke<bool>(
method: ActionMethod.startTun, // method: ActionMethod.stopTun,
data: json.encode(fd), // );
); // }
if (res.isEmpty) {
return null;
}
return DateTime.fromMillisecondsSinceEpoch(int.parse(res));
}
@override
Future<bool> stopTun() {
return invoke<bool>(
method: ActionMethod.stopTun,
);
}
@override @override
Future<AndroidVpnOptions?> getAndroidVpnOptions() async { Future<AndroidVpnOptions?> getAndroidVpnOptions() async {
@@ -224,37 +211,12 @@ class ClashLibHandler {
); );
} }
attachInvokePort(int invokePort) {
clashFFI.attachInvokePort(
invokePort,
);
}
DateTime? startTun(int fd) {
final runTimeRaw = clashFFI.startTUN(fd);
final runTimeString = runTimeRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(runTimeRaw);
if (runTimeString.isEmpty) return null;
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
}
stopTun() {
clashFFI.stopTun();
}
updateDns(String dns) { updateDns(String dns) {
final dnsChar = dns.toNativeUtf8().cast<Char>(); final dnsChar = dns.toNativeUtf8().cast<Char>();
clashFFI.updateDns(dnsChar); clashFFI.updateDns(dnsChar);
malloc.free(dnsChar); malloc.free(dnsChar);
} }
setProcessMap(ProcessMapItem processMapItem) {
final processMapItemChar =
json.encode(processMapItem).toNativeUtf8().cast<Char>();
clashFFI.setProcessMap(processMapItemChar);
malloc.free(processMapItemChar);
}
setState(CoreState state) { setState(CoreState state) {
final stateChar = json.encode(state).toNativeUtf8().cast<Char>(); final stateChar = json.encode(state).toNativeUtf8().cast<Char>();
clashFFI.setState(stateChar); clashFFI.setState(stateChar);
@@ -305,17 +267,11 @@ class ClashLibHandler {
return true; return true;
} }
setFdMap(String id) {
final idChar = id.toNativeUtf8().cast<Char>();
clashFFI.setFdMap(idChar);
malloc.free(idChar);
}
Future<String> quickStart( Future<String> quickStart(
String homeDir, InitParams initParams,
UpdateConfigParams updateConfigParams, UpdateConfigParams updateConfigParams,
CoreState state, CoreState state,
) { ) {
final completer = Completer<String>(); final completer = Completer<String>();
final receiver = ReceivePort(); final receiver = ReceivePort();
receiver.listen((message) { receiver.listen((message) {
@@ -325,17 +281,18 @@ class ClashLibHandler {
} }
}); });
final params = json.encode(updateConfigParams); final params = json.encode(updateConfigParams);
final initValue = json.encode(initParams);
final stateParams = json.encode(state); final stateParams = json.encode(state);
final homeChar = homeDir.toNativeUtf8().cast<Char>(); final initParamsChar = initValue.toNativeUtf8().cast<Char>();
final paramsChar = params.toNativeUtf8().cast<Char>(); final paramsChar = params.toNativeUtf8().cast<Char>();
final stateParamsChar = stateParams.toNativeUtf8().cast<Char>(); final stateParamsChar = stateParams.toNativeUtf8().cast<Char>();
clashFFI.quickStart( clashFFI.quickStart(
homeChar, initParamsChar,
paramsChar, paramsChar,
stateParamsChar, stateParamsChar,
receiver.sendPort.nativePort, receiver.sendPort.nativePort,
); );
malloc.free(homeChar); malloc.free(initParamsChar);
malloc.free(paramsChar); malloc.free(paramsChar);
malloc.free(stateParamsChar); malloc.free(stateParamsChar);
return completer.future; return completer.future;

View File

@@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
extension ColorExtension on Color { extension ColorExtension on Color {
@@ -37,11 +39,54 @@ extension ColorExtension on Color {
return withAlpha(0); return withAlpha(0);
} }
Color darken([double amount = .1]) { int get value32bit {
assert(amount >= 0 && amount <= 1); return _floatToInt8(a) << 24 |
final hsl = HSLColor.fromColor(this); _floatToInt8(r) << 16 |
final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0)); _floatToInt8(g) << 8 |
return hslDark.toColor(); _floatToInt8(b) << 0;
}
int get alpha8bit => (0xff000000 & value32bit) >> 24;
int get red8bit => (0x00ff0000 & value32bit) >> 16;
int get green8bit => (0x0000ff00 & value32bit) >> 8;
int get blue8bit => (0x000000ff & value32bit) >> 0;
int _floatToInt8(double x) {
return (x * 255.0).round() & 0xff;
}
Color lighten([double amount = 10]) {
if (amount <= 0) return this;
if (amount > 100) return Colors.white;
final HSLColor hsl = this == const Color(0xFF000000)
? HSLColor.fromColor(this).withSaturation(0)
: HSLColor.fromColor(this);
return hsl
.withLightness(min(1, max(0, hsl.lightness + amount / 100)))
.toColor();
}
String get hex {
final value = toARGB32();
final red = (value >> 16) & 0xFF;
final green = (value >> 8) & 0xFF;
final blue = value & 0xFF;
return '#${red.toRadixString(16).padLeft(2, '0')}'
'${green.toRadixString(16).padLeft(2, '0')}'
'${blue.toRadixString(16).padLeft(2, '0')}'
.toUpperCase();
}
Color darken([final int amount = 10]) {
if (amount <= 0) return this;
if (amount > 100) return Colors.black;
final HSLColor hsl = HSLColor.fromColor(this);
return hsl
.withLightness(min(1, max(0, hsl.lightness - amount / 100)))
.toColor();
} }
Color blendDarken( Color blendDarken(
@@ -74,7 +119,7 @@ extension ColorSchemeExtension on ColorScheme {
? copyWith( ? copyWith(
surface: Colors.black, surface: Colors.black,
surfaceContainer: surfaceContainer.darken( surfaceContainer: surfaceContainer.darken(
0.05, 5,
), ),
) )
: this; : this;

View File

@@ -19,7 +19,7 @@ export 'navigation.dart';
export 'navigator.dart'; export 'navigator.dart';
export 'network.dart'; export 'network.dart';
export 'num.dart'; export 'num.dart';
export 'other.dart'; export 'utils.dart';
export 'package.dart'; export 'package.dart';
export 'path.dart'; export 'path.dart';
export 'picker.dart'; export 'picker.dart';

View File

@@ -78,7 +78,7 @@ const viewModeColumnsMap = {
ViewMode.desktop: [4, 3], ViewMode.desktop: [4, 3],
}; };
const defaultPrimaryColor = Colors.brown; const defaultPrimaryColor = 0xFF795548;
double getWidgetHeight(num lines) { double getWidgetHeight(num lines) {
return max(lines * 84 * textScaleFactor + (lines - 1) * 16, 0); return max(lines * 84 * textScaleFactor + (lines - 1) * 16, 0);
@@ -87,3 +87,13 @@ double getWidgetHeight(num lines) {
final mainIsolate = "FlClashMainIsolate"; final mainIsolate = "FlClashMainIsolate";
final serviceIsolate = "FlClashServiceIsolate"; final serviceIsolate = "FlClashServiceIsolate";
const defaultPrimaryColors = [
defaultPrimaryColor,
0xFF03A9F4,
0xFFFFFF00,
0XFFBBC9CC,
0XFFABD397,
0XFFD8C0C3,
0XFF665390,
];

View File

@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:launch_at_startup/launch_at_startup.dart'; import 'package:launch_at_startup/launch_at_startup.dart';
import 'constant.dart'; import 'constant.dart';
@@ -34,6 +35,9 @@ class AutoLaunch {
} }
updateStatus(bool isAutoLaunch) async { updateStatus(bool isAutoLaunch) async {
if(kDebugMode){
return;
}
if (await isEnable == isAutoLaunch) return; if (await isEnable == isAutoLaunch) return;
if (isAutoLaunch == true) { if (isAutoLaunch == true) {
enable(); enable();

View File

@@ -54,4 +54,4 @@ class Render {
} }
} }
final render = system.isDesktop ? Render() : null; final Render? render = system.isDesktop ? Render() : null;

View File

@@ -68,7 +68,7 @@ class Request {
final remoteVersion = data['tag_name']; final remoteVersion = data['tag_name'];
final version = globalState.packageInfo.version; final version = globalState.packageInfo.version;
final hasUpdate = final hasUpdate =
other.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0; utils.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0;
if (!hasUpdate) return null; if (!hasUpdate) return null;
return data; return data;
} }

View File

@@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/common/utils.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
@@ -10,7 +11,6 @@ import 'package:tray_manager/tray_manager.dart';
import 'app_localizations.dart'; import 'app_localizations.dart';
import 'constant.dart'; import 'constant.dart';
import 'other.dart';
import 'window.dart'; import 'window.dart';
class Tray { class Tray {
@@ -25,7 +25,7 @@ class Tray {
await trayManager.destroy(); await trayManager.destroy();
} }
await trayManager.setIcon( await trayManager.setIcon(
other.getTrayIconPath( utils.getTrayIconPath(
brightness: brightness ?? brightness: brightness ??
WidgetsBinding.instance.platformDispatcher.platformBrightness, WidgetsBinding.instance.platformDispatcher.platformBrightness,
), ),

View File

@@ -7,7 +7,7 @@ import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lpinyin/lpinyin.dart'; import 'package:lpinyin/lpinyin.dart';
class Other { class Utils {
Color? getDelayColor(int? delay) { Color? getDelayColor(int? delay) {
if (delay == null) return null; if (delay == null) return null;
if (delay < 0) return Colors.red; if (delay < 0) return Colors.red;
@@ -233,6 +233,63 @@ class Other {
return max((viewWidth / 350).floor(), 1); return max((viewWidth / 350).floor(), 1);
} }
final _indexPrimary = [
50,
100,
200,
300,
400,
500,
600,
700,
800,
850,
900,
];
_createPrimarySwatch(Color color) {
final Map<int, Color> swatch = <int, Color>{};
final int a = color.alpha8bit;
final int r = color.red8bit;
final int g = color.green8bit;
final int b = color.blue8bit;
for (final int strength in _indexPrimary) {
final double ds = 0.5 - strength / 1000;
swatch[strength] = Color.fromARGB(
a,
r + ((ds < 0 ? r : (255 - r)) * ds).round(),
g + ((ds < 0 ? g : (255 - g)) * ds).round(),
b + ((ds < 0 ? b : (255 - b)) * ds).round(),
);
}
swatch[50] = swatch[50]!.lighten(18);
swatch[100] = swatch[100]!.lighten(16);
swatch[200] = swatch[200]!.lighten(14);
swatch[300] = swatch[300]!.lighten(10);
swatch[400] = swatch[400]!.lighten(6);
swatch[700] = swatch[700]!.darken(2);
swatch[800] = swatch[800]!.darken(3);
swatch[900] = swatch[900]!.darken(4);
return MaterialColor(color.value32bit, swatch);
}
List<Color> getMaterialColorShades(Color color) {
final swatch = _createPrimarySwatch(color);
return <Color>[
if (swatch[50] != null) swatch[50]!,
if (swatch[100] != null) swatch[100]!,
if (swatch[200] != null) swatch[200]!,
if (swatch[300] != null) swatch[300]!,
if (swatch[400] != null) swatch[400]!,
if (swatch[500] != null) swatch[500]!,
if (swatch[600] != null) swatch[600]!,
if (swatch[700] != null) swatch[700]!,
if (swatch[800] != null) swatch[800]!,
if (swatch[850] != null) swatch[850]!,
if (swatch[900] != null) swatch[900]!,
];
}
String getBackupFileName() { String getBackupFileName() {
return "${appName}_backup_${DateTime.now().show}.zip"; return "${appName}_backup_${DateTime.now().show}.zip";
} }
@@ -268,4 +325,4 @@ class Other {
} }
} }
final other = Other(); final utils = Utils();

View File

@@ -30,8 +30,9 @@ class AppController {
AppController(this.context, WidgetRef ref) : _ref = ref; AppController(this.context, WidgetRef ref) : _ref = ref;
updateClashConfigDebounce() { updateClashConfigDebounce() {
debouncer.call(DebounceTag.updateClashConfig, () { debouncer.call(DebounceTag.updateClashConfig, () async {
updateClashConfig(true); final isPatch = globalState.appState.needApply ? false : true;
await updateClashConfig(isPatch);
}); });
} }
@@ -70,7 +71,7 @@ class AppController {
restartCore() async { restartCore() async {
await clashService?.reStart(); await clashService?.reStart();
await initCore(); await _initCore();
if (_ref.read(runTimeProvider.notifier).isStart) { if (_ref.read(runTimeProvider.notifier).isStart) {
await globalState.handleStart(); await globalState.handleStart();
@@ -100,7 +101,6 @@ class AppController {
_ref.read(trafficsProvider.notifier).clear(); _ref.read(trafficsProvider.notifier).clear();
_ref.read(totalTrafficProvider.notifier).value = Traffic(); _ref.read(totalTrafficProvider.notifier).value = Traffic();
_ref.read(runTimeProvider.notifier).value = null; _ref.read(runTimeProvider.notifier).value = null;
// tray.updateTrayTitle(null);
addCheckIpNumDebounce(); addCheckIpNumDebounce();
} }
} }
@@ -153,7 +153,7 @@ class AppController {
updateLocalIp() async { updateLocalIp() async {
_ref.read(localIpProvider.notifier).value = null; _ref.read(localIpProvider.notifier).value = null;
await Future.delayed(commonDuration); await Future.delayed(commonDuration);
_ref.read(localIpProvider.notifier).value = await other.getLocalIpAddress(); _ref.read(localIpProvider.notifier).value = await utils.getLocalIpAddress();
} }
Future<void> updateProfile(Profile profile) async { Future<void> updateProfile(Profile profile) async {
@@ -283,6 +283,9 @@ class AppController {
final res = await clashCore.updateConfig( final res = await clashCore.updateConfig(
globalState.getUpdateConfigParams(isPatch), globalState.getUpdateConfigParams(isPatch),
); );
if (isPatch == false) {
_ref.read(needApplyProvider.notifier).value = false;
}
if (res.isNotEmpty) throw res; if (res.isNotEmpty) throw res;
lastTunEnable = enableTun; lastTunEnable = enableTun;
lastProfileModified = await profile?.profileLastModified; lastProfileModified = await profile?.profileLastModified;
@@ -417,13 +420,13 @@ class AppController {
Map<String, dynamic>? data, Map<String, dynamic>? data,
bool handleError = false, bool handleError = false,
}) async { }) async {
if(globalState.isPre){ if (globalState.isPre) {
return; return;
} }
if (data != null) { if (data != null) {
final tagName = data['tag_name']; final tagName = data['tag_name'];
final body = data['body']; final body = data['body'];
final submits = other.parseReleaseBody(body); final submits = utils.parseReleaseBody(body);
final textTheme = context.textTheme; final textTheme = context.textTheme;
final res = await globalState.showMessage( final res = await globalState.showMessage(
title: appLocalizations.discoverNewVersion, title: appLocalizations.discoverNewVersion,
@@ -478,7 +481,7 @@ class AppController {
await handleExit(); await handleExit();
} }
Future<void> initCore() async { Future<void> _initCore() async {
final isInit = await clashCore.isInit; final isInit = await clashCore.isInit;
if (!isInit) { if (!isInit) {
await clashCore.setState( await clashCore.setState(
@@ -492,7 +495,7 @@ class AppController {
init() async { init() async {
await _handlePreference(); await _handlePreference();
await _handlerDisclaimer(); await _handlerDisclaimer();
await initCore(); await _initCore();
await _initStatus(); await _initStatus();
updateTray(true); updateTray(true);
autoLaunch?.updateStatus( autoLaunch?.updateStatus(
@@ -668,9 +671,9 @@ class AppController {
List<Proxy> _sortOfName(List<Proxy> proxies) { List<Proxy> _sortOfName(List<Proxy> proxies) {
return List.of(proxies) return List.of(proxies)
..sort( ..sort(
(a, b) => other.sortByChar( (a, b) => utils.sortByChar(
other.getPinyin(a.name), utils.getPinyin(a.name),
other.getPinyin(b.name), utils.getPinyin(b.name),
), ),
); );
} }
@@ -860,7 +863,7 @@ class AppController {
return utf8.encode(logsRawString); return utf8.encode(logsRawString);
}); });
return await picker.saveFile( return await picker.saveFile(
other.logFile, utils.logFile,
Uint8List.fromList(data), Uint8List.fromList(data),
) != ) !=
null; null;

View File

@@ -75,7 +75,7 @@ class BackupAndRecovery extends ConsumerWidget {
() async { () async {
final backupData = await globalState.appController.backupData(); final backupData = await globalState.appController.backupData();
final value = await picker.saveFile( final value = await picker.saveFile(
other.getBackupFileName(), utils.getBackupFileName(),
Uint8List.fromList(backupData), Uint8List.fromList(backupData),
); );
if (value == null) return false; if (value == null) return false;

View File

@@ -61,7 +61,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
if (res != true) { if (res != true) {
return; return;
} }
ref.read(patchClashConfigProvider.notifier).updateState( ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith( (state) => state.copyWith(
dns: defaultDns, dns: defaultDns,

View File

@@ -11,8 +11,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'item.dart'; import 'item.dart';
double _preOffset = 0;
class RequestsFragment extends ConsumerStatefulWidget { class RequestsFragment extends ConsumerStatefulWidget {
const RequestsFragment({super.key}); const RequestsFragment({super.key});
@@ -26,10 +24,8 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
final _requestsStateNotifier = final _requestsStateNotifier =
ValueNotifier<ConnectionsState>(const ConnectionsState()); ValueNotifier<ConnectionsState>(const ConnectionsState());
List<Connection> _requests = []; List<Connection> _requests = [];
final _cacheKey = ValueKey("requests_list");
final ScrollController _scrollController = ScrollController( late ScrollController _scrollController;
initialScrollOffset: _preOffset != 0 ? _preOffset : double.maxFinite,
);
double _currentMaxWidth = 0; double _currentMaxWidth = 0;
@@ -49,10 +45,13 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final preOffset = globalState.cacheScrollPosition[_cacheKey] ?? -1;
_scrollController = ScrollController(
initialScrollOffset: preOffset > 0 ? preOffset : double.maxFinite,
);
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith( _requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
connections: globalState.appState.requests.list, connections: globalState.appState.requests.list,
); );
ref.listenManual( ref.listenManual(
isCurrentPageProvider( isCurrentPageProvider(
PageLabel.requests, PageLabel.requests,
@@ -177,11 +176,10 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
.toList(); .toList();
return Align( return Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: NotificationListener<ScrollEndNotification>( child: ScrollToEndBox(
onNotification: (details) { controller: _scrollController,
_preOffset = details.metrics.pixels; cacheKey: _cacheKey,
return false; dataSource: connections,
},
child: CommonScrollBar( child: CommonScrollBar(
controller: _scrollController, controller: _scrollController,
child: CacheItemExtentListView( child: CacheItemExtentListView(

View File

@@ -71,10 +71,10 @@ class _StartButtonState extends State<StartButton>
final textWidth = globalState.measure final textWidth = globalState.measure
.computeTextSize( .computeTextSize(
Text( Text(
other.getTimeDifference( utils.getTimeDifference(
DateTime.now(), DateTime.now(),
), ),
style: Theme.of(context).textTheme.titleMedium?.toSoftBold, style: context.textTheme.titleMedium?.toSoftBold,
), ),
) )
.width + .width +
@@ -123,10 +123,12 @@ class _StartButtonState extends State<StartButton>
child: Consumer( child: Consumer(
builder: (_, ref, __) { builder: (_, ref, __) {
final runTime = ref.watch(runTimeProvider); final runTime = ref.watch(runTimeProvider);
final text = other.getTimeText(runTime); final text = utils.getTimeText(runTime);
return Text( return Text(
text, text,
style: Theme.of(context).textTheme.titleMedium?.toSoftBold, style: Theme.of(context).textTheme.titleMedium?.toSoftBold.copyWith(
color: context.colorScheme.onPrimaryContainer
),
); );
}, },
), ),

View File

@@ -8,8 +8,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/models.dart'; import '../models/models.dart';
import '../widgets/widgets.dart'; import '../widgets/widgets.dart';
double _preOffset = 0;
class LogsFragment extends ConsumerStatefulWidget { class LogsFragment extends ConsumerStatefulWidget {
const LogsFragment({super.key}); const LogsFragment({super.key});
@@ -19,9 +17,8 @@ class LogsFragment extends ConsumerStatefulWidget {
class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin { class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
final _logsStateNotifier = ValueNotifier<LogsState>(LogsState()); final _logsStateNotifier = ValueNotifier<LogsState>(LogsState());
final _scrollController = ScrollController( final _cacheKey = ValueKey("logs_list");
initialScrollOffset: _preOffset != 0 ? _preOffset : double.maxFinite, late ScrollController _scrollController;
);
double _currentMaxWidth = 0; double _currentMaxWidth = 0;
final GlobalKey<CacheItemExtentListViewState> _key = GlobalKey(); final GlobalKey<CacheItemExtentListViewState> _key = GlobalKey();
@@ -30,6 +27,10 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final preOffset = globalState.cacheScrollPosition[_cacheKey] ?? -1;
_scrollController = ScrollController(
initialScrollOffset: preOffset > 0 ? preOffset : double.maxFinite,
);
_logsStateNotifier.value = _logsStateNotifier.value.copyWith( _logsStateNotifier.value = _logsStateNotifier.value.copyWith(
logs: globalState.appState.logs.list, logs: globalState.appState.logs.list,
); );
@@ -180,11 +181,10 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
), ),
) )
.toList(); .toList();
return NotificationListener<ScrollEndNotification>( return ScrollToEndBox<Log>(
onNotification: (details) { controller: _scrollController,
_preOffset = details.metrics.pixels; cacheKey: _cacheKey,
return false; dataSource: logs,
},
child: CommonScrollBar( child: CommonScrollBar(
controller: _scrollController, controller: _scrollController,
child: CacheItemExtentListView( child: CacheItemExtentListView(

View File

@@ -45,6 +45,7 @@ class _OverrideProfileState extends State<OverrideProfile> {
} }
_handleSave(WidgetRef ref, OverrideData overrideData) { _handleSave(WidgetRef ref, OverrideData overrideData) {
ref.read(needApplyProvider.notifier).value = true;
ref.read(profilesProvider.notifier).updateProfile( ref.read(profilesProvider.notifier).updateProfile(
widget.profileId, widget.profileId,
(state) => state.copyWith( (state) => state.copyWith(

View File

@@ -63,7 +63,7 @@ class ProxyCard extends StatelessWidget {
delay > 0 ? '$delay ms' : "Timeout", delay > 0 ? '$delay ms' : "Timeout",
style: context.textTheme.labelSmall?.copyWith( style: context.textTheme.labelSmall?.copyWith(
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
color: other.getDelayColor( color: utils.getDelayColor(
delay, delay,
), ),
), ),

View File

@@ -1,9 +1,16 @@
import 'dart:math';
import 'dart:ui' as ui;
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/selector.dart';
import 'package:fl_clash/providers/config.dart'; import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart'; import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
class ThemeModeItem { class ThemeModeItem {
final ThemeMode themeMode; final ThemeMode themeMode;
@@ -32,39 +39,20 @@ class ThemeFragment extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final previewCard = Padding( return SingleChildScrollView(child: ThemeColorsBox());
padding: const EdgeInsets.symmetric(horizontal: 16),
child: CommonCard(
onPressed: () {},
info: Info(
label: appLocalizations.preview,
iconData: Icons.looks,
),
child: Container(
height: 200,
),
),
);
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
previewCard,
const ThemeColorsBox(),
],
),
);
} }
} }
class ItemCard extends StatelessWidget { class ItemCard extends StatelessWidget {
final Widget child; final Widget child;
final Info info; final Info info;
final List<Widget> actions;
const ItemCard({ const ItemCard({
super.key, super.key,
required this.info, required this.info,
required this.child, required this.child,
this.actions = const [],
}); });
@override @override
@@ -78,6 +66,7 @@ class ItemCard extends StatelessWidget {
children: [ children: [
InfoHeader( InfoHeader(
info: info, info: info,
actions: actions,
), ),
child, child,
], ],
@@ -98,7 +87,6 @@ class _ThemeColorsBoxState extends ConsumerState<ThemeColorsBox> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
// _FontFamilyItem(),
_ThemeModeItem(), _ThemeModeItem(),
_PrimaryColorItem(), _PrimaryColorItem(),
_PrueBlackItem(), _PrueBlackItem(),
@@ -110,75 +98,6 @@ class _ThemeColorsBoxState extends ConsumerState<ThemeColorsBox> {
} }
} }
// class _FontFamilyItem extends ConsumerWidget {
// const _FontFamilyItem();
//
// @override
// Widget build(BuildContext context, WidgetRef ref) {
// final fontFamily =
// ref.watch(themeSettingProvider.select((state) => state.fontFamily));
// List<FontFamilyItem> fontFamilyItems = [
// FontFamilyItem(
// label: appLocalizations.systemFont,
// fontFamily: FontFamily.system,
// ),
// const FontFamilyItem(
// label: "roboto",
// fontFamily: FontFamily.roboto,
// ),
// ];
// return ItemCard(
// info: Info(
// label: appLocalizations.fontFamily,
// iconData: Icons.text_fields,
// ),
// child: Container(
// margin: const EdgeInsets.only(
// left: 16,
// right: 16,
// ),
// height: 48,
// child: ListView.separated(
// scrollDirection: Axis.horizontal,
// itemBuilder: (_, index) {
// final fontFamilyItem = fontFamilyItems[index];
// return CommonCard(
// isSelected: fontFamilyItem.fontFamily == fontFamily,
// onPressed: () {
// ref.read(themeSettingProvider.notifier).updateState(
// (state) => state.copyWith(
// fontFamily: fontFamilyItem.fontFamily,
// ),
// );
// },
// child: Padding(
// padding: const EdgeInsets.symmetric(horizontal: 16),
// child: Row(
// mainAxisSize: MainAxisSize.min,
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// Flexible(
// child: Text(
// fontFamilyItem.label,
// ),
// ),
// ],
// ),
// ),
// );
// },
// separatorBuilder: (_, __) {
// return const SizedBox(
// width: 16,
// );
// },
// itemCount: fontFamilyItems.length,
// ),
// ),
// );
// }
// }
class _ThemeModeItem extends ConsumerWidget { class _ThemeModeItem extends ConsumerWidget {
const _ThemeModeItem(); const _ThemeModeItem();
@@ -210,7 +129,7 @@ class _ThemeModeItem extends ConsumerWidget {
), ),
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
height: 64, height: 56,
child: ListView.separated( child: ListView.separated(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: themeModeItems.length, itemCount: themeModeItems.length,
@@ -258,56 +177,256 @@ class _ThemeModeItem extends ConsumerWidget {
} }
} }
class _PrimaryColorItem extends ConsumerWidget { class _PrimaryColorItem extends ConsumerStatefulWidget {
const _PrimaryColorItem(); const _PrimaryColorItem();
@override @override
Widget build(BuildContext context, WidgetRef ref) { ConsumerState<_PrimaryColorItem> createState() => _PrimaryColorItemState();
final primaryColor = }
ref.watch(themeSettingProvider.select((state) => state.primaryColor));
List<Color?> primaryColors = [ class _PrimaryColorItemState extends ConsumerState<_PrimaryColorItem> {
null, int? _removablePrimaryColor;
defaultPrimaryColor,
Colors.pinkAccent, int _calcColumns(double maxWidth) {
Colors.lightBlue, return max((maxWidth / 96).ceil(), 3);
Colors.greenAccent, }
Colors.yellowAccent,
Colors.purple, _handleReset() async {
]; final res = await globalState.showMessage(
message: TextSpan(
text: appLocalizations.resetTip,
),
);
if (res != true) {
return;
}
ref.read(themeSettingProvider.notifier).updateState(
(state) {
return state.copyWith(
primaryColors: defaultPrimaryColors,
primaryColor: defaultPrimaryColor,
schemeVariant: DynamicSchemeVariant.tonalSpot,
);
},
);
}
_handleDel() async {
if (_removablePrimaryColor == null) {
return;
}
final res = await globalState.showMessage(
message: TextSpan(text: appLocalizations.deleteColorTip));
if (res != true) {
return;
}
ref.read(themeSettingProvider.notifier).updateState(
(state) {
final newPrimaryColors = List<int>.from(state.primaryColors)
..remove(_removablePrimaryColor);
int? newPrimaryColor = state.primaryColor;
if (state.primaryColor == _removablePrimaryColor) {
if (newPrimaryColors.contains(defaultPrimaryColor)) {
newPrimaryColor = defaultPrimaryColor;
} else {
newPrimaryColor = null;
}
}
return state.copyWith(
primaryColors: newPrimaryColors,
primaryColor: newPrimaryColor,
);
},
);
setState(() {
_removablePrimaryColor = null;
});
}
_handleAdd() async {
final res = await globalState.showCommonDialog<int>(
child: _PaletteDialog(),
);
if (res == null) {
return;
}
final isExists = ref.read(
themeSettingProvider.select((state) => state.primaryColors.contains(res)),
);
if (isExists && mounted) {
context.showNotifier(appLocalizations.colorExists);
return;
}
ref.read(themeSettingProvider.notifier).updateState(
(state) {
return state.copyWith(
primaryColors: List.from(
state.primaryColors,
)..add(res),
);
},
);
}
_handleChangeSchemeVariant() async {
final schemeVariant = ref.read(
themeSettingProvider.select(
(state) => state.schemeVariant,
),
);
final value = await globalState.showCommonDialog<DynamicSchemeVariant>(
child: OptionsDialog<DynamicSchemeVariant>(
title: appLocalizations.colorSchemes,
options: DynamicSchemeVariant.values,
textBuilder: (item) => Intl.message("${item.name}Scheme"),
value: schemeVariant,
),
);
if (value == null) {
return;
}
ref.read(themeSettingProvider.notifier).updateState(
(state) {
return state.copyWith(
schemeVariant: value,
);
},
);
}
@override
Widget build(BuildContext context) {
final vm3 = ref.watch(
themeSettingProvider.select(
(state) => VM3(
a: state.primaryColor,
b: state.primaryColors,
c: state.schemeVariant,
),
),
);
final primaryColor = vm3.a;
final primaryColors = [null, ...vm3.b];
final schemeVariant = vm3.c;
return ItemCard( return ItemCard(
info: Info( info: Info(
label: appLocalizations.themeColor, label: appLocalizations.themeColor,
iconData: Icons.palette, iconData: Icons.palette,
), ),
actions: genActions(
[
if (_removablePrimaryColor == null)
FilledButton(
style: ButtonStyle(
visualDensity: VisualDensity.compact,
),
onPressed: _handleChangeSchemeVariant,
child: Text(Intl.message("${schemeVariant.name}Scheme")),
),
_removablePrimaryColor != null
? FilledButton(
style: ButtonStyle(
visualDensity: VisualDensity.compact,
),
onPressed: () {
setState(() {
_removablePrimaryColor = null;
});
},
child: Text(appLocalizations.cancel),
)
: IconButton.filledTonal(
iconSize: 20,
padding: EdgeInsets.all(4),
visualDensity: VisualDensity.compact,
onPressed: _handleReset,
icon: Icon(Icons.replay),
)
],
space: 8,
),
child: Container( child: Container(
margin: const EdgeInsets.only( margin: const EdgeInsets.only(
left: 16, left: 16,
right: 16, right: 16,
bottom: 16, bottom: 16,
), ),
height: 88, child: LayoutBuilder(
child: ListView.separated( builder: (_, constraints) {
scrollDirection: Axis.horizontal, final columns = _calcColumns(constraints.maxWidth);
itemBuilder: (_, index) { final itemWidth =
final color = primaryColors[index]; (constraints.maxWidth - (columns - 1) * 16) / columns;
return ColorSchemeBox( return Wrap(
isSelected: color?.toARGB32() == primaryColor, spacing: 16,
primaryColor: color, runSpacing: 16,
onPressed: () { children: [
ref.read(themeSettingProvider.notifier).updateState( for (final color in primaryColors)
(state) => state.copyWith( Container(
primaryColor: color?.toARGB32(), clipBehavior: Clip.none,
width: itemWidth,
height: itemWidth,
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
EffectGestureDetector(
child: ColorSchemeBox(
isSelected: color == primaryColor,
primaryColor: color != null ? Color(color) : null,
onPressed: () {
ref
.read(themeSettingProvider.notifier)
.updateState(
(state) => state.copyWith(
primaryColor: color,
),
);
},
),
onLongPress: () {
setState(() {
_removablePrimaryColor = color;
});
},
),
if (_removablePrimaryColor != null &&
_removablePrimaryColor == color)
Container(
color: Colors.white.opacity0,
padding: EdgeInsets.all(8),
child: IconButton.filledTonal(
onPressed: _handleDel,
padding: EdgeInsets.all(12),
iconSize: 30,
icon: Icon(
color: context.colorScheme.primary,
Icons.delete,
),
),
),
],
),
),
if (_removablePrimaryColor == null)
Container(
width: itemWidth,
height: itemWidth,
padding: EdgeInsets.all(
4,
),
child: IconButton.filledTonal(
onPressed: _handleAdd,
iconSize: 32,
icon: Icon(
color: context.colorScheme.primary,
Icons.add,
), ),
); ),
}, )
],
); );
}, },
separatorBuilder: (_, __) {
return const SizedBox(
width: 16,
);
},
itemCount: primaryColors.length,
), ),
), ),
); );
@@ -326,9 +445,14 @@ class _PrueBlackItem extends ConsumerWidget {
child: ListItem.switchItem( child: ListItem.switchItem(
leading: Icon( leading: Icon(
Icons.contrast, Icons.contrast,
color: context.colorScheme.primary,
), ),
title: Text(appLocalizations.pureBlackMode), horizontalTitleGap: 12,
title: Text(
appLocalizations.pureBlackMode,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurfaceVariant,
),
),
delegate: SwitchDelegate( delegate: SwitchDelegate(
value: prueBlack, value: prueBlack,
onChanged: (value) { onChanged: (value) {
@@ -343,3 +467,66 @@ class _PrueBlackItem extends ConsumerWidget {
); );
} }
} }
class _PaletteDialog extends StatefulWidget {
const _PaletteDialog();
@override
State<_PaletteDialog> createState() => _PaletteDialogState();
}
class _PaletteDialogState extends State<_PaletteDialog> {
final _controller = ValueNotifier<ui.Color>(Colors.transparent);
@override
Widget build(BuildContext context) {
return CommonDialog(
title: appLocalizations.palette,
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(appLocalizations.cancel),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(_controller.value.toARGB32());
},
child: Text(appLocalizations.confirm),
),
],
child: Column(
children: [
SizedBox(
height: 8,
),
SizedBox(
width: 250,
height: 250,
child: Palette(
controller: _controller,
),
),
SizedBox(
height: 24,
),
ValueListenableBuilder(
valueListenable: _controller,
builder: (_, color, __) {
return PrimaryColorBox(
primaryColor: color,
child: FilledButton(
onPressed: () {},
child: Text(
_controller.value.hex,
),
),
);
},
),
],
),
);
}
}

View File

@@ -122,7 +122,7 @@ class _LocaleItem extends ConsumerWidget {
final locale = final locale =
ref.watch(appSettingProvider.select((state) => state.locale)); ref.watch(appSettingProvider.select((state) => state.locale));
final subTitle = locale ?? appLocalizations.defaultText; final subTitle = locale ?? appLocalizations.defaultText;
final currentLocale = other.getLocaleForString(locale); final currentLocale = utils.getLocaleForString(locale);
return ListItem<Locale?>.options( return ListItem<Locale?>.options(
leading: const Icon(Icons.language_outlined), leading: const Icon(Icons.language_outlined),
title: Text(appLocalizations.language), title: Text(appLocalizations.language),

View File

@@ -372,5 +372,18 @@
"generalDesc": "Modify general settings", "generalDesc": "Modify general settings",
"findProcessModeDesc": "There is a certain performance loss after opening", "findProcessModeDesc": "There is a certain performance loss after opening",
"tabAnimationDesc": "Effective only in mobile view", "tabAnimationDesc": "Effective only in mobile view",
"saveTip": "Are you sure you want to save?" "saveTip": "Are you sure you want to save?",
"deleteColorTip": "Are you sure you want to delete the current color?",
"colorExists": "Current color already exists",
"colorSchemes": "Color schemes",
"palette": "Palette",
"tonalSpotScheme": "TonalSpot",
"fidelityScheme": "Fidelity",
"monochromeScheme": "Monochrome",
"neutralScheme": "Neutral",
"vibrantScheme": "Vibrant",
"expressiveScheme": "Expressive",
"contentScheme": "Content",
"rainbowScheme": "Rainbow",
"fruitSaladScheme": "FruitSalad"
} }

View File

@@ -372,5 +372,18 @@
"generalDesc": "一般設定を変更", "generalDesc": "一般設定を変更",
"findProcessModeDesc": "有効化するとパフォーマンスが若干低下します", "findProcessModeDesc": "有効化するとパフォーマンスが若干低下します",
"tabAnimationDesc": "モバイル表示でのみ有効", "tabAnimationDesc": "モバイル表示でのみ有効",
"saveTip": "保存してもよろしいですか?" "saveTip": "保存してもよろしいですか?",
"deleteColorTip": "現在の色を削除しますか?",
"colorExists": "この色は既に存在します",
"colorSchemes": "カラースキーム",
"palette": "パレット",
"tonalSpotScheme": "トーンスポット",
"fidelityScheme": "ハイファイデリティー",
"monochromeScheme": "モノクローム",
"neutralScheme": "ニュートラル",
"vibrantScheme": "ビブラント",
"expressiveScheme": "エクスプレッシブ",
"contentScheme": "コンテンツテーマ",
"rainbowScheme": "レインボー",
"fruitSaladScheme": "フルーツサラダ"
} }

View File

@@ -372,5 +372,18 @@
"generalDesc": "Изменение общих настроек", "generalDesc": "Изменение общих настроек",
"findProcessModeDesc": "При включении возможны небольшие потери производительности", "findProcessModeDesc": "При включении возможны небольшие потери производительности",
"tabAnimationDesc": "Действительно только в мобильном виде", "tabAnimationDesc": "Действительно только в мобильном виде",
"saveTip": "Вы уверены, что хотите сохранить?" "saveTip": "Вы уверены, что хотите сохранить?",
"deleteColorTip": "Удалить текущий цвет?",
"colorExists": "Этот цвет уже существует",
"colorSchemes": "Цветовые схемы",
"palette": "Палитра",
"tonalSpotScheme": "Тональный акцент",
"fidelityScheme": "Точная передача",
"monochromeScheme": "Монохром",
"neutralScheme": "Нейтральные",
"vibrantScheme": "Яркие",
"expressiveScheme": "Экспрессивные",
"contentScheme": "Контентная тема",
"rainbowScheme": "Радужные",
"fruitSaladScheme": "Фруктовый микс"
} }

View File

@@ -372,5 +372,18 @@
"generalDesc": "修改通用设置", "generalDesc": "修改通用设置",
"findProcessModeDesc": "开启后会有一定性能损耗", "findProcessModeDesc": "开启后会有一定性能损耗",
"tabAnimationDesc": "仅在移动视图中有效", "tabAnimationDesc": "仅在移动视图中有效",
"saveTip": "确定要保存吗?" "saveTip": "确定要保存吗?",
"deleteColorTip": "确定删除当前颜色吗?",
"colorExists": "该颜色已存在",
"colorSchemes": "配色方案",
"palette": "调色板",
"tonalSpotScheme": "调性点缀",
"fidelityScheme": "高保真",
"monochromeScheme": "单色",
"neutralScheme": "中性",
"vibrantScheme": "活力",
"expressiveScheme": "表现力",
"contentScheme": "内容主题",
"rainbowScheme": "彩虹",
"fruitSaladScheme": "果缤纷"
} }

View File

@@ -148,6 +148,10 @@ class MessageLookup extends MessageLookupByLibrary {
"checking": MessageLookupByLibrary.simpleMessage("Checking..."), "checking": MessageLookupByLibrary.simpleMessage("Checking..."),
"clipboardExport": MessageLookupByLibrary.simpleMessage("Export clipboard"), "clipboardExport": MessageLookupByLibrary.simpleMessage("Export clipboard"),
"clipboardImport": MessageLookupByLibrary.simpleMessage("Clipboard import"), "clipboardImport": MessageLookupByLibrary.simpleMessage("Clipboard import"),
"colorExists": MessageLookupByLibrary.simpleMessage(
"Current color already exists",
),
"colorSchemes": MessageLookupByLibrary.simpleMessage("Color schemes"),
"columns": MessageLookupByLibrary.simpleMessage("Columns"), "columns": MessageLookupByLibrary.simpleMessage("Columns"),
"compatible": MessageLookupByLibrary.simpleMessage("Compatibility mode"), "compatible": MessageLookupByLibrary.simpleMessage("Compatibility mode"),
"compatibleDesc": MessageLookupByLibrary.simpleMessage( "compatibleDesc": MessageLookupByLibrary.simpleMessage(
@@ -163,6 +167,7 @@ class MessageLookup extends MessageLookupByLibrary {
"contentEmptyTip": MessageLookupByLibrary.simpleMessage( "contentEmptyTip": MessageLookupByLibrary.simpleMessage(
"Content cannot be empty", "Content cannot be empty",
), ),
"contentScheme": MessageLookupByLibrary.simpleMessage("Content"),
"copy": MessageLookupByLibrary.simpleMessage("Copy"), "copy": MessageLookupByLibrary.simpleMessage("Copy"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage( "copyEnvVar": MessageLookupByLibrary.simpleMessage(
"Copying environment variables", "Copying environment variables",
@@ -188,6 +193,9 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("Delay"), "delay": MessageLookupByLibrary.simpleMessage("Delay"),
"delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"), "delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"),
"delete": MessageLookupByLibrary.simpleMessage("Delete"), "delete": MessageLookupByLibrary.simpleMessage("Delete"),
"deleteColorTip": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to delete the current color?",
),
"deleteProfileTip": MessageLookupByLibrary.simpleMessage( "deleteProfileTip": MessageLookupByLibrary.simpleMessage(
"Sure you want to delete the current profile?", "Sure you want to delete the current profile?",
), ),
@@ -234,6 +242,7 @@ class MessageLookup extends MessageLookupByLibrary {
"exportFile": MessageLookupByLibrary.simpleMessage("Export file"), "exportFile": MessageLookupByLibrary.simpleMessage("Export file"),
"exportLogs": MessageLookupByLibrary.simpleMessage("Export logs"), "exportLogs": MessageLookupByLibrary.simpleMessage("Export logs"),
"exportSuccess": MessageLookupByLibrary.simpleMessage("Export Success"), "exportSuccess": MessageLookupByLibrary.simpleMessage("Export Success"),
"expressiveScheme": MessageLookupByLibrary.simpleMessage("Expressive"),
"externalController": MessageLookupByLibrary.simpleMessage( "externalController": MessageLookupByLibrary.simpleMessage(
"ExternalController", "ExternalController",
), ),
@@ -251,6 +260,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Generally use offshore DNS", "Generally use offshore DNS",
), ),
"fallbackFilter": MessageLookupByLibrary.simpleMessage("Fallback filter"), "fallbackFilter": MessageLookupByLibrary.simpleMessage("Fallback filter"),
"fidelityScheme": MessageLookupByLibrary.simpleMessage("Fidelity"),
"file": MessageLookupByLibrary.simpleMessage("File"), "file": MessageLookupByLibrary.simpleMessage("File"),
"fileDesc": MessageLookupByLibrary.simpleMessage("Directly upload profile"), "fileDesc": MessageLookupByLibrary.simpleMessage("Directly upload profile"),
"fileIsUpdate": MessageLookupByLibrary.simpleMessage( "fileIsUpdate": MessageLookupByLibrary.simpleMessage(
@@ -265,6 +275,7 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"fontFamily": MessageLookupByLibrary.simpleMessage("FontFamily"), "fontFamily": MessageLookupByLibrary.simpleMessage("FontFamily"),
"fourColumns": MessageLookupByLibrary.simpleMessage("Four columns"), "fourColumns": MessageLookupByLibrary.simpleMessage("Four columns"),
"fruitSaladScheme": MessageLookupByLibrary.simpleMessage("FruitSalad"),
"general": MessageLookupByLibrary.simpleMessage("General"), "general": MessageLookupByLibrary.simpleMessage("General"),
"generalDesc": MessageLookupByLibrary.simpleMessage( "generalDesc": MessageLookupByLibrary.simpleMessage(
"Modify general settings", "Modify general settings",
@@ -358,6 +369,7 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"minutes": MessageLookupByLibrary.simpleMessage("Minutes"), "minutes": MessageLookupByLibrary.simpleMessage("Minutes"),
"mode": MessageLookupByLibrary.simpleMessage("Mode"), "mode": MessageLookupByLibrary.simpleMessage("Mode"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Monochrome"),
"months": MessageLookupByLibrary.simpleMessage("Months"), "months": MessageLookupByLibrary.simpleMessage("Months"),
"more": MessageLookupByLibrary.simpleMessage("More"), "more": MessageLookupByLibrary.simpleMessage("More"),
"name": MessageLookupByLibrary.simpleMessage("Name"), "name": MessageLookupByLibrary.simpleMessage("Name"),
@@ -380,6 +392,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Network detection", "Network detection",
), ),
"networkSpeed": MessageLookupByLibrary.simpleMessage("Network speed"), "networkSpeed": MessageLookupByLibrary.simpleMessage("Network speed"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("Neutral"),
"noData": MessageLookupByLibrary.simpleMessage("No data"), "noData": MessageLookupByLibrary.simpleMessage("No data"),
"noHotKey": MessageLookupByLibrary.simpleMessage("No HotKey"), "noHotKey": MessageLookupByLibrary.simpleMessage("No HotKey"),
"noIcon": MessageLookupByLibrary.simpleMessage("None"), "noIcon": MessageLookupByLibrary.simpleMessage("None"),
@@ -436,6 +449,7 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideOriginRules": MessageLookupByLibrary.simpleMessage( "overrideOriginRules": MessageLookupByLibrary.simpleMessage(
"Override the original rule", "Override the original rule",
), ),
"palette": MessageLookupByLibrary.simpleMessage("Palette"),
"password": MessageLookupByLibrary.simpleMessage("Password"), "password": MessageLookupByLibrary.simpleMessage("Password"),
"passwordTip": MessageLookupByLibrary.simpleMessage( "passwordTip": MessageLookupByLibrary.simpleMessage(
"Password cannot be empty", "Password cannot be empty",
@@ -506,6 +520,7 @@ class MessageLookup extends MessageLookupByLibrary {
"qrcodeDesc": MessageLookupByLibrary.simpleMessage( "qrcodeDesc": MessageLookupByLibrary.simpleMessage(
"Scan QR code to obtain profile", "Scan QR code to obtain profile",
), ),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("Rainbow"),
"recovery": MessageLookupByLibrary.simpleMessage("Recovery"), "recovery": MessageLookupByLibrary.simpleMessage("Recovery"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("Recovery all data"), "recoveryAll": MessageLookupByLibrary.simpleMessage("Recovery all data"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage( "recoveryProfiles": MessageLookupByLibrary.simpleMessage(
@@ -623,6 +638,7 @@ class MessageLookup extends MessageLookupByLibrary {
"time": MessageLookupByLibrary.simpleMessage("Time"), "time": MessageLookupByLibrary.simpleMessage("Time"),
"tip": MessageLookupByLibrary.simpleMessage("tip"), "tip": MessageLookupByLibrary.simpleMessage("tip"),
"toggle": MessageLookupByLibrary.simpleMessage("Toggle"), "toggle": MessageLookupByLibrary.simpleMessage("Toggle"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("TonalSpot"),
"tools": MessageLookupByLibrary.simpleMessage("Tools"), "tools": MessageLookupByLibrary.simpleMessage("Tools"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"), "trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"),
"tun": MessageLookupByLibrary.simpleMessage("TUN"), "tun": MessageLookupByLibrary.simpleMessage("TUN"),
@@ -651,6 +667,7 @@ class MessageLookup extends MessageLookupByLibrary {
"valueExists": MessageLookupByLibrary.simpleMessage( "valueExists": MessageLookupByLibrary.simpleMessage(
"The current value already exists", "The current value already exists",
), ),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("Vibrant"),
"view": MessageLookupByLibrary.simpleMessage("View"), "view": MessageLookupByLibrary.simpleMessage("View"),
"vpnDesc": MessageLookupByLibrary.simpleMessage( "vpnDesc": MessageLookupByLibrary.simpleMessage(
"Modify VPN related settings", "Modify VPN related settings",

View File

@@ -106,6 +106,8 @@ class MessageLookup extends MessageLookupByLibrary {
"checking": MessageLookupByLibrary.simpleMessage("確認中..."), "checking": MessageLookupByLibrary.simpleMessage("確認中..."),
"clipboardExport": MessageLookupByLibrary.simpleMessage("クリップボードにエクスポート"), "clipboardExport": MessageLookupByLibrary.simpleMessage("クリップボードにエクスポート"),
"clipboardImport": MessageLookupByLibrary.simpleMessage("クリップボードからインポート"), "clipboardImport": MessageLookupByLibrary.simpleMessage("クリップボードからインポート"),
"colorExists": MessageLookupByLibrary.simpleMessage("この色は既に存在します"),
"colorSchemes": MessageLookupByLibrary.simpleMessage("カラースキーム"),
"columns": MessageLookupByLibrary.simpleMessage(""), "columns": MessageLookupByLibrary.simpleMessage(""),
"compatible": MessageLookupByLibrary.simpleMessage("互換モード"), "compatible": MessageLookupByLibrary.simpleMessage("互換モード"),
"compatibleDesc": MessageLookupByLibrary.simpleMessage( "compatibleDesc": MessageLookupByLibrary.simpleMessage(
@@ -117,6 +119,7 @@ class MessageLookup extends MessageLookupByLibrary {
"connectivity": MessageLookupByLibrary.simpleMessage("接続性:"), "connectivity": MessageLookupByLibrary.simpleMessage("接続性:"),
"content": MessageLookupByLibrary.simpleMessage("内容"), "content": MessageLookupByLibrary.simpleMessage("内容"),
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容は必須です"), "contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容は必須です"),
"contentScheme": MessageLookupByLibrary.simpleMessage("コンテンツテーマ"),
"copy": MessageLookupByLibrary.simpleMessage("コピー"), "copy": MessageLookupByLibrary.simpleMessage("コピー"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage("環境変数をコピー"), "copyEnvVar": MessageLookupByLibrary.simpleMessage("環境変数をコピー"),
"copyLink": MessageLookupByLibrary.simpleMessage("リンクをコピー"), "copyLink": MessageLookupByLibrary.simpleMessage("リンクをコピー"),
@@ -138,6 +141,7 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("遅延"), "delay": MessageLookupByLibrary.simpleMessage("遅延"),
"delaySort": MessageLookupByLibrary.simpleMessage("遅延順"), "delaySort": MessageLookupByLibrary.simpleMessage("遅延順"),
"delete": MessageLookupByLibrary.simpleMessage("削除"), "delete": MessageLookupByLibrary.simpleMessage("削除"),
"deleteColorTip": MessageLookupByLibrary.simpleMessage("現在の色を削除しますか?"),
"deleteProfileTip": MessageLookupByLibrary.simpleMessage( "deleteProfileTip": MessageLookupByLibrary.simpleMessage(
"現在のプロファイルを削除しますか?", "現在のプロファイルを削除しますか?",
), ),
@@ -172,6 +176,7 @@ class MessageLookup extends MessageLookupByLibrary {
"exportFile": MessageLookupByLibrary.simpleMessage("ファイルをエクスポート"), "exportFile": MessageLookupByLibrary.simpleMessage("ファイルをエクスポート"),
"exportLogs": MessageLookupByLibrary.simpleMessage("ログをエクスポート"), "exportLogs": MessageLookupByLibrary.simpleMessage("ログをエクスポート"),
"exportSuccess": MessageLookupByLibrary.simpleMessage("エクスポート成功"), "exportSuccess": MessageLookupByLibrary.simpleMessage("エクスポート成功"),
"expressiveScheme": MessageLookupByLibrary.simpleMessage("エクスプレッシブ"),
"externalController": MessageLookupByLibrary.simpleMessage("外部コントローラー"), "externalController": MessageLookupByLibrary.simpleMessage("外部コントローラー"),
"externalControllerDesc": MessageLookupByLibrary.simpleMessage( "externalControllerDesc": MessageLookupByLibrary.simpleMessage(
"有効化するとClashコアをポート9090で制御可能", "有効化するとClashコアをポート9090で制御可能",
@@ -183,6 +188,7 @@ class MessageLookup extends MessageLookupByLibrary {
"fallback": MessageLookupByLibrary.simpleMessage("フォールバック"), "fallback": MessageLookupByLibrary.simpleMessage("フォールバック"),
"fallbackDesc": MessageLookupByLibrary.simpleMessage("通常はオフショアDNSを使用"), "fallbackDesc": MessageLookupByLibrary.simpleMessage("通常はオフショアDNSを使用"),
"fallbackFilter": MessageLookupByLibrary.simpleMessage("フォールバックフィルター"), "fallbackFilter": MessageLookupByLibrary.simpleMessage("フォールバックフィルター"),
"fidelityScheme": MessageLookupByLibrary.simpleMessage("ハイファイデリティー"),
"file": MessageLookupByLibrary.simpleMessage("ファイル"), "file": MessageLookupByLibrary.simpleMessage("ファイル"),
"fileDesc": MessageLookupByLibrary.simpleMessage("プロファイルを直接アップロード"), "fileDesc": MessageLookupByLibrary.simpleMessage("プロファイルを直接アップロード"),
"fileIsUpdate": MessageLookupByLibrary.simpleMessage( "fileIsUpdate": MessageLookupByLibrary.simpleMessage(
@@ -195,6 +201,7 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"fontFamily": MessageLookupByLibrary.simpleMessage("フォントファミリー"), "fontFamily": MessageLookupByLibrary.simpleMessage("フォントファミリー"),
"fourColumns": MessageLookupByLibrary.simpleMessage("4列"), "fourColumns": MessageLookupByLibrary.simpleMessage("4列"),
"fruitSaladScheme": MessageLookupByLibrary.simpleMessage("フルーツサラダ"),
"general": MessageLookupByLibrary.simpleMessage("一般"), "general": MessageLookupByLibrary.simpleMessage("一般"),
"generalDesc": MessageLookupByLibrary.simpleMessage("一般設定を変更"), "generalDesc": MessageLookupByLibrary.simpleMessage("一般設定を変更"),
"geoData": MessageLookupByLibrary.simpleMessage("地域データ"), "geoData": MessageLookupByLibrary.simpleMessage("地域データ"),
@@ -258,6 +265,7 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"minutes": MessageLookupByLibrary.simpleMessage(""), "minutes": MessageLookupByLibrary.simpleMessage(""),
"mode": MessageLookupByLibrary.simpleMessage("モード"), "mode": MessageLookupByLibrary.simpleMessage("モード"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("モノクローム"),
"months": MessageLookupByLibrary.simpleMessage(""), "months": MessageLookupByLibrary.simpleMessage(""),
"more": MessageLookupByLibrary.simpleMessage("詳細"), "more": MessageLookupByLibrary.simpleMessage("詳細"),
"name": MessageLookupByLibrary.simpleMessage("名前"), "name": MessageLookupByLibrary.simpleMessage("名前"),
@@ -272,6 +280,7 @@ class MessageLookup extends MessageLookupByLibrary {
"networkDesc": MessageLookupByLibrary.simpleMessage("ネットワーク関連設定の変更"), "networkDesc": MessageLookupByLibrary.simpleMessage("ネットワーク関連設定の変更"),
"networkDetection": MessageLookupByLibrary.simpleMessage("ネットワーク検出"), "networkDetection": MessageLookupByLibrary.simpleMessage("ネットワーク検出"),
"networkSpeed": MessageLookupByLibrary.simpleMessage("ネットワーク速度"), "networkSpeed": MessageLookupByLibrary.simpleMessage("ネットワーク速度"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("ニュートラル"),
"noData": MessageLookupByLibrary.simpleMessage("データなし"), "noData": MessageLookupByLibrary.simpleMessage("データなし"),
"noHotKey": MessageLookupByLibrary.simpleMessage("ホットキーなし"), "noHotKey": MessageLookupByLibrary.simpleMessage("ホットキーなし"),
"noIcon": MessageLookupByLibrary.simpleMessage("なし"), "noIcon": MessageLookupByLibrary.simpleMessage("なし"),
@@ -314,6 +323,7 @@ class MessageLookup extends MessageLookupByLibrary {
"有効化するとプロファイルのDNS設定を上書き", "有効化するとプロファイルのDNS設定を上書き",
), ),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを上書き"), "overrideOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを上書き"),
"palette": MessageLookupByLibrary.simpleMessage("パレット"),
"password": MessageLookupByLibrary.simpleMessage("パスワード"), "password": MessageLookupByLibrary.simpleMessage("パスワード"),
"passwordTip": MessageLookupByLibrary.simpleMessage("パスワードは必須です"), "passwordTip": MessageLookupByLibrary.simpleMessage("パスワードは必須です"),
"paste": MessageLookupByLibrary.simpleMessage("貼り付け"), "paste": MessageLookupByLibrary.simpleMessage("貼り付け"),
@@ -370,6 +380,7 @@ class MessageLookup extends MessageLookupByLibrary {
"pureBlackMode": MessageLookupByLibrary.simpleMessage("純黒モード"), "pureBlackMode": MessageLookupByLibrary.simpleMessage("純黒モード"),
"qrcode": MessageLookupByLibrary.simpleMessage("QRコード"), "qrcode": MessageLookupByLibrary.simpleMessage("QRコード"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("QRコードをスキャンしてプロファイルを取得"), "qrcodeDesc": MessageLookupByLibrary.simpleMessage("QRコードをスキャンしてプロファイルを取得"),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("レインボー"),
"recovery": MessageLookupByLibrary.simpleMessage("復元"), "recovery": MessageLookupByLibrary.simpleMessage("復元"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("全データ復元"), "recoveryAll": MessageLookupByLibrary.simpleMessage("全データ復元"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("プロファイルのみ復元"), "recoveryProfiles": MessageLookupByLibrary.simpleMessage("プロファイルのみ復元"),
@@ -461,6 +472,7 @@ class MessageLookup extends MessageLookupByLibrary {
"time": MessageLookupByLibrary.simpleMessage("時間"), "time": MessageLookupByLibrary.simpleMessage("時間"),
"tip": MessageLookupByLibrary.simpleMessage("ヒント"), "tip": MessageLookupByLibrary.simpleMessage("ヒント"),
"toggle": MessageLookupByLibrary.simpleMessage("トグル"), "toggle": MessageLookupByLibrary.simpleMessage("トグル"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("トーンスポット"),
"tools": MessageLookupByLibrary.simpleMessage("ツール"), "tools": MessageLookupByLibrary.simpleMessage("ツール"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("トラフィック使用量"), "trafficUsage": MessageLookupByLibrary.simpleMessage("トラフィック使用量"),
"tun": MessageLookupByLibrary.simpleMessage("TUN"), "tun": MessageLookupByLibrary.simpleMessage("TUN"),
@@ -483,6 +495,7 @@ class MessageLookup extends MessageLookupByLibrary {
"useSystemHosts": MessageLookupByLibrary.simpleMessage("システムホストを使用"), "useSystemHosts": MessageLookupByLibrary.simpleMessage("システムホストを使用"),
"value": MessageLookupByLibrary.simpleMessage(""), "value": MessageLookupByLibrary.simpleMessage(""),
"valueExists": MessageLookupByLibrary.simpleMessage("現在の値は既に存在します"), "valueExists": MessageLookupByLibrary.simpleMessage("現在の値は既に存在します"),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("ビブラント"),
"view": MessageLookupByLibrary.simpleMessage("表示"), "view": MessageLookupByLibrary.simpleMessage("表示"),
"vpnDesc": MessageLookupByLibrary.simpleMessage("VPN関連設定の変更"), "vpnDesc": MessageLookupByLibrary.simpleMessage("VPN関連設定の変更"),
"vpnEnableDesc": MessageLookupByLibrary.simpleMessage( "vpnEnableDesc": MessageLookupByLibrary.simpleMessage(

View File

@@ -154,6 +154,10 @@ class MessageLookup extends MessageLookupByLibrary {
"clipboardImport": MessageLookupByLibrary.simpleMessage( "clipboardImport": MessageLookupByLibrary.simpleMessage(
"Импорт из буфера обмена", "Импорт из буфера обмена",
), ),
"colorExists": MessageLookupByLibrary.simpleMessage(
"Этот цвет уже существует",
),
"colorSchemes": MessageLookupByLibrary.simpleMessage("Цветовые схемы"),
"columns": MessageLookupByLibrary.simpleMessage("Столбцы"), "columns": MessageLookupByLibrary.simpleMessage("Столбцы"),
"compatible": MessageLookupByLibrary.simpleMessage("Режим совместимости"), "compatible": MessageLookupByLibrary.simpleMessage("Режим совместимости"),
"compatibleDesc": MessageLookupByLibrary.simpleMessage( "compatibleDesc": MessageLookupByLibrary.simpleMessage(
@@ -169,6 +173,7 @@ class MessageLookup extends MessageLookupByLibrary {
"contentEmptyTip": MessageLookupByLibrary.simpleMessage( "contentEmptyTip": MessageLookupByLibrary.simpleMessage(
"Содержание не может быть пустым", "Содержание не может быть пустым",
), ),
"contentScheme": MessageLookupByLibrary.simpleMessage("Контентная тема"),
"copy": MessageLookupByLibrary.simpleMessage("Копировать"), "copy": MessageLookupByLibrary.simpleMessage("Копировать"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage( "copyEnvVar": MessageLookupByLibrary.simpleMessage(
"Копирование переменных окружения", "Копирование переменных окружения",
@@ -196,6 +201,9 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("Задержка"), "delay": MessageLookupByLibrary.simpleMessage("Задержка"),
"delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"), "delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"),
"delete": MessageLookupByLibrary.simpleMessage("Удалить"), "delete": MessageLookupByLibrary.simpleMessage("Удалить"),
"deleteColorTip": MessageLookupByLibrary.simpleMessage(
"Удалить текущий цвет?",
),
"deleteProfileTip": MessageLookupByLibrary.simpleMessage( "deleteProfileTip": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите удалить текущий профиль?", "Вы уверены, что хотите удалить текущий профиль?",
), ),
@@ -248,6 +256,7 @@ class MessageLookup extends MessageLookupByLibrary {
"exportFile": MessageLookupByLibrary.simpleMessage("Экспорт файла"), "exportFile": MessageLookupByLibrary.simpleMessage("Экспорт файла"),
"exportLogs": MessageLookupByLibrary.simpleMessage("Экспорт логов"), "exportLogs": MessageLookupByLibrary.simpleMessage("Экспорт логов"),
"exportSuccess": MessageLookupByLibrary.simpleMessage("Экспорт успешен"), "exportSuccess": MessageLookupByLibrary.simpleMessage("Экспорт успешен"),
"expressiveScheme": MessageLookupByLibrary.simpleMessage("Экспрессивные"),
"externalController": MessageLookupByLibrary.simpleMessage( "externalController": MessageLookupByLibrary.simpleMessage(
"Внешний контроллер", "Внешний контроллер",
), ),
@@ -267,6 +276,7 @@ class MessageLookup extends MessageLookupByLibrary {
"fallbackFilter": MessageLookupByLibrary.simpleMessage( "fallbackFilter": MessageLookupByLibrary.simpleMessage(
"Фильтр резервного DNS", "Фильтр резервного DNS",
), ),
"fidelityScheme": MessageLookupByLibrary.simpleMessage("Точная передача"),
"file": MessageLookupByLibrary.simpleMessage("Файл"), "file": MessageLookupByLibrary.simpleMessage("Файл"),
"fileDesc": MessageLookupByLibrary.simpleMessage("Прямая загрузка профиля"), "fileDesc": MessageLookupByLibrary.simpleMessage("Прямая загрузка профиля"),
"fileIsUpdate": MessageLookupByLibrary.simpleMessage( "fileIsUpdate": MessageLookupByLibrary.simpleMessage(
@@ -283,6 +293,7 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"fontFamily": MessageLookupByLibrary.simpleMessage("Семейство шрифтов"), "fontFamily": MessageLookupByLibrary.simpleMessage("Семейство шрифтов"),
"fourColumns": MessageLookupByLibrary.simpleMessage("Четыре столбца"), "fourColumns": MessageLookupByLibrary.simpleMessage("Четыре столбца"),
"fruitSaladScheme": MessageLookupByLibrary.simpleMessage("Фруктовый микс"),
"general": MessageLookupByLibrary.simpleMessage("Общие"), "general": MessageLookupByLibrary.simpleMessage("Общие"),
"generalDesc": MessageLookupByLibrary.simpleMessage( "generalDesc": MessageLookupByLibrary.simpleMessage(
"Изменение общих настроек", "Изменение общих настроек",
@@ -384,6 +395,7 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"minutes": MessageLookupByLibrary.simpleMessage("Минут"), "minutes": MessageLookupByLibrary.simpleMessage("Минут"),
"mode": MessageLookupByLibrary.simpleMessage("Режим"), "mode": MessageLookupByLibrary.simpleMessage("Режим"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Монохром"),
"months": MessageLookupByLibrary.simpleMessage("Месяцев"), "months": MessageLookupByLibrary.simpleMessage("Месяцев"),
"more": MessageLookupByLibrary.simpleMessage("Еще"), "more": MessageLookupByLibrary.simpleMessage("Еще"),
"name": MessageLookupByLibrary.simpleMessage("Имя"), "name": MessageLookupByLibrary.simpleMessage("Имя"),
@@ -406,6 +418,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Обнаружение сети", "Обнаружение сети",
), ),
"networkSpeed": MessageLookupByLibrary.simpleMessage("Скорость сети"), "networkSpeed": MessageLookupByLibrary.simpleMessage("Скорость сети"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("Нейтральные"),
"noData": MessageLookupByLibrary.simpleMessage("Нет данных"), "noData": MessageLookupByLibrary.simpleMessage("Нет данных"),
"noHotKey": MessageLookupByLibrary.simpleMessage("Нет горячей клавиши"), "noHotKey": MessageLookupByLibrary.simpleMessage("Нет горячей клавиши"),
"noIcon": MessageLookupByLibrary.simpleMessage("Нет иконки"), "noIcon": MessageLookupByLibrary.simpleMessage("Нет иконки"),
@@ -466,6 +479,7 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideOriginRules": MessageLookupByLibrary.simpleMessage( "overrideOriginRules": MessageLookupByLibrary.simpleMessage(
"Переопределить оригинальное правило", "Переопределить оригинальное правило",
), ),
"palette": MessageLookupByLibrary.simpleMessage("Палитра"),
"password": MessageLookupByLibrary.simpleMessage("Пароль"), "password": MessageLookupByLibrary.simpleMessage("Пароль"),
"passwordTip": MessageLookupByLibrary.simpleMessage( "passwordTip": MessageLookupByLibrary.simpleMessage(
"Пароль не может быть пустым", "Пароль не может быть пустым",
@@ -538,6 +552,7 @@ class MessageLookup extends MessageLookupByLibrary {
"qrcodeDesc": MessageLookupByLibrary.simpleMessage( "qrcodeDesc": MessageLookupByLibrary.simpleMessage(
"Сканируйте QR-код для получения профиля", "Сканируйте QR-код для получения профиля",
), ),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("Радужные"),
"recovery": MessageLookupByLibrary.simpleMessage("Восстановление"), "recovery": MessageLookupByLibrary.simpleMessage("Восстановление"),
"recoveryAll": MessageLookupByLibrary.simpleMessage( "recoveryAll": MessageLookupByLibrary.simpleMessage(
"Восстановить все данные", "Восстановить все данные",
@@ -661,6 +676,7 @@ class MessageLookup extends MessageLookupByLibrary {
"time": MessageLookupByLibrary.simpleMessage("Время"), "time": MessageLookupByLibrary.simpleMessage("Время"),
"tip": MessageLookupByLibrary.simpleMessage("подсказка"), "tip": MessageLookupByLibrary.simpleMessage("подсказка"),
"toggle": MessageLookupByLibrary.simpleMessage("Переключить"), "toggle": MessageLookupByLibrary.simpleMessage("Переключить"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("Тональный акцент"),
"tools": MessageLookupByLibrary.simpleMessage("Инструменты"), "tools": MessageLookupByLibrary.simpleMessage("Инструменты"),
"trafficUsage": MessageLookupByLibrary.simpleMessage( "trafficUsage": MessageLookupByLibrary.simpleMessage(
"Использование трафика", "Использование трафика",
@@ -695,6 +711,7 @@ class MessageLookup extends MessageLookupByLibrary {
"valueExists": MessageLookupByLibrary.simpleMessage( "valueExists": MessageLookupByLibrary.simpleMessage(
"Текущее значение уже существует", "Текущее значение уже существует",
), ),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("Яркие"),
"view": MessageLookupByLibrary.simpleMessage("Просмотр"), "view": MessageLookupByLibrary.simpleMessage("Просмотр"),
"vpnDesc": MessageLookupByLibrary.simpleMessage( "vpnDesc": MessageLookupByLibrary.simpleMessage(
"Изменение настроек, связанных с VPN", "Изменение настроек, связанных с VPN",

View File

@@ -96,6 +96,8 @@ class MessageLookup extends MessageLookupByLibrary {
"checking": MessageLookupByLibrary.simpleMessage("检测中..."), "checking": MessageLookupByLibrary.simpleMessage("检测中..."),
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"), "clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"), "clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
"colorExists": MessageLookupByLibrary.simpleMessage("该颜色已存在"),
"colorSchemes": MessageLookupByLibrary.simpleMessage("配色方案"),
"columns": MessageLookupByLibrary.simpleMessage("列数"), "columns": MessageLookupByLibrary.simpleMessage("列数"),
"compatible": MessageLookupByLibrary.simpleMessage("兼容模式"), "compatible": MessageLookupByLibrary.simpleMessage("兼容模式"),
"compatibleDesc": MessageLookupByLibrary.simpleMessage( "compatibleDesc": MessageLookupByLibrary.simpleMessage(
@@ -107,6 +109,7 @@ class MessageLookup extends MessageLookupByLibrary {
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"), "connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
"content": MessageLookupByLibrary.simpleMessage("内容"), "content": MessageLookupByLibrary.simpleMessage("内容"),
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容不能为空"), "contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容不能为空"),
"contentScheme": MessageLookupByLibrary.simpleMessage("内容主题"),
"copy": MessageLookupByLibrary.simpleMessage("复制"), "copy": MessageLookupByLibrary.simpleMessage("复制"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage("复制环境变量"), "copyEnvVar": MessageLookupByLibrary.simpleMessage("复制环境变量"),
"copyLink": MessageLookupByLibrary.simpleMessage("复制链接"), "copyLink": MessageLookupByLibrary.simpleMessage("复制链接"),
@@ -126,6 +129,7 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("延迟"), "delay": MessageLookupByLibrary.simpleMessage("延迟"),
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"), "delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
"delete": MessageLookupByLibrary.simpleMessage("删除"), "delete": MessageLookupByLibrary.simpleMessage("删除"),
"deleteColorTip": MessageLookupByLibrary.simpleMessage("确定删除当前颜色吗?"),
"deleteProfileTip": MessageLookupByLibrary.simpleMessage("确定要删除当前配置吗?"), "deleteProfileTip": MessageLookupByLibrary.simpleMessage("确定要删除当前配置吗?"),
"deleteRuleTip": MessageLookupByLibrary.simpleMessage("确定要删除选中的规则吗?"), "deleteRuleTip": MessageLookupByLibrary.simpleMessage("确定要删除选中的规则吗?"),
"desc": MessageLookupByLibrary.simpleMessage( "desc": MessageLookupByLibrary.simpleMessage(
@@ -156,6 +160,7 @@ class MessageLookup extends MessageLookupByLibrary {
"exportFile": MessageLookupByLibrary.simpleMessage("导出文件"), "exportFile": MessageLookupByLibrary.simpleMessage("导出文件"),
"exportLogs": MessageLookupByLibrary.simpleMessage("导出日志"), "exportLogs": MessageLookupByLibrary.simpleMessage("导出日志"),
"exportSuccess": MessageLookupByLibrary.simpleMessage("导出成功"), "exportSuccess": MessageLookupByLibrary.simpleMessage("导出成功"),
"expressiveScheme": MessageLookupByLibrary.simpleMessage("表现力"),
"externalController": MessageLookupByLibrary.simpleMessage("外部控制器"), "externalController": MessageLookupByLibrary.simpleMessage("外部控制器"),
"externalControllerDesc": MessageLookupByLibrary.simpleMessage( "externalControllerDesc": MessageLookupByLibrary.simpleMessage(
"开启后将可以通过9090端口控制Clash内核", "开启后将可以通过9090端口控制Clash内核",
@@ -167,6 +172,7 @@ class MessageLookup extends MessageLookupByLibrary {
"fallback": MessageLookupByLibrary.simpleMessage("Fallback"), "fallback": MessageLookupByLibrary.simpleMessage("Fallback"),
"fallbackDesc": MessageLookupByLibrary.simpleMessage("一般情况下使用境外DNS"), "fallbackDesc": MessageLookupByLibrary.simpleMessage("一般情况下使用境外DNS"),
"fallbackFilter": MessageLookupByLibrary.simpleMessage("Fallback过滤"), "fallbackFilter": MessageLookupByLibrary.simpleMessage("Fallback过滤"),
"fidelityScheme": MessageLookupByLibrary.simpleMessage("高保真"),
"file": MessageLookupByLibrary.simpleMessage("文件"), "file": MessageLookupByLibrary.simpleMessage("文件"),
"fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"), "fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
"fileIsUpdate": MessageLookupByLibrary.simpleMessage("文件有修改,是否保存修改"), "fileIsUpdate": MessageLookupByLibrary.simpleMessage("文件有修改,是否保存修改"),
@@ -175,6 +181,7 @@ class MessageLookup extends MessageLookupByLibrary {
"findProcessModeDesc": MessageLookupByLibrary.simpleMessage("开启后会有一定性能损耗"), "findProcessModeDesc": MessageLookupByLibrary.simpleMessage("开启后会有一定性能损耗"),
"fontFamily": MessageLookupByLibrary.simpleMessage("字体"), "fontFamily": MessageLookupByLibrary.simpleMessage("字体"),
"fourColumns": MessageLookupByLibrary.simpleMessage("四列"), "fourColumns": MessageLookupByLibrary.simpleMessage("四列"),
"fruitSaladScheme": MessageLookupByLibrary.simpleMessage("果缤纷"),
"general": MessageLookupByLibrary.simpleMessage("常规"), "general": MessageLookupByLibrary.simpleMessage("常规"),
"generalDesc": MessageLookupByLibrary.simpleMessage("修改通用设置"), "generalDesc": MessageLookupByLibrary.simpleMessage("修改通用设置"),
"geoData": MessageLookupByLibrary.simpleMessage("地理数据"), "geoData": MessageLookupByLibrary.simpleMessage("地理数据"),
@@ -230,6 +237,7 @@ class MessageLookup extends MessageLookupByLibrary {
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"), "minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
"minutes": MessageLookupByLibrary.simpleMessage("分钟"), "minutes": MessageLookupByLibrary.simpleMessage("分钟"),
"mode": MessageLookupByLibrary.simpleMessage("模式"), "mode": MessageLookupByLibrary.simpleMessage("模式"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("单色"),
"months": MessageLookupByLibrary.simpleMessage(""), "months": MessageLookupByLibrary.simpleMessage(""),
"more": MessageLookupByLibrary.simpleMessage("更多"), "more": MessageLookupByLibrary.simpleMessage("更多"),
"name": MessageLookupByLibrary.simpleMessage("名称"), "name": MessageLookupByLibrary.simpleMessage("名称"),
@@ -242,6 +250,7 @@ class MessageLookup extends MessageLookupByLibrary {
"networkDesc": MessageLookupByLibrary.simpleMessage("修改网络相关设置"), "networkDesc": MessageLookupByLibrary.simpleMessage("修改网络相关设置"),
"networkDetection": MessageLookupByLibrary.simpleMessage("网络检测"), "networkDetection": MessageLookupByLibrary.simpleMessage("网络检测"),
"networkSpeed": MessageLookupByLibrary.simpleMessage("网络速度"), "networkSpeed": MessageLookupByLibrary.simpleMessage("网络速度"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("中性"),
"noData": MessageLookupByLibrary.simpleMessage("暂无数据"), "noData": MessageLookupByLibrary.simpleMessage("暂无数据"),
"noHotKey": MessageLookupByLibrary.simpleMessage("暂无快捷键"), "noHotKey": MessageLookupByLibrary.simpleMessage("暂无快捷键"),
"noIcon": MessageLookupByLibrary.simpleMessage("无图标"), "noIcon": MessageLookupByLibrary.simpleMessage("无图标"),
@@ -276,6 +285,7 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideDns": MessageLookupByLibrary.simpleMessage("覆写DNS"), "overrideDns": MessageLookupByLibrary.simpleMessage("覆写DNS"),
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage("开启后将覆盖配置中的DNS选项"), "overrideDnsDesc": MessageLookupByLibrary.simpleMessage("开启后将覆盖配置中的DNS选项"),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("覆盖原始规则"), "overrideOriginRules": MessageLookupByLibrary.simpleMessage("覆盖原始规则"),
"palette": MessageLookupByLibrary.simpleMessage("调色板"),
"password": MessageLookupByLibrary.simpleMessage("密码"), "password": MessageLookupByLibrary.simpleMessage("密码"),
"passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"), "passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"),
"paste": MessageLookupByLibrary.simpleMessage("粘贴"), "paste": MessageLookupByLibrary.simpleMessage("粘贴"),
@@ -324,6 +334,7 @@ class MessageLookup extends MessageLookupByLibrary {
"pureBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"), "pureBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"), "qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"), "qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("彩虹"),
"recovery": MessageLookupByLibrary.simpleMessage("恢复"), "recovery": MessageLookupByLibrary.simpleMessage("恢复"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"), "recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"), "recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
@@ -405,6 +416,7 @@ class MessageLookup extends MessageLookupByLibrary {
"time": MessageLookupByLibrary.simpleMessage("时间"), "time": MessageLookupByLibrary.simpleMessage("时间"),
"tip": MessageLookupByLibrary.simpleMessage("提示"), "tip": MessageLookupByLibrary.simpleMessage("提示"),
"toggle": MessageLookupByLibrary.simpleMessage("切换"), "toggle": MessageLookupByLibrary.simpleMessage("切换"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("调性点缀"),
"tools": MessageLookupByLibrary.simpleMessage("工具"), "tools": MessageLookupByLibrary.simpleMessage("工具"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"), "trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),
"tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"), "tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
@@ -425,6 +437,7 @@ class MessageLookup extends MessageLookupByLibrary {
"useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"), "useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"),
"value": MessageLookupByLibrary.simpleMessage(""), "value": MessageLookupByLibrary.simpleMessage(""),
"valueExists": MessageLookupByLibrary.simpleMessage("当前值已存在"), "valueExists": MessageLookupByLibrary.simpleMessage("当前值已存在"),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("活力"),
"view": MessageLookupByLibrary.simpleMessage("查看"), "view": MessageLookupByLibrary.simpleMessage("查看"),
"vpnDesc": MessageLookupByLibrary.simpleMessage("修改VPN相关设置"), "vpnDesc": MessageLookupByLibrary.simpleMessage("修改VPN相关设置"),
"vpnEnableDesc": MessageLookupByLibrary.simpleMessage( "vpnEnableDesc": MessageLookupByLibrary.simpleMessage(

View File

@@ -2904,6 +2904,106 @@ class AppLocalizations {
args: [], args: [],
); );
} }
/// `Are you sure you want to delete the current color?`
String get deleteColorTip {
return Intl.message(
'Are you sure you want to delete the current color?',
name: 'deleteColorTip',
desc: '',
args: [],
);
}
/// `Current color already exists`
String get colorExists {
return Intl.message(
'Current color already exists',
name: 'colorExists',
desc: '',
args: [],
);
}
/// `Color schemes`
String get colorSchemes {
return Intl.message(
'Color schemes',
name: 'colorSchemes',
desc: '',
args: [],
);
}
/// `Palette`
String get palette {
return Intl.message('Palette', name: 'palette', desc: '', args: []);
}
/// `TonalSpot`
String get tonalSpotScheme {
return Intl.message(
'TonalSpot',
name: 'tonalSpotScheme',
desc: '',
args: [],
);
}
/// `Fidelity`
String get fidelityScheme {
return Intl.message('Fidelity', name: 'fidelityScheme', desc: '', args: []);
}
/// `Monochrome`
String get monochromeScheme {
return Intl.message(
'Monochrome',
name: 'monochromeScheme',
desc: '',
args: [],
);
}
/// `Neutral`
String get neutralScheme {
return Intl.message('Neutral', name: 'neutralScheme', desc: '', args: []);
}
/// `Vibrant`
String get vibrantScheme {
return Intl.message('Vibrant', name: 'vibrantScheme', desc: '', args: []);
}
/// `Expressive`
String get expressiveScheme {
return Intl.message(
'Expressive',
name: 'expressiveScheme',
desc: '',
args: [],
);
}
/// `Content`
String get contentScheme {
return Intl.message('Content', name: 'contentScheme', desc: '', args: []);
}
/// `Rainbow`
String get rainbowScheme {
return Intl.message('Rainbow', name: 'rainbowScheme', desc: '', args: []);
}
/// `FruitSalad`
String get fruitSaladScheme {
return Intl.message(
'FruitSalad',
name: 'fruitSaladScheme',
desc: '',
args: [],
);
}
} }
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> { class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -5,7 +5,7 @@ import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'dart:ui'; import 'dart:ui';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/core.dart';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/tile.dart'; import 'package:fl_clash/plugins/tile.dart';
import 'package:fl_clash/plugins/vpn.dart'; import 'package:fl_clash/plugins/vpn.dart';
@@ -17,7 +17,6 @@ import 'application.dart';
import 'clash/core.dart'; import 'clash/core.dart';
import 'clash/lib.dart'; import 'clash/lib.dart';
import 'common/common.dart'; import 'common/common.dart';
import 'models/models.dart';
Future<void> main() async { Future<void> main() async {
globalState.isService = false; globalState.isService = false;
@@ -47,7 +46,6 @@ Future<void> _service(List<String> flags) async {
onStop: () async { onStop: () async {
await app?.tip(appLocalizations.stopVpn); await app?.tip(appLocalizations.stopVpn);
clashLibHandler.stopListener(); clashLibHandler.stopListener();
clashLibHandler.stopTun();
await vpn?.stop(); await vpn?.stop();
exit(0); exit(0);
}, },
@@ -64,43 +62,11 @@ Future<void> _service(List<String> flags) async {
vpn?.addListener( vpn?.addListener(
_VpnListenerWithService( _VpnListenerWithService(
onStarted: (int fd) {
commonPrint.log("vpn started fd: $fd");
final time = clashLibHandler.startTun(fd);
commonPrint.log("vpn start tun time: $time");
},
onDnsChanged: (String dns) { onDnsChanged: (String dns) {
clashLibHandler.updateDns(dns); clashLibHandler.updateDns(dns);
}, },
), ),
); );
final invokeReceiverPort = ReceivePort();
clashLibHandler.attachInvokePort(
invokeReceiverPort.sendPort.nativePort,
);
invokeReceiverPort.listen(
(message) async {
final invokeMessage = InvokeMessage.fromJson(json.decode(message));
switch (invokeMessage.type) {
case InvokeMessageType.protect:
final fd = Fd.fromJson(invokeMessage.data);
await vpn?.setProtect(fd.value);
clashLibHandler.setFdMap(fd.id);
case InvokeMessageType.process:
final process = ProcessData.fromJson(invokeMessage.data);
final processName = await vpn?.resolverProcess(process) ?? "";
clashLibHandler.setProcessMap(
ProcessMapItem(
id: process.id,
value: processName,
),
);
}
},
);
if (!quickStart) { if (!quickStart) {
_handleMainIpc(clashLibHandler); _handleMainIpc(clashLibHandler);
} else { } else {
@@ -108,9 +74,13 @@ Future<void> _service(List<String> flags) async {
await ClashCore.initGeo(); await ClashCore.initGeo();
app?.tip(appLocalizations.startVpn); app?.tip(appLocalizations.startVpn);
final homeDirPath = await appPath.homeDirPath; final homeDirPath = await appPath.homeDirPath;
final version = await system.version;
clashLibHandler clashLibHandler
.quickStart( .quickStart(
homeDirPath, InitParams(
homeDir: homeDirPath,
version: version,
),
globalState.getUpdateConfigParams(), globalState.getUpdateConfigParams(),
globalState.getCoreState(), globalState.getCoreState(),
) )
@@ -165,20 +135,11 @@ class _TileListenerWithService with TileListener {
@immutable @immutable
class _VpnListenerWithService with VpnListener { class _VpnListenerWithService with VpnListener {
final Function(int fd) _onStarted;
final Function(String dns) _onDnsChanged; final Function(String dns) _onDnsChanged;
const _VpnListenerWithService({ const _VpnListenerWithService({
required Function(int fd) onStarted,
required Function(String dns) onDnsChanged, required Function(String dns) onDnsChanged,
}) : _onStarted = onStarted, }) : _onDnsChanged = onDnsChanged;
_onDnsChanged = onDnsChanged;
@override
void onStarted(int fd) {
super.onStarted(fd);
_onStarted(fd);
}
@override @override
void onDnsChanged(String dns) { void onDnsChanged(String dns) {

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AppStateManager extends StatefulWidget { class AppStateManager extends StatefulWidget {
@@ -21,7 +22,6 @@ class _AppStateManagerState extends State<AppStateManager>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
} }
@@ -33,10 +33,10 @@ class _AppStateManagerState extends State<AppStateManager>
@override @override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async { Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
commonPrint.log("$state");
if (state == AppLifecycleState.paused || if (state == AppLifecycleState.paused ||
state == AppLifecycleState.inactive) { state == AppLifecycleState.inactive) {
globalState.appController.savePreferences(); globalState.appController.savePreferences();
render?.pause();
} else { } else {
render?.resume(); render?.resume();
} }
@@ -70,6 +70,15 @@ class AppEnvManager extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (kDebugMode) {
if (globalState.isPre) {
return Banner(
message: 'DEBUG',
location: BannerLocation.topEnd,
child: child,
);
}
}
if (globalState.isPre) { if (globalState.isPre) {
return Banner( return Banner(
message: 'PRE', message: 'PRE',

View File

@@ -36,7 +36,7 @@ class MessageManagerState extends State<MessageManager> {
Future<void> message(String text) async { Future<void> message(String text) async {
final commonMessage = CommonMessage( final commonMessage = CommonMessage(
id: other.uuidV4, id: utils.uuidV4,
text: text, text: text,
); );
_bufferMessages.add(commonMessage); _bufferMessages.add(commonMessage);
@@ -91,7 +91,7 @@ class MessageManagerState extends State<MessageManager> {
), ),
elevation: 10, elevation: 10,
margin: EdgeInsets.only( margin: EdgeInsets.only(
top: kToolbarHeight, top: kToolbarHeight + 8,
left: 12, left: 12,
right: 12, right: 12,
), ),

View File

@@ -60,15 +60,10 @@ class _WindowContainerState extends ConsumerState<WindowManager>
@override @override
void onWindowFocus() { void onWindowFocus() {
super.onWindowFocus(); super.onWindowFocus();
commonPrint.log("focus");
render?.resume(); render?.resume();
} }
@override
void onWindowBlur() {
super.onWindowBlur();
render?.pause();
}
@override @override
Future<void> onShouldTerminate() async { Future<void> onShouldTerminate() async {
await globalState.appController.handleExit(); await globalState.appController.handleExit();
@@ -102,6 +97,8 @@ class _WindowContainerState extends ConsumerState<WindowManager>
@override @override
void onWindowMinimize() async { void onWindowMinimize() async {
globalState.appController.savePreferencesDebounce(); globalState.appController.savePreferencesDebounce();
commonPrint.log("minimize");
render?.pause();
super.onWindowMinimize(); super.onWindowMinimize();
} }

View File

@@ -31,11 +31,12 @@ class AppState with _$AppState {
required FixedList<Log> logs, required FixedList<Log> logs,
required FixedList<Traffic> traffics, required FixedList<Traffic> traffics,
required Traffic totalTraffic, required Traffic totalTraffic,
@Default(false) bool needApply,
}) = _AppState; }) = _AppState;
} }
extension AppStateExt on AppState { extension AppStateExt on AppState {
ViewMode get viewMode => other.getViewMode(viewSize.width); ViewMode get viewMode => utils.getViewMode(viewSize.width);
bool get isStart => runTime != null; bool get isStart => runTime != null;
} }

View File

@@ -361,7 +361,7 @@ class Rule with _$Rule {
factory Rule.value(String value) { factory Rule.value(String value) {
return Rule( return Rule(
value: value, value: value,
id: other.uuidV4, id: utils.uuidV4,
); );
} }
@@ -426,12 +426,12 @@ class ClashConfig with _$ClashConfig {
@Default(defaultMixedPort) @JsonKey(name: "mixed-port") int mixedPort, @Default(defaultMixedPort) @JsonKey(name: "mixed-port") int mixedPort,
@Default(Mode.rule) Mode mode, @Default(Mode.rule) Mode mode,
@Default(false) @JsonKey(name: "allow-lan") bool allowLan, @Default(false) @JsonKey(name: "allow-lan") bool allowLan,
@Default(LogLevel.info) @JsonKey(name: "log-level") LogLevel logLevel, @Default(LogLevel.error) @JsonKey(name: "log-level") LogLevel logLevel,
@Default(false) bool ipv6, @Default(false) bool ipv6,
@Default(FindProcessMode.off) @Default(FindProcessMode.off)
@JsonKey( @JsonKey(
name: "find-process-mode", name: "find-process-mode",
unknownEnumValue: FindProcessMode.off, unknownEnumValue: FindProcessMode.always,
) )
FindProcessMode findProcessMode, FindProcessMode findProcessMode,
@Default(defaultKeepAliveInterval) @Default(defaultKeepAliveInterval)

View File

@@ -369,21 +369,32 @@ class ColorSchemes with _$ColorSchemes {
} }
extension ColorSchemesExt on ColorSchemes { extension ColorSchemesExt on ColorSchemes {
ColorScheme getColorSchemeForBrightness(Brightness? brightness) { ColorScheme getColorSchemeForBrightness(
Brightness brightness,
DynamicSchemeVariant schemeVariant,
) {
if (brightness == Brightness.dark) { if (brightness == Brightness.dark) {
return darkColorScheme != null return darkColorScheme != null
? ColorScheme.fromSeed( ? ColorScheme.fromSeed(
seedColor: darkColorScheme!.primary, seedColor: darkColorScheme!.primary,
brightness: Brightness.dark, brightness: Brightness.dark,
dynamicSchemeVariant: schemeVariant,
) )
: ColorScheme.fromSeed( : ColorScheme.fromSeed(
seedColor: defaultPrimaryColor, seedColor: Color(defaultPrimaryColor),
brightness: Brightness.dark, brightness: Brightness.dark,
dynamicSchemeVariant: schemeVariant,
); );
} }
return lightColorScheme != null return lightColorScheme != null
? ColorScheme.fromSeed(seedColor: lightColorScheme!.primary,dynamicSchemeVariant: DynamicSchemeVariant.vibrant) ? ColorScheme.fromSeed(
: ColorScheme.fromSeed(seedColor: defaultPrimaryColor); seedColor: lightColorScheme!.primary,
dynamicSchemeVariant: schemeVariant,
)
: ColorScheme.fromSeed(
seedColor: Color(defaultPrimaryColor),
dynamicSchemeVariant: schemeVariant,
);
} }
} }

View File

@@ -8,6 +8,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'models.dart'; import 'models.dart';
part 'generated/config.freezed.dart'; part 'generated/config.freezed.dart';
part 'generated/config.g.dart'; part 'generated/config.g.dart';
const defaultBypassDomain = [ const defaultBypassDomain = [
@@ -36,10 +37,7 @@ const defaultNetworkProps = NetworkProps();
const defaultProxiesStyle = ProxiesStyle(); const defaultProxiesStyle = ProxiesStyle();
const defaultWindowProps = WindowProps(); const defaultWindowProps = WindowProps();
const defaultAccessControl = AccessControl(); const defaultAccessControl = AccessControl();
final defaultThemeProps = ThemeProps().copyWith( const defaultThemeProps = ThemeProps();
primaryColor: defaultPrimaryColor.toARGB32(),
themeMode: ThemeMode.dark,
);
const List<DashboardWidget> defaultDashboardWidgets = [ const List<DashboardWidget> defaultDashboardWidgets = [
DashboardWidget.networkSpeed, DashboardWidget.networkSpeed,
@@ -75,7 +73,7 @@ class AppSettingProps with _$AppSettingProps {
@Default(false) bool autoLaunch, @Default(false) bool autoLaunch,
@Default(false) bool silentLaunch, @Default(false) bool silentLaunch,
@Default(false) bool autoRun, @Default(false) bool autoRun,
@Default(false) bool openLogs, @Default(true) bool openLogs,
@Default(true) bool closeConnections, @Default(true) bool closeConnections,
@Default(defaultTestUrl) String testUrl, @Default(defaultTestUrl) String testUrl,
@Default(true) bool isAnimateToPage, @Default(true) bool isAnimateToPage,
@@ -142,7 +140,7 @@ class VpnProps with _$VpnProps {
}) = _VpnProps; }) = _VpnProps;
factory VpnProps.fromJson(Map<String, Object?>? json) => factory VpnProps.fromJson(Map<String, Object?>? json) =>
json == null ? const VpnProps() : _$VpnPropsFromJson(json); json == null ? defaultVpnProps : _$VpnPropsFromJson(json);
} }
@freezed @freezed
@@ -175,24 +173,15 @@ class ProxiesStyle with _$ProxiesStyle {
@freezed @freezed
class ThemeProps with _$ThemeProps { class ThemeProps with _$ThemeProps {
const factory ThemeProps({ const factory ThemeProps({
int? primaryColor, @Default(defaultPrimaryColor) int? primaryColor,
@Default(ThemeMode.system) ThemeMode themeMode, @Default(defaultPrimaryColors) List<int> primaryColors,
@Default(ThemeMode.dark) ThemeMode themeMode,
@Default(DynamicSchemeVariant.tonalSpot) DynamicSchemeVariant schemeVariant,
@Default(false) bool pureBlack, @Default(false) bool pureBlack,
}) = _ThemeProps; }) = _ThemeProps;
factory ThemeProps.fromJson(Map<String, Object?> json) => factory ThemeProps.fromJson(Map<String, Object?>? json) =>
_$ThemePropsFromJson(json); json == null ? defaultThemeProps : _$ThemePropsFromJson(json);
factory ThemeProps.safeFromJson(Map<String, Object?>? json) {
if (json == null) {
return defaultThemeProps;
}
try {
return ThemeProps.fromJson(json);
} catch (_) {
return defaultThemeProps;
}
}
} }
@freezed @freezed
@@ -208,7 +197,7 @@ class Config with _$Config {
DAV? dav, DAV? dav,
@Default(defaultNetworkProps) NetworkProps networkProps, @Default(defaultNetworkProps) NetworkProps networkProps,
@Default(defaultVpnProps) VpnProps vpnProps, @Default(defaultVpnProps) VpnProps vpnProps,
@JsonKey(fromJson: ThemeProps.safeFromJson) required ThemeProps themeProps, @Default(defaultThemeProps) ThemeProps themeProps,
@Default(defaultProxiesStyle) ProxiesStyle proxiesStyle, @Default(defaultProxiesStyle) ProxiesStyle proxiesStyle,
@Default(defaultWindowProps) WindowProps windowProps, @Default(defaultWindowProps) WindowProps windowProps,
@Default(defaultClashConfig) ClashConfig patchClashConfig, @Default(defaultClashConfig) ClashConfig patchClashConfig,

View File

@@ -82,6 +82,17 @@ class UpdateConfigParams with _$UpdateConfigParams {
_$UpdateConfigParamsFromJson(json); _$UpdateConfigParamsFromJson(json);
} }
@freezed
class InitParams with _$InitParams {
const factory InitParams({
@JsonKey(name: "home-dir") required String homeDir,
required int version,
}) = _InitParams;
factory InitParams.fromJson(Map<String, Object?> json) =>
_$InitParamsFromJson(json);
}
@freezed @freezed
class ChangeProxyParams with _$ChangeProxyParams { class ChangeProxyParams with _$ChangeProxyParams {
const factory ChangeProxyParams({ const factory ChangeProxyParams({

View File

@@ -35,6 +35,7 @@ mixin _$AppState {
FixedList<Log> get logs => throw _privateConstructorUsedError; FixedList<Log> get logs => throw _privateConstructorUsedError;
FixedList<Traffic> get traffics => throw _privateConstructorUsedError; FixedList<Traffic> get traffics => throw _privateConstructorUsedError;
Traffic get totalTraffic => throw _privateConstructorUsedError; Traffic get totalTraffic => throw _privateConstructorUsedError;
bool get needApply => throw _privateConstructorUsedError;
/// Create a copy of AppState /// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -66,7 +67,8 @@ abstract class $AppStateCopyWith<$Res> {
int version, int version,
FixedList<Log> logs, FixedList<Log> logs,
FixedList<Traffic> traffics, FixedList<Traffic> traffics,
Traffic totalTraffic}); Traffic totalTraffic,
bool needApply});
$ColorSchemesCopyWith<$Res> get colorSchemes; $ColorSchemesCopyWith<$Res> get colorSchemes;
} }
@@ -104,6 +106,7 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
Object? logs = null, Object? logs = null,
Object? traffics = null, Object? traffics = null,
Object? totalTraffic = null, Object? totalTraffic = null,
Object? needApply = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
isInit: null == isInit isInit: null == isInit
@@ -178,6 +181,10 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
? _value.totalTraffic ? _value.totalTraffic
: totalTraffic // ignore: cast_nullable_to_non_nullable : totalTraffic // ignore: cast_nullable_to_non_nullable
as Traffic, as Traffic,
needApply: null == needApply
? _value.needApply
: needApply // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val); ) as $Val);
} }
@@ -218,7 +225,8 @@ abstract class _$$AppStateImplCopyWith<$Res>
int version, int version,
FixedList<Log> logs, FixedList<Log> logs,
FixedList<Traffic> traffics, FixedList<Traffic> traffics,
Traffic totalTraffic}); Traffic totalTraffic,
bool needApply});
@override @override
$ColorSchemesCopyWith<$Res> get colorSchemes; $ColorSchemesCopyWith<$Res> get colorSchemes;
@@ -255,6 +263,7 @@ class __$$AppStateImplCopyWithImpl<$Res>
Object? logs = null, Object? logs = null,
Object? traffics = null, Object? traffics = null,
Object? totalTraffic = null, Object? totalTraffic = null,
Object? needApply = null,
}) { }) {
return _then(_$AppStateImpl( return _then(_$AppStateImpl(
isInit: null == isInit isInit: null == isInit
@@ -329,6 +338,10 @@ class __$$AppStateImplCopyWithImpl<$Res>
? _value.totalTraffic ? _value.totalTraffic
: totalTraffic // ignore: cast_nullable_to_non_nullable : totalTraffic // ignore: cast_nullable_to_non_nullable
as Traffic, as Traffic,
needApply: null == needApply
? _value.needApply
: needApply // ignore: cast_nullable_to_non_nullable
as bool,
)); ));
} }
} }
@@ -354,7 +367,8 @@ class _$AppStateImpl implements _AppState {
required this.version, required this.version,
required this.logs, required this.logs,
required this.traffics, required this.traffics,
required this.totalTraffic}) required this.totalTraffic,
this.needApply = false})
: _packages = packages, : _packages = packages,
_delayMap = delayMap, _delayMap = delayMap,
_groups = groups, _groups = groups,
@@ -429,10 +443,13 @@ class _$AppStateImpl implements _AppState {
final FixedList<Traffic> traffics; final FixedList<Traffic> traffics;
@override @override
final Traffic totalTraffic; final Traffic totalTraffic;
@override
@JsonKey()
final bool needApply;
@override @override
String toString() { String toString() {
return 'AppState(isInit: $isInit, pageLabel: $pageLabel, packages: $packages, colorSchemes: $colorSchemes, sortNum: $sortNum, viewSize: $viewSize, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic)'; return 'AppState(isInit: $isInit, pageLabel: $pageLabel, packages: $packages, colorSchemes: $colorSchemes, sortNum: $sortNum, viewSize: $viewSize, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, needApply: $needApply)';
} }
@override @override
@@ -466,30 +483,34 @@ class _$AppStateImpl implements _AppState {
(identical(other.traffics, traffics) || (identical(other.traffics, traffics) ||
other.traffics == traffics) && other.traffics == traffics) &&
(identical(other.totalTraffic, totalTraffic) || (identical(other.totalTraffic, totalTraffic) ||
other.totalTraffic == totalTraffic)); other.totalTraffic == totalTraffic) &&
(identical(other.needApply, needApply) ||
other.needApply == needApply));
} }
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hashAll([
runtimeType, runtimeType,
isInit, isInit,
pageLabel, pageLabel,
const DeepCollectionEquality().hash(_packages), const DeepCollectionEquality().hash(_packages),
colorSchemes, colorSchemes,
sortNum, sortNum,
viewSize, viewSize,
const DeepCollectionEquality().hash(_delayMap), const DeepCollectionEquality().hash(_delayMap),
const DeepCollectionEquality().hash(_groups), const DeepCollectionEquality().hash(_groups),
checkIpNum, checkIpNum,
brightness, brightness,
runTime, runTime,
const DeepCollectionEquality().hash(_providers), const DeepCollectionEquality().hash(_providers),
localIp, localIp,
requests, requests,
version, version,
logs, logs,
traffics, traffics,
totalTraffic); totalTraffic,
needApply
]);
/// Create a copy of AppState /// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -519,7 +540,8 @@ abstract class _AppState implements AppState {
required final int version, required final int version,
required final FixedList<Log> logs, required final FixedList<Log> logs,
required final FixedList<Traffic> traffics, required final FixedList<Traffic> traffics,
required final Traffic totalTraffic}) = _$AppStateImpl; required final Traffic totalTraffic,
final bool needApply}) = _$AppStateImpl;
@override @override
bool get isInit; bool get isInit;
@@ -557,6 +579,8 @@ abstract class _AppState implements AppState {
FixedList<Traffic> get traffics; FixedList<Traffic> get traffics;
@override @override
Traffic get totalTraffic; Traffic get totalTraffic;
@override
bool get needApply;
/// Create a copy of AppState /// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.

View File

@@ -2832,7 +2832,7 @@ mixin _$ClashConfig {
@JsonKey(name: "log-level") @JsonKey(name: "log-level")
LogLevel get logLevel => throw _privateConstructorUsedError; LogLevel get logLevel => throw _privateConstructorUsedError;
bool get ipv6 => throw _privateConstructorUsedError; bool get ipv6 => throw _privateConstructorUsedError;
@JsonKey(name: "find-process-mode", unknownEnumValue: FindProcessMode.off) @JsonKey(name: "find-process-mode", unknownEnumValue: FindProcessMode.always)
FindProcessMode get findProcessMode => throw _privateConstructorUsedError; FindProcessMode get findProcessMode => throw _privateConstructorUsedError;
@JsonKey(name: "keep-alive-interval") @JsonKey(name: "keep-alive-interval")
int get keepAliveInterval => throw _privateConstructorUsedError; int get keepAliveInterval => throw _privateConstructorUsedError;
@@ -2880,7 +2880,8 @@ abstract class $ClashConfigCopyWith<$Res> {
@JsonKey(name: "allow-lan") bool allowLan, @JsonKey(name: "allow-lan") bool allowLan,
@JsonKey(name: "log-level") LogLevel logLevel, @JsonKey(name: "log-level") LogLevel logLevel,
bool ipv6, bool ipv6,
@JsonKey(name: "find-process-mode", unknownEnumValue: FindProcessMode.off) @JsonKey(
name: "find-process-mode", unknownEnumValue: FindProcessMode.always)
FindProcessMode findProcessMode, FindProcessMode findProcessMode,
@JsonKey(name: "keep-alive-interval") int keepAliveInterval, @JsonKey(name: "keep-alive-interval") int keepAliveInterval,
@JsonKey(name: "unified-delay") bool unifiedDelay, @JsonKey(name: "unified-delay") bool unifiedDelay,
@@ -3057,7 +3058,8 @@ abstract class _$$ClashConfigImplCopyWith<$Res>
@JsonKey(name: "allow-lan") bool allowLan, @JsonKey(name: "allow-lan") bool allowLan,
@JsonKey(name: "log-level") LogLevel logLevel, @JsonKey(name: "log-level") LogLevel logLevel,
bool ipv6, bool ipv6,
@JsonKey(name: "find-process-mode", unknownEnumValue: FindProcessMode.off) @JsonKey(
name: "find-process-mode", unknownEnumValue: FindProcessMode.always)
FindProcessMode findProcessMode, FindProcessMode findProcessMode,
@JsonKey(name: "keep-alive-interval") int keepAliveInterval, @JsonKey(name: "keep-alive-interval") int keepAliveInterval,
@JsonKey(name: "unified-delay") bool unifiedDelay, @JsonKey(name: "unified-delay") bool unifiedDelay,
@@ -3198,9 +3200,10 @@ class _$ClashConfigImpl implements _ClashConfig {
{@JsonKey(name: "mixed-port") this.mixedPort = defaultMixedPort, {@JsonKey(name: "mixed-port") this.mixedPort = defaultMixedPort,
this.mode = Mode.rule, this.mode = Mode.rule,
@JsonKey(name: "allow-lan") this.allowLan = false, @JsonKey(name: "allow-lan") this.allowLan = false,
@JsonKey(name: "log-level") this.logLevel = LogLevel.info, @JsonKey(name: "log-level") this.logLevel = LogLevel.error,
this.ipv6 = false, this.ipv6 = false,
@JsonKey(name: "find-process-mode", unknownEnumValue: FindProcessMode.off) @JsonKey(
name: "find-process-mode", unknownEnumValue: FindProcessMode.always)
this.findProcessMode = FindProcessMode.off, this.findProcessMode = FindProcessMode.off,
@JsonKey(name: "keep-alive-interval") @JsonKey(name: "keep-alive-interval")
this.keepAliveInterval = defaultKeepAliveInterval, this.keepAliveInterval = defaultKeepAliveInterval,
@@ -3242,7 +3245,7 @@ class _$ClashConfigImpl implements _ClashConfig {
@JsonKey() @JsonKey()
final bool ipv6; final bool ipv6;
@override @override
@JsonKey(name: "find-process-mode", unknownEnumValue: FindProcessMode.off) @JsonKey(name: "find-process-mode", unknownEnumValue: FindProcessMode.always)
final FindProcessMode findProcessMode; final FindProcessMode findProcessMode;
@override @override
@JsonKey(name: "keep-alive-interval") @JsonKey(name: "keep-alive-interval")
@@ -3385,7 +3388,8 @@ abstract class _ClashConfig implements ClashConfig {
@JsonKey(name: "allow-lan") final bool allowLan, @JsonKey(name: "allow-lan") final bool allowLan,
@JsonKey(name: "log-level") final LogLevel logLevel, @JsonKey(name: "log-level") final LogLevel logLevel,
final bool ipv6, final bool ipv6,
@JsonKey(name: "find-process-mode", unknownEnumValue: FindProcessMode.off) @JsonKey(
name: "find-process-mode", unknownEnumValue: FindProcessMode.always)
final FindProcessMode findProcessMode, final FindProcessMode findProcessMode,
@JsonKey(name: "keep-alive-interval") final int keepAliveInterval, @JsonKey(name: "keep-alive-interval") final int keepAliveInterval,
@JsonKey(name: "unified-delay") final bool unifiedDelay, @JsonKey(name: "unified-delay") final bool unifiedDelay,
@@ -3419,7 +3423,7 @@ abstract class _ClashConfig implements ClashConfig {
@override @override
bool get ipv6; bool get ipv6;
@override @override
@JsonKey(name: "find-process-mode", unknownEnumValue: FindProcessMode.off) @JsonKey(name: "find-process-mode", unknownEnumValue: FindProcessMode.always)
FindProcessMode get findProcessMode; FindProcessMode get findProcessMode;
@override @override
@JsonKey(name: "keep-alive-interval") @JsonKey(name: "keep-alive-interval")

View File

@@ -268,11 +268,11 @@ _$ClashConfigImpl _$$ClashConfigImplFromJson(Map<String, dynamic> json) =>
mode: $enumDecodeNullable(_$ModeEnumMap, json['mode']) ?? Mode.rule, mode: $enumDecodeNullable(_$ModeEnumMap, json['mode']) ?? Mode.rule,
allowLan: json['allow-lan'] as bool? ?? false, allowLan: json['allow-lan'] as bool? ?? false,
logLevel: $enumDecodeNullable(_$LogLevelEnumMap, json['log-level']) ?? logLevel: $enumDecodeNullable(_$LogLevelEnumMap, json['log-level']) ??
LogLevel.info, LogLevel.error,
ipv6: json['ipv6'] as bool? ?? false, ipv6: json['ipv6'] as bool? ?? false,
findProcessMode: $enumDecodeNullable( findProcessMode: $enumDecodeNullable(
_$FindProcessModeEnumMap, json['find-process-mode'], _$FindProcessModeEnumMap, json['find-process-mode'],
unknownValue: FindProcessMode.off) ?? unknownValue: FindProcessMode.always) ??
FindProcessMode.off, FindProcessMode.off,
keepAliveInterval: (json['keep-alive-interval'] as num?)?.toInt() ?? keepAliveInterval: (json['keep-alive-interval'] as num?)?.toInt() ??
defaultKeepAliveInterval, defaultKeepAliveInterval,

View File

@@ -301,7 +301,7 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
this.autoLaunch = false, this.autoLaunch = false,
this.silentLaunch = false, this.silentLaunch = false,
this.autoRun = false, this.autoRun = false,
this.openLogs = false, this.openLogs = true,
this.closeConnections = true, this.closeConnections = true,
this.testUrl = defaultTestUrl, this.testUrl = defaultTestUrl,
this.isAnimateToPage = true, this.isAnimateToPage = true,
@@ -1724,7 +1724,9 @@ ThemeProps _$ThemePropsFromJson(Map<String, dynamic> json) {
/// @nodoc /// @nodoc
mixin _$ThemeProps { mixin _$ThemeProps {
int? get primaryColor => throw _privateConstructorUsedError; int? get primaryColor => throw _privateConstructorUsedError;
List<int> get primaryColors => throw _privateConstructorUsedError;
ThemeMode get themeMode => throw _privateConstructorUsedError; ThemeMode get themeMode => throw _privateConstructorUsedError;
DynamicSchemeVariant get schemeVariant => throw _privateConstructorUsedError;
bool get pureBlack => throw _privateConstructorUsedError; bool get pureBlack => throw _privateConstructorUsedError;
/// Serializes this ThemeProps to a JSON map. /// Serializes this ThemeProps to a JSON map.
@@ -1743,7 +1745,12 @@ abstract class $ThemePropsCopyWith<$Res> {
ThemeProps value, $Res Function(ThemeProps) then) = ThemeProps value, $Res Function(ThemeProps) then) =
_$ThemePropsCopyWithImpl<$Res, ThemeProps>; _$ThemePropsCopyWithImpl<$Res, ThemeProps>;
@useResult @useResult
$Res call({int? primaryColor, ThemeMode themeMode, bool pureBlack}); $Res call(
{int? primaryColor,
List<int> primaryColors,
ThemeMode themeMode,
DynamicSchemeVariant schemeVariant,
bool pureBlack});
} }
/// @nodoc /// @nodoc
@@ -1762,7 +1769,9 @@ class _$ThemePropsCopyWithImpl<$Res, $Val extends ThemeProps>
@override @override
$Res call({ $Res call({
Object? primaryColor = freezed, Object? primaryColor = freezed,
Object? primaryColors = null,
Object? themeMode = null, Object? themeMode = null,
Object? schemeVariant = null,
Object? pureBlack = null, Object? pureBlack = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
@@ -1770,10 +1779,18 @@ class _$ThemePropsCopyWithImpl<$Res, $Val extends ThemeProps>
? _value.primaryColor ? _value.primaryColor
: primaryColor // ignore: cast_nullable_to_non_nullable : primaryColor // ignore: cast_nullable_to_non_nullable
as int?, as int?,
primaryColors: null == primaryColors
? _value.primaryColors
: primaryColors // ignore: cast_nullable_to_non_nullable
as List<int>,
themeMode: null == themeMode themeMode: null == themeMode
? _value.themeMode ? _value.themeMode
: themeMode // ignore: cast_nullable_to_non_nullable : themeMode // ignore: cast_nullable_to_non_nullable
as ThemeMode, as ThemeMode,
schemeVariant: null == schemeVariant
? _value.schemeVariant
: schemeVariant // ignore: cast_nullable_to_non_nullable
as DynamicSchemeVariant,
pureBlack: null == pureBlack pureBlack: null == pureBlack
? _value.pureBlack ? _value.pureBlack
: pureBlack // ignore: cast_nullable_to_non_nullable : pureBlack // ignore: cast_nullable_to_non_nullable
@@ -1790,7 +1807,12 @@ abstract class _$$ThemePropsImplCopyWith<$Res>
__$$ThemePropsImplCopyWithImpl<$Res>; __$$ThemePropsImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({int? primaryColor, ThemeMode themeMode, bool pureBlack}); $Res call(
{int? primaryColor,
List<int> primaryColors,
ThemeMode themeMode,
DynamicSchemeVariant schemeVariant,
bool pureBlack});
} }
/// @nodoc /// @nodoc
@@ -1807,7 +1829,9 @@ class __$$ThemePropsImplCopyWithImpl<$Res>
@override @override
$Res call({ $Res call({
Object? primaryColor = freezed, Object? primaryColor = freezed,
Object? primaryColors = null,
Object? themeMode = null, Object? themeMode = null,
Object? schemeVariant = null,
Object? pureBlack = null, Object? pureBlack = null,
}) { }) {
return _then(_$ThemePropsImpl( return _then(_$ThemePropsImpl(
@@ -1815,10 +1839,18 @@ class __$$ThemePropsImplCopyWithImpl<$Res>
? _value.primaryColor ? _value.primaryColor
: primaryColor // ignore: cast_nullable_to_non_nullable : primaryColor // ignore: cast_nullable_to_non_nullable
as int?, as int?,
primaryColors: null == primaryColors
? _value._primaryColors
: primaryColors // ignore: cast_nullable_to_non_nullable
as List<int>,
themeMode: null == themeMode themeMode: null == themeMode
? _value.themeMode ? _value.themeMode
: themeMode // ignore: cast_nullable_to_non_nullable : themeMode // ignore: cast_nullable_to_non_nullable
as ThemeMode, as ThemeMode,
schemeVariant: null == schemeVariant
? _value.schemeVariant
: schemeVariant // ignore: cast_nullable_to_non_nullable
as DynamicSchemeVariant,
pureBlack: null == pureBlack pureBlack: null == pureBlack
? _value.pureBlack ? _value.pureBlack
: pureBlack // ignore: cast_nullable_to_non_nullable : pureBlack // ignore: cast_nullable_to_non_nullable
@@ -1831,25 +1863,41 @@ class __$$ThemePropsImplCopyWithImpl<$Res>
@JsonSerializable() @JsonSerializable()
class _$ThemePropsImpl implements _ThemeProps { class _$ThemePropsImpl implements _ThemeProps {
const _$ThemePropsImpl( const _$ThemePropsImpl(
{this.primaryColor, {this.primaryColor = defaultPrimaryColor,
this.themeMode = ThemeMode.system, final List<int> primaryColors = defaultPrimaryColors,
this.pureBlack = false}); this.themeMode = ThemeMode.dark,
this.schemeVariant = DynamicSchemeVariant.tonalSpot,
this.pureBlack = false})
: _primaryColors = primaryColors;
factory _$ThemePropsImpl.fromJson(Map<String, dynamic> json) => factory _$ThemePropsImpl.fromJson(Map<String, dynamic> json) =>
_$$ThemePropsImplFromJson(json); _$$ThemePropsImplFromJson(json);
@override @override
@JsonKey()
final int? primaryColor; final int? primaryColor;
final List<int> _primaryColors;
@override
@JsonKey()
List<int> get primaryColors {
if (_primaryColors is EqualUnmodifiableListView) return _primaryColors;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_primaryColors);
}
@override @override
@JsonKey() @JsonKey()
final ThemeMode themeMode; final ThemeMode themeMode;
@override @override
@JsonKey() @JsonKey()
final DynamicSchemeVariant schemeVariant;
@override
@JsonKey()
final bool pureBlack; final bool pureBlack;
@override @override
String toString() { String toString() {
return 'ThemeProps(primaryColor: $primaryColor, themeMode: $themeMode, pureBlack: $pureBlack)'; return 'ThemeProps(primaryColor: $primaryColor, primaryColors: $primaryColors, themeMode: $themeMode, schemeVariant: $schemeVariant, pureBlack: $pureBlack)';
} }
@override @override
@@ -1859,16 +1907,25 @@ class _$ThemePropsImpl implements _ThemeProps {
other is _$ThemePropsImpl && other is _$ThemePropsImpl &&
(identical(other.primaryColor, primaryColor) || (identical(other.primaryColor, primaryColor) ||
other.primaryColor == primaryColor) && other.primaryColor == primaryColor) &&
const DeepCollectionEquality()
.equals(other._primaryColors, _primaryColors) &&
(identical(other.themeMode, themeMode) || (identical(other.themeMode, themeMode) ||
other.themeMode == themeMode) && other.themeMode == themeMode) &&
(identical(other.schemeVariant, schemeVariant) ||
other.schemeVariant == schemeVariant) &&
(identical(other.pureBlack, pureBlack) || (identical(other.pureBlack, pureBlack) ||
other.pureBlack == pureBlack)); other.pureBlack == pureBlack));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => int get hashCode => Object.hash(
Object.hash(runtimeType, primaryColor, themeMode, pureBlack); runtimeType,
primaryColor,
const DeepCollectionEquality().hash(_primaryColors),
themeMode,
schemeVariant,
pureBlack);
/// Create a copy of ThemeProps /// Create a copy of ThemeProps
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -1889,7 +1946,9 @@ class _$ThemePropsImpl implements _ThemeProps {
abstract class _ThemeProps implements ThemeProps { abstract class _ThemeProps implements ThemeProps {
const factory _ThemeProps( const factory _ThemeProps(
{final int? primaryColor, {final int? primaryColor,
final List<int> primaryColors,
final ThemeMode themeMode, final ThemeMode themeMode,
final DynamicSchemeVariant schemeVariant,
final bool pureBlack}) = _$ThemePropsImpl; final bool pureBlack}) = _$ThemePropsImpl;
factory _ThemeProps.fromJson(Map<String, dynamic> json) = factory _ThemeProps.fromJson(Map<String, dynamic> json) =
@@ -1898,8 +1957,12 @@ abstract class _ThemeProps implements ThemeProps {
@override @override
int? get primaryColor; int? get primaryColor;
@override @override
List<int> get primaryColors;
@override
ThemeMode get themeMode; ThemeMode get themeMode;
@override @override
DynamicSchemeVariant get schemeVariant;
@override
bool get pureBlack; bool get pureBlack;
/// Create a copy of ThemeProps /// Create a copy of ThemeProps
@@ -1925,7 +1988,6 @@ mixin _$Config {
DAV? get dav => throw _privateConstructorUsedError; DAV? get dav => throw _privateConstructorUsedError;
NetworkProps get networkProps => throw _privateConstructorUsedError; NetworkProps get networkProps => throw _privateConstructorUsedError;
VpnProps get vpnProps => throw _privateConstructorUsedError; VpnProps get vpnProps => throw _privateConstructorUsedError;
@JsonKey(fromJson: ThemeProps.safeFromJson)
ThemeProps get themeProps => throw _privateConstructorUsedError; ThemeProps get themeProps => throw _privateConstructorUsedError;
ProxiesStyle get proxiesStyle => throw _privateConstructorUsedError; ProxiesStyle get proxiesStyle => throw _privateConstructorUsedError;
WindowProps get windowProps => throw _privateConstructorUsedError; WindowProps get windowProps => throw _privateConstructorUsedError;
@@ -1955,7 +2017,7 @@ abstract class $ConfigCopyWith<$Res> {
DAV? dav, DAV? dav,
NetworkProps networkProps, NetworkProps networkProps,
VpnProps vpnProps, VpnProps vpnProps,
@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ThemeProps themeProps,
ProxiesStyle proxiesStyle, ProxiesStyle proxiesStyle,
WindowProps windowProps, WindowProps windowProps,
ClashConfig patchClashConfig}); ClashConfig patchClashConfig});
@@ -2152,7 +2214,7 @@ abstract class _$$ConfigImplCopyWith<$Res> implements $ConfigCopyWith<$Res> {
DAV? dav, DAV? dav,
NetworkProps networkProps, NetworkProps networkProps,
VpnProps vpnProps, VpnProps vpnProps,
@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ThemeProps themeProps,
ProxiesStyle proxiesStyle, ProxiesStyle proxiesStyle,
WindowProps windowProps, WindowProps windowProps,
ClashConfig patchClashConfig}); ClashConfig patchClashConfig});
@@ -2267,7 +2329,7 @@ class _$ConfigImpl implements _Config {
this.dav, this.dav,
this.networkProps = defaultNetworkProps, this.networkProps = defaultNetworkProps,
this.vpnProps = defaultVpnProps, this.vpnProps = defaultVpnProps,
@JsonKey(fromJson: ThemeProps.safeFromJson) required this.themeProps, this.themeProps = defaultThemeProps,
this.proxiesStyle = defaultProxiesStyle, this.proxiesStyle = defaultProxiesStyle,
this.windowProps = defaultWindowProps, this.windowProps = defaultWindowProps,
this.patchClashConfig = defaultClashConfig}) this.patchClashConfig = defaultClashConfig})
@@ -2312,7 +2374,7 @@ class _$ConfigImpl implements _Config {
@JsonKey() @JsonKey()
final VpnProps vpnProps; final VpnProps vpnProps;
@override @override
@JsonKey(fromJson: ThemeProps.safeFromJson) @JsonKey()
final ThemeProps themeProps; final ThemeProps themeProps;
@override @override
@JsonKey() @JsonKey()
@@ -2402,8 +2464,7 @@ abstract class _Config implements Config {
final DAV? dav, final DAV? dav,
final NetworkProps networkProps, final NetworkProps networkProps,
final VpnProps vpnProps, final VpnProps vpnProps,
@JsonKey(fromJson: ThemeProps.safeFromJson) final ThemeProps themeProps,
required final ThemeProps themeProps,
final ProxiesStyle proxiesStyle, final ProxiesStyle proxiesStyle,
final WindowProps windowProps, final WindowProps windowProps,
final ClashConfig patchClashConfig}) = _$ConfigImpl; final ClashConfig patchClashConfig}) = _$ConfigImpl;
@@ -2428,7 +2489,6 @@ abstract class _Config implements Config {
@override @override
VpnProps get vpnProps; VpnProps get vpnProps;
@override @override
@JsonKey(fromJson: ThemeProps.safeFromJson)
ThemeProps get themeProps; ThemeProps get themeProps;
@override @override
ProxiesStyle get proxiesStyle; ProxiesStyle get proxiesStyle;

View File

@@ -17,7 +17,7 @@ _$AppSettingPropsImpl _$$AppSettingPropsImplFromJson(
autoLaunch: json['autoLaunch'] as bool? ?? false, autoLaunch: json['autoLaunch'] as bool? ?? false,
silentLaunch: json['silentLaunch'] as bool? ?? false, silentLaunch: json['silentLaunch'] as bool? ?? false,
autoRun: json['autoRun'] as bool? ?? false, autoRun: json['autoRun'] as bool? ?? false,
openLogs: json['openLogs'] as bool? ?? false, openLogs: json['openLogs'] as bool? ?? true,
closeConnections: json['closeConnections'] as bool? ?? true, closeConnections: json['closeConnections'] as bool? ?? true,
testUrl: json['testUrl'] as String? ?? defaultTestUrl, testUrl: json['testUrl'] as String? ?? defaultTestUrl,
isAnimateToPage: json['isAnimateToPage'] as bool? ?? true, isAnimateToPage: json['isAnimateToPage'] as bool? ?? true,
@@ -221,16 +221,26 @@ const _$ProxyCardTypeEnumMap = {
_$ThemePropsImpl _$$ThemePropsImplFromJson(Map<String, dynamic> json) => _$ThemePropsImpl _$$ThemePropsImplFromJson(Map<String, dynamic> json) =>
_$ThemePropsImpl( _$ThemePropsImpl(
primaryColor: (json['primaryColor'] as num?)?.toInt(), primaryColor:
(json['primaryColor'] as num?)?.toInt() ?? defaultPrimaryColor,
primaryColors: (json['primaryColors'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList() ??
defaultPrimaryColors,
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ?? themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
ThemeMode.system, ThemeMode.dark,
schemeVariant: $enumDecodeNullable(
_$DynamicSchemeVariantEnumMap, json['schemeVariant']) ??
DynamicSchemeVariant.tonalSpot,
pureBlack: json['pureBlack'] as bool? ?? false, pureBlack: json['pureBlack'] as bool? ?? false,
); );
Map<String, dynamic> _$$ThemePropsImplToJson(_$ThemePropsImpl instance) => Map<String, dynamic> _$$ThemePropsImplToJson(_$ThemePropsImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'primaryColor': instance.primaryColor, 'primaryColor': instance.primaryColor,
'primaryColors': instance.primaryColors,
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, 'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
'schemeVariant': _$DynamicSchemeVariantEnumMap[instance.schemeVariant]!,
'pureBlack': instance.pureBlack, 'pureBlack': instance.pureBlack,
}; };
@@ -240,6 +250,18 @@ const _$ThemeModeEnumMap = {
ThemeMode.dark: 'dark', ThemeMode.dark: 'dark',
}; };
const _$DynamicSchemeVariantEnumMap = {
DynamicSchemeVariant.tonalSpot: 'tonalSpot',
DynamicSchemeVariant.fidelity: 'fidelity',
DynamicSchemeVariant.monochrome: 'monochrome',
DynamicSchemeVariant.neutral: 'neutral',
DynamicSchemeVariant.vibrant: 'vibrant',
DynamicSchemeVariant.expressive: 'expressive',
DynamicSchemeVariant.content: 'content',
DynamicSchemeVariant.rainbow: 'rainbow',
DynamicSchemeVariant.fruitSalad: 'fruitSalad',
};
_$ConfigImpl _$$ConfigImplFromJson(Map<String, dynamic> json) => _$ConfigImpl( _$ConfigImpl _$$ConfigImplFromJson(Map<String, dynamic> json) => _$ConfigImpl(
appSetting: json['appSetting'] == null appSetting: json['appSetting'] == null
? defaultAppSettingProps ? defaultAppSettingProps
@@ -265,8 +287,9 @@ _$ConfigImpl _$$ConfigImplFromJson(Map<String, dynamic> json) => _$ConfigImpl(
vpnProps: json['vpnProps'] == null vpnProps: json['vpnProps'] == null
? defaultVpnProps ? defaultVpnProps
: VpnProps.fromJson(json['vpnProps'] as Map<String, dynamic>?), : VpnProps.fromJson(json['vpnProps'] as Map<String, dynamic>?),
themeProps: themeProps: json['themeProps'] == null
ThemeProps.safeFromJson(json['themeProps'] as Map<String, Object?>?), ? defaultThemeProps
: ThemeProps.fromJson(json['themeProps'] as Map<String, dynamic>?),
proxiesStyle: json['proxiesStyle'] == null proxiesStyle: json['proxiesStyle'] == null
? defaultProxiesStyle ? defaultProxiesStyle
: ProxiesStyle.fromJson( : ProxiesStyle.fromJson(

View File

@@ -1151,6 +1151,178 @@ abstract class _UpdateConfigParams implements UpdateConfigParams {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
InitParams _$InitParamsFromJson(Map<String, dynamic> json) {
return _InitParams.fromJson(json);
}
/// @nodoc
mixin _$InitParams {
@JsonKey(name: "home-dir")
String get homeDir => throw _privateConstructorUsedError;
int get version => throw _privateConstructorUsedError;
/// Serializes this InitParams to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of InitParams
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$InitParamsCopyWith<InitParams> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $InitParamsCopyWith<$Res> {
factory $InitParamsCopyWith(
InitParams value, $Res Function(InitParams) then) =
_$InitParamsCopyWithImpl<$Res, InitParams>;
@useResult
$Res call({@JsonKey(name: "home-dir") String homeDir, int version});
}
/// @nodoc
class _$InitParamsCopyWithImpl<$Res, $Val extends InitParams>
implements $InitParamsCopyWith<$Res> {
_$InitParamsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of InitParams
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? homeDir = null,
Object? version = null,
}) {
return _then(_value.copyWith(
homeDir: null == homeDir
? _value.homeDir
: homeDir // ignore: cast_nullable_to_non_nullable
as String,
version: null == version
? _value.version
: version // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$InitParamsImplCopyWith<$Res>
implements $InitParamsCopyWith<$Res> {
factory _$$InitParamsImplCopyWith(
_$InitParamsImpl value, $Res Function(_$InitParamsImpl) then) =
__$$InitParamsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({@JsonKey(name: "home-dir") String homeDir, int version});
}
/// @nodoc
class __$$InitParamsImplCopyWithImpl<$Res>
extends _$InitParamsCopyWithImpl<$Res, _$InitParamsImpl>
implements _$$InitParamsImplCopyWith<$Res> {
__$$InitParamsImplCopyWithImpl(
_$InitParamsImpl _value, $Res Function(_$InitParamsImpl) _then)
: super(_value, _then);
/// Create a copy of InitParams
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? homeDir = null,
Object? version = null,
}) {
return _then(_$InitParamsImpl(
homeDir: null == homeDir
? _value.homeDir
: homeDir // ignore: cast_nullable_to_non_nullable
as String,
version: null == version
? _value.version
: version // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$InitParamsImpl implements _InitParams {
const _$InitParamsImpl(
{@JsonKey(name: "home-dir") required this.homeDir,
required this.version});
factory _$InitParamsImpl.fromJson(Map<String, dynamic> json) =>
_$$InitParamsImplFromJson(json);
@override
@JsonKey(name: "home-dir")
final String homeDir;
@override
final int version;
@override
String toString() {
return 'InitParams(homeDir: $homeDir, version: $version)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$InitParamsImpl &&
(identical(other.homeDir, homeDir) || other.homeDir == homeDir) &&
(identical(other.version, version) || other.version == version));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, homeDir, version);
/// Create a copy of InitParams
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$InitParamsImplCopyWith<_$InitParamsImpl> get copyWith =>
__$$InitParamsImplCopyWithImpl<_$InitParamsImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$InitParamsImplToJson(
this,
);
}
}
abstract class _InitParams implements InitParams {
const factory _InitParams(
{@JsonKey(name: "home-dir") required final String homeDir,
required final int version}) = _$InitParamsImpl;
factory _InitParams.fromJson(Map<String, dynamic> json) =
_$InitParamsImpl.fromJson;
@override
@JsonKey(name: "home-dir")
String get homeDir;
@override
int get version;
/// Create a copy of InitParams
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$InitParamsImplCopyWith<_$InitParamsImpl> get copyWith =>
throw _privateConstructorUsedError;
}
ChangeProxyParams _$ChangeProxyParamsFromJson(Map<String, dynamic> json) { ChangeProxyParams _$ChangeProxyParamsFromJson(Map<String, dynamic> json) {
return _ChangeProxyParams.fromJson(json); return _ChangeProxyParams.fromJson(json);
} }

View File

@@ -100,6 +100,18 @@ Map<String, dynamic> _$$UpdateConfigParamsImplToJson(
'params': instance.params, 'params': instance.params,
}; };
_$InitParamsImpl _$$InitParamsImplFromJson(Map<String, dynamic> json) =>
_$InitParamsImpl(
homeDir: json['home-dir'] as String,
version: (json['version'] as num).toInt(),
);
Map<String, dynamic> _$$InitParamsImplToJson(_$InitParamsImpl instance) =>
<String, dynamic>{
'home-dir': instance.homeDir,
'version': instance.version,
};
_$ChangeProxyParamsImpl _$$ChangeProxyParamsImplFromJson( _$ChangeProxyParamsImpl _$$ChangeProxyParamsImplFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
_$ChangeProxyParamsImpl( _$ChangeProxyParamsImpl(

View File

@@ -173,7 +173,7 @@ extension ProfileExtension on Profile {
final disposition = response.headers.value("content-disposition"); final disposition = response.headers.value("content-disposition");
final userinfo = response.headers.value('subscription-userinfo'); final userinfo = response.headers.value('subscription-userinfo');
return await copyWith( return await copyWith(
label: label ?? other.getFileNameForDisposition(disposition) ?? id, label: label ?? utils.getFileNameForDisposition(disposition) ?? id,
subscriptionInfo: SubscriptionInfo.formHString(userinfo), subscriptionInfo: SubscriptionInfo.formHString(userinfo),
).saveFile(response.data); ).saveFile(response.data);
} }

View File

@@ -155,9 +155,9 @@ extension PackageListSelectorStateExt on PackageListSelectorState {
(a, b) { (a, b) {
return switch (sort) { return switch (sort) {
AccessSortType.none => 0, AccessSortType.none => 0,
AccessSortType.name => other.sortByChar( AccessSortType.name => utils.sortByChar(
other.getPinyin(a.label), utils.getPinyin(a.label),
other.getPinyin(b.label), utils.getPinyin(b.label),
), ),
AccessSortType.time => b.lastUpdateTime.compareTo(a.lastUpdateTime), AccessSortType.time => b.lastUpdateTime.compareTo(a.lastUpdateTime),
}; };

View File

@@ -31,7 +31,6 @@ class Service {
Future<bool?> startVpn() async { Future<bool?> startVpn() async {
final options = await clashLib?.getAndroidVpnOptions(); final options = await clashLib?.getAndroidVpnOptions();
// commonPrint.log("$options");
return await methodChannel.invokeMethod<bool>("startVpn", { return await methodChannel.invokeMethod<bool>("startVpn", {
'data': json.encode(options), 'data': json.encode(options),
}); });

View File

@@ -8,8 +8,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
abstract mixin class VpnListener { abstract mixin class VpnListener {
void onStarted(int fd) {}
void onDnsChanged(String dns) {} void onDnsChanged(String dns) {}
} }
@@ -32,10 +30,6 @@ class Vpn {
default: default:
for (final VpnListener listener in _listeners) { for (final VpnListener listener in _listeners) {
switch (call.method) { switch (call.method) {
case "started":
final fd = call.arguments as int;
listener.onStarted(fd);
break;
case "dnsChanged": case "dnsChanged":
final dns = call.arguments as String; final dns = call.arguments as String;
listener.onDnsChanged(dns); listener.onDnsChanged(dns);
@@ -62,16 +56,6 @@ class Vpn {
return await methodChannel.invokeMethod<bool>("stop"); return await methodChannel.invokeMethod<bool>("stop");
} }
Future<bool?> setProtect(int fd) async {
return await methodChannel.invokeMethod<bool?>("setProtect", {'fd': fd});
}
Future<String?> resolverProcess(ProcessData process) async {
return await methodChannel.invokeMethod<String>("resolverProcess", {
"data": json.encode(process),
});
}
void addListener(VpnListener listener) { void addListener(VpnListener listener) {
_listeners.add(listener); _listeners.add(listener);
} }

View File

@@ -196,7 +196,7 @@ class ViewSize extends _$ViewSize with AutoDisposeNotifierMixin {
); );
} }
ViewMode get viewMode => other.getViewMode(state.width); ViewMode get viewMode => utils.getViewMode(state.width);
bool get isMobileView => viewMode == ViewMode.mobile; bool get isMobileView => viewMode == ViewMode.mobile;
} }
@@ -208,7 +208,7 @@ double viewWidth(Ref ref) {
@riverpod @riverpod
ViewMode viewMode(Ref ref) { ViewMode viewMode(Ref ref) {
return other.getViewMode(ref.watch(viewWidthProvider)); return utils.getViewMode(ref.watch(viewWidthProvider));
} }
@riverpod @riverpod
@@ -356,3 +356,18 @@ class DelayDataSource extends _$DelayDataSource with AutoDisposeNotifierMixin {
} }
} }
} }
@riverpod
class NeedApply extends _$NeedApply with AutoDisposeNotifierMixin {
@override
bool build() {
return globalState.appState.needApply;
}
@override
onUpdate(value) {
globalState.appState = globalState.appState.copyWith(
needApply: value,
);
}
}

View File

@@ -120,7 +120,7 @@ class Profiles extends _$Profiles with AutoDisposeNotifierMixin {
(element) => element.label == realLabel && element.id != id) != (element) => element.label == realLabel && element.id != id) !=
-1; -1;
if (hasDup) { if (hasDup) {
return _getLabel(other.getOverwriteLabel(realLabel), id); return _getLabel(utils.getOverwriteLabel(realLabel), id);
} else { } else {
return label; return label;
} }

View File

@@ -22,7 +22,7 @@ final viewWidthProvider = AutoDisposeProvider<double>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef ViewWidthRef = AutoDisposeProviderRef<double>; typedef ViewWidthRef = AutoDisposeProviderRef<double>;
String _$viewModeHash() => r'fbda5aee64803b09b1431b00650ac6e16d044743'; String _$viewModeHash() => r'736e2acc7e7d98ee30132de1990bf85f9506b47a';
/// See also [viewMode]. /// See also [viewMode].
@ProviderFor(viewMode) @ProviderFor(viewMode)
@@ -203,7 +203,7 @@ final runTimeProvider = AutoDisposeNotifierProvider<RunTime, int?>.internal(
); );
typedef _$RunTime = AutoDisposeNotifier<int?>; typedef _$RunTime = AutoDisposeNotifier<int?>;
String _$viewSizeHash() => r'44a8ff7a1fb1a9ad278b999560bef3ce2c9fea2a'; String _$viewSizeHash() => r'07f9cce28a69d1496ba4643ef72a739312f6fc28';
/// See also [ViewSize]. /// See also [ViewSize].
@ProviderFor(ViewSize) @ProviderFor(ViewSize)
@@ -336,5 +336,19 @@ final delayDataSourceProvider =
); );
typedef _$DelayDataSource = AutoDisposeNotifier<DelayMap>; typedef _$DelayDataSource = AutoDisposeNotifier<DelayMap>;
String _$needApplyHash() => r'62ff248d67b0525c6a55e556fbd29a2044e97766';
/// See also [NeedApply].
@ProviderFor(NeedApply)
final needApplyProvider = AutoDisposeNotifierProvider<NeedApply, bool>.internal(
NeedApply.new,
name: r'needApplyProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$needApplyHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$NeedApply = AutoDisposeNotifier<bool>;
// ignore_for_file: type=lint // 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 // 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

@@ -83,7 +83,7 @@ final themeSettingProvider =
); );
typedef _$ThemeSetting = AutoDisposeNotifier<ThemeProps>; typedef _$ThemeSetting = AutoDisposeNotifier<ThemeProps>;
String _$profilesHash() => r'2023af6ceaf273df480897561565cb3be8054ded'; String _$profilesHash() => r'a6514c89064e4f42fc31fe7d525088fd26c51016';
/// See also [Profiles]. /// See also [Profiles].
@ProviderFor(Profiles) @ProviderFor(Profiles)

View File

@@ -216,7 +216,7 @@ final startButtonSelectorStateProvider =
typedef StartButtonSelectorStateRef typedef StartButtonSelectorStateRef
= AutoDisposeProviderRef<StartButtonSelectorState>; = AutoDisposeProviderRef<StartButtonSelectorState>;
String _$profilesSelectorStateHash() => String _$profilesSelectorStateHash() =>
r'9fa4447dace0322e888efb38cbee1dabd33e0e71'; r'aac2deee6e747eceaf62cb5f279ec99ce9227a5a';
/// See also [profilesSelectorState]. /// See also [profilesSelectorState].
@ProviderFor(profilesSelectorState) @ProviderFor(profilesSelectorState)
@@ -1091,7 +1091,7 @@ final currentProfileProvider = AutoDisposeProvider<Profile?>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef CurrentProfileRef = AutoDisposeProviderRef<Profile?>; typedef CurrentProfileRef = AutoDisposeProviderRef<Profile?>;
String _$getProxiesColumnsHash() => r'895705381fe361fa40f16da2f9cb26e8da3293e8'; String _$getProxiesColumnsHash() => r'725066b5fc21f590a4c2656a1fd5e14ab7079079';
/// See also [getProxiesColumns]. /// See also [getProxiesColumns].
@ProviderFor(getProxiesColumns) @ProviderFor(getProxiesColumns)
@@ -1765,6 +1765,169 @@ class _GetProfileOverrideDataProviderElement
String get profileId => (origin as GetProfileOverrideDataProvider).profileId; String get profileId => (origin as GetProfileOverrideDataProvider).profileId;
} }
String _$genColorSchemeHash() => r'a27ccae9b5c11d47cd46804f42f8e9dc7946a6c2';
/// See also [genColorScheme].
@ProviderFor(genColorScheme)
const genColorSchemeProvider = GenColorSchemeFamily();
/// See also [genColorScheme].
class GenColorSchemeFamily extends Family<ColorScheme> {
/// See also [genColorScheme].
const GenColorSchemeFamily();
/// See also [genColorScheme].
GenColorSchemeProvider call(
Brightness brightness, {
Color? color,
bool isOverride = false,
}) {
return GenColorSchemeProvider(
brightness,
color: color,
isOverride: isOverride,
);
}
@override
GenColorSchemeProvider getProviderOverride(
covariant GenColorSchemeProvider provider,
) {
return call(
provider.brightness,
color: provider.color,
isOverride: provider.isOverride,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'genColorSchemeProvider';
}
/// See also [genColorScheme].
class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
/// See also [genColorScheme].
GenColorSchemeProvider(
Brightness brightness, {
Color? color,
bool isOverride = false,
}) : this._internal(
(ref) => genColorScheme(
ref as GenColorSchemeRef,
brightness,
color: color,
isOverride: isOverride,
),
from: genColorSchemeProvider,
name: r'genColorSchemeProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$genColorSchemeHash,
dependencies: GenColorSchemeFamily._dependencies,
allTransitiveDependencies:
GenColorSchemeFamily._allTransitiveDependencies,
brightness: brightness,
color: color,
isOverride: isOverride,
);
GenColorSchemeProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.brightness,
required this.color,
required this.isOverride,
}) : super.internal();
final Brightness brightness;
final Color? color;
final bool isOverride;
@override
Override overrideWith(
ColorScheme Function(GenColorSchemeRef provider) create,
) {
return ProviderOverride(
origin: this,
override: GenColorSchemeProvider._internal(
(ref) => create(ref as GenColorSchemeRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
brightness: brightness,
color: color,
isOverride: isOverride,
),
);
}
@override
AutoDisposeProviderElement<ColorScheme> createElement() {
return _GenColorSchemeProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is GenColorSchemeProvider &&
other.brightness == brightness &&
other.color == color &&
other.isOverride == isOverride;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, brightness.hashCode);
hash = _SystemHash.combine(hash, color.hashCode);
hash = _SystemHash.combine(hash, isOverride.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin GenColorSchemeRef on AutoDisposeProviderRef<ColorScheme> {
/// The parameter `brightness` of this provider.
Brightness get brightness;
/// The parameter `color` of this provider.
Color? get color;
/// The parameter `isOverride` of this provider.
bool get isOverride;
}
class _GenColorSchemeProviderElement
extends AutoDisposeProviderElement<ColorScheme> with GenColorSchemeRef {
_GenColorSchemeProviderElement(super.provider);
@override
Brightness get brightness => (origin as GenColorSchemeProvider).brightness;
@override
Color? get color => (origin as GenColorSchemeProvider).color;
@override
bool get isOverride => (origin as GenColorSchemeProvider).isOverride;
}
String _$profileOverrideStateHash() => String _$profileOverrideStateHash() =>
r'16d7c75849ed077d60553e5d2bba4ed54b307971'; r'16d7c75849ed077d60553e5d2bba4ed54b307971';

View File

@@ -1,6 +1,7 @@
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -205,7 +206,7 @@ ProfilesSelectorState profilesSelectorState(Ref ref) {
final currentProfileId = ref.watch(currentProfileIdProvider); final currentProfileId = ref.watch(currentProfileIdProvider);
final profiles = ref.watch(profilesProvider); final profiles = ref.watch(profilesProvider);
final columns = ref.watch( final columns = ref.watch(
viewWidthProvider.select((state) => other.getProfilesColumns(state))); viewWidthProvider.select((state) => utils.getProfilesColumns(state)));
return ProfilesSelectorState( return ProfilesSelectorState(
profiles: profiles, profiles: profiles,
currentProfileId: currentProfileId, currentProfileId: currentProfileId,
@@ -406,7 +407,7 @@ int getProxiesColumns(Ref ref) {
final viewWidth = ref.watch(viewWidthProvider); final viewWidth = ref.watch(viewWidthProvider);
final proxiesLayout = final proxiesLayout =
ref.watch(proxiesStyleSettingProvider.select((state) => state.layout)); ref.watch(proxiesStyleSettingProvider.select((state) => state.layout));
return other.getProxiesColumns(viewWidth, proxiesLayout); return utils.getProxiesColumns(viewWidth, proxiesLayout);
} }
ProxyCardState _getProxyCardState( ProxyCardState _getProxyCardState(
@@ -504,3 +505,32 @@ OverrideData? getProfileOverrideData(Ref ref, String profileId) {
), ),
); );
} }
@riverpod
ColorScheme genColorScheme(
Ref ref,
Brightness brightness, {
Color? color,
bool isOverride = false,
}) {
final vm2 = ref.watch(
themeSettingProvider.select(
(state) => VM2(
a: state.primaryColor,
b: state.schemeVariant,
),
),
);
if (color == null && (isOverride == true || vm2.a == null)) {
final colorSchemes = ref.watch(appSchemesProvider);
return colorSchemes.getColorSchemeForBrightness(
brightness,
vm2.b,
);
}
return ColorScheme.fromSeed(
seedColor: color ?? Color(vm2.a!),
brightness: brightness,
dynamicSchemeVariant: vm2.b,
);
}

View File

@@ -20,6 +20,7 @@ typedef UpdateTasks = List<FutureOr Function()>;
class GlobalState { class GlobalState {
static GlobalState? _instance; static GlobalState? _instance;
Map<Key, double> cacheScrollPosition = {};
bool isService = false; bool isService = false;
Timer? timer; Timer? timer;
Timer? groupsUpdateTimer; Timer? groupsUpdateTimer;
@@ -65,7 +66,7 @@ class GlobalState {
); );
await globalState.migrateOldData(config); await globalState.migrateOldData(config);
await AppLocalizations.load( await AppLocalizations.load(
other.getLocaleForString(config.appSetting.locale) ?? utils.getLocaleForString(config.appSetting.locale) ??
WidgetsBinding.instance.platformDispatcher.locale, WidgetsBinding.instance.platformDispatcher.locale,
); );
} }
@@ -110,7 +111,6 @@ class GlobalState {
Future handleStop() async { Future handleStop() async {
startTime = null; startTime = null;
await clashCore.stopListener(); await clashCore.stopListener();
await clashLib?.stopTun();
await service?.stopVpn(); await service?.stopVpn();
stopUpdateTasks(); stopUpdateTasks();
} }

View File

@@ -32,7 +32,7 @@ class InfoHeader extends StatelessWidget {
return Padding( return Padding(
padding: padding ?? baseInfoEdgeInsets, padding: padding ?? baseInfoEdgeInsets,
child: Row( child: Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Flexible( Flexible(

View File

@@ -1,6 +1,6 @@
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'card.dart'; import 'card.dart';
import 'grid.dart'; import 'grid.dart';
@@ -11,33 +11,17 @@ class ColorSchemeBox extends StatelessWidget {
const ColorSchemeBox({ const ColorSchemeBox({
super.key, super.key,
this.primaryColor, required this.primaryColor,
this.onPressed, this.onPressed,
this.isSelected, this.isSelected,
}); });
ThemeData _getTheme(BuildContext context) {
if (primaryColor != null) {
return Theme.of(context).copyWith(
colorScheme: ColorScheme.fromSeed(
seedColor: primaryColor!,
brightness: Theme.of(context).brightness,
),
);
} else {
return Theme.of(context).copyWith(
colorScheme: globalState.appState.colorSchemes
.getColorSchemeForBrightness(Theme.of(context).brightness),
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AspectRatio( return AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: Theme( child: PrimaryColorBox(
data: _getTheme(context), primaryColor: primaryColor,
child: Builder( child: Builder(
builder: (context) { builder: (context) {
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
@@ -101,3 +85,32 @@ class ColorSchemeBox extends StatelessWidget {
); );
} }
} }
class PrimaryColorBox extends ConsumerWidget {
final Color? primaryColor;
final Widget child;
const PrimaryColorBox({
super.key,
required this.primaryColor,
required this.child,
});
@override
Widget build(BuildContext context, ref) {
final themeData = Theme.of(context);
final colorScheme = ref.watch(
genColorSchemeProvider(
themeData.brightness,
color: primaryColor,
isOverride: true,
),
);
return Theme(
data: themeData.copyWith(
colorScheme: colorScheme,
),
child: child,
);
}
}

View File

@@ -278,6 +278,9 @@ class ListItem<T> extends StatelessWidget {
onBack: action, onBack: action,
title: openDelegate.title, title: openDelegate.title,
body: child, body: child,
actions: [
if (openDelegate.action != null) openDelegate.action!,
],
); );
}, },
); );
@@ -495,132 +498,3 @@ Widget generateListView(List<Widget> items) {
), ),
); );
} }
class CacheItemExtentListView extends StatefulWidget {
final NullableIndexedWidgetBuilder itemBuilder;
final int itemCount;
final String Function(int index) keyBuilder;
final double Function(int index) itemExtentBuilder;
final ScrollPhysics? physics;
final bool shrinkWrap;
final bool reverse;
final ScrollController controller;
const CacheItemExtentListView({
super.key,
this.physics,
this.reverse = false,
this.shrinkWrap = false,
required this.itemBuilder,
required this.controller,
required this.keyBuilder,
required this.itemCount,
required this.itemExtentBuilder,
});
@override
State<CacheItemExtentListView> createState() =>
CacheItemExtentListViewState();
}
class CacheItemExtentListViewState extends State<CacheItemExtentListView> {
late final FixedMap<String, double> _cacheHeightMap;
@override
void initState() {
super.initState();
_cacheHeightMap = FixedMap(widget.itemCount);
}
clearCache() {
_cacheHeightMap.clear();
}
@override
Widget build(BuildContext context) {
_cacheHeightMap.updateMaxSize(widget.itemCount);
return ListView.builder(
itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount,
physics: widget.physics,
reverse: widget.reverse,
shrinkWrap: widget.shrinkWrap,
controller: widget.controller,
itemExtentBuilder: (index, __) {
final key = widget.keyBuilder(index);
if (_cacheHeightMap.containsKey(key)) {
return _cacheHeightMap.get(key);
}
return _cacheHeightMap.put(key, widget.itemExtentBuilder(index));
},
);
}
@override
void dispose() {
_cacheHeightMap.clear();
super.dispose();
}
}
class CacheItemExtentSliverReorderableList extends StatefulWidget {
final IndexedWidgetBuilder itemBuilder;
final int itemCount;
final String Function(int index) keyBuilder;
final double Function(int index) itemExtentBuilder;
final ReorderCallback onReorder;
final ReorderItemProxyDecorator? proxyDecorator;
const CacheItemExtentSliverReorderableList({
super.key,
required this.itemBuilder,
required this.keyBuilder,
required this.itemCount,
required this.itemExtentBuilder,
required this.onReorder,
this.proxyDecorator,
});
@override
State<CacheItemExtentSliverReorderableList> createState() =>
CacheItemExtentSliverReorderableListState();
}
class CacheItemExtentSliverReorderableListState
extends State<CacheItemExtentSliverReorderableList> {
late final FixedMap<String, double> _cacheHeightMap;
@override
void initState() {
super.initState();
_cacheHeightMap = FixedMap(widget.itemCount);
}
clearCache() {
_cacheHeightMap.clear();
}
@override
Widget build(BuildContext context) {
_cacheHeightMap.updateMaxSize(widget.itemCount);
return SliverReorderableList(
itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount,
itemExtentBuilder: (index, __) {
final key = widget.keyBuilder(index);
if (_cacheHeightMap.containsKey(key)) {
return _cacheHeightMap.get(key);
}
return _cacheHeightMap.put(key, widget.itemExtentBuilder(index));
},
onReorder: widget.onReorder,
proxyDecorator: widget.proxyDecorator,
);
}
@override
void dispose() {
_cacheHeightMap.clear();
super.dispose();
}
}

442
lib/widgets/palette.dart Normal file
View File

@@ -0,0 +1,442 @@
import 'dart:math' as math;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@immutable
class Palette extends StatefulWidget {
const Palette({
super.key,
required this.controller,
});
final ValueNotifier<Color> controller;
@override
State<Palette> createState() => _PaletteState();
}
class _PaletteState extends State<Palette> {
final double _thickness = 20;
final double _radius = 4;
final double _padding = 8;
final GlobalKey renderBoxKey = GlobalKey();
bool isSquare = false;
bool isTrack = false;
late double colorHue;
late double colorSaturation;
late double colorValue;
late FocusNode _focusNode;
Color get value => widget.controller.value;
HSVColor get color => HSVColor.fromColor(value);
@override
void initState() {
super.initState();
colorHue = color.hue;
colorSaturation = color.saturation;
colorValue = color.value;
_focusNode = FocusNode();
}
_handleChange() {
widget.controller.value = HSVColor.fromAHSV(
color.alpha,
colorHue,
colorSaturation,
colorValue,
).toColor();
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
double trackRadius(Size size) =>
math.min(size.width, size.height) / 2 - _thickness;
static double squareRadius(double radius, double trackSquarePadding) =>
(radius - trackSquarePadding) / math.sqrt(2);
void onStart(Offset offset) {
final RenderBox renderBox =
renderBoxKey.currentContext!.findRenderObject()! as RenderBox;
final size = renderBox.size;
final radius = trackRadius(size);
final radiusOuter = radius + _thickness;
final effectiveSquareRadius = squareRadius(radius, _padding);
final startPosition = renderBox.localToGlobal(Offset.zero);
final center = Offset(size.width / 2, size.height / 2);
final vector = offset - startPosition - center;
final vectorLength = _Computer.vectorLength(vector);
isSquare = vector.dx.abs() < effectiveSquareRadius &&
vector.dy.abs() < effectiveSquareRadius;
isTrack = vectorLength >= radius && vectorLength <= radiusOuter;
if (isSquare) {
colorSaturation =
_Computer.vectorToSaturation(vector.dx, effectiveSquareRadius)
.clamp(0.0, 1.0);
colorValue = _Computer.vectorToValue(vector.dy, effectiveSquareRadius)
.clamp(0.0, 1.0);
_handleChange();
} else if (isTrack) {
colorHue = _Computer.vectorToHue(vector);
_handleChange();
} else {
isTrack = false;
isSquare = false;
}
}
void onUpdate(Offset offset) {
final RenderBox renderBox =
renderBoxKey.currentContext!.findRenderObject()! as RenderBox;
final size = renderBox.size;
final radius = trackRadius(size);
final effectiveSquareRadius = squareRadius(radius, _padding);
final startPosition = renderBox.localToGlobal(Offset.zero);
final center = Offset(size.width / 2, size.height / 2);
final vector = offset - startPosition - center;
if (isSquare) {
isTrack = false;
colorSaturation =
_Computer.vectorToSaturation(vector.dx, effectiveSquareRadius)
.clamp(0.0, 1.0);
colorValue = _Computer.vectorToValue(vector.dy, effectiveSquareRadius)
.clamp(0.0, 1.0);
_handleChange();
} else if (isTrack) {
isSquare = false;
colorHue = _Computer.vectorToHue(vector);
_handleChange();
} else {
isTrack = false;
isSquare = false;
}
}
void onEnd() {
isTrack = false;
isSquare = false;
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: widget.controller,
builder: (_, __, ___) {
return GestureDetector(
dragStartBehavior: DragStartBehavior.down,
onVerticalDragDown: (DragDownDetails details) =>
onStart(details.globalPosition),
onVerticalDragUpdate: (DragUpdateDetails details) =>
onUpdate(details.globalPosition),
onHorizontalDragUpdate: (DragUpdateDetails details) =>
onUpdate(details.globalPosition),
onVerticalDragEnd: (DragEndDetails details) => onEnd(),
onHorizontalDragEnd: (DragEndDetails details) => onEnd(),
onTapUp: (TapUpDetails details) => onEnd(),
child: SizedBox(
key: renderBoxKey,
child: Focus(
focusNode: _focusNode,
child: MouseRegion(
cursor: WidgetStateMouseCursor.clickable,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
RepaintBoundary(
child: CustomPaint(
painter: _ShadePainter(
colorHue: colorHue,
colorSaturation: colorSaturation,
colorValue: colorValue,
thickness: _thickness,
padding: _padding,
trackBorderRadius: _radius,
),
),
),
CustomPaint(
painter: _ShadeThumbPainter(
colorSaturation: colorSaturation,
colorValue: colorValue,
thickness: _thickness,
padding: _padding,
),
),
RepaintBoundary(
child: CustomPaint(
painter: _TrackPainter(
thickness: _thickness,
ticks: 360,
),
),
),
CustomPaint(
painter: _TrackThumbPainter(
colorHue: colorHue,
thickness: _thickness,
),
),
],
),
),
),
),
);
},
);
}
}
class _ShadePainter extends CustomPainter {
const _ShadePainter({
required this.colorHue,
required this.colorSaturation,
required this.colorValue,
required this.thickness,
required this.padding,
required this.trackBorderRadius,
}) : super();
final double colorHue;
final double colorSaturation;
final double colorValue;
final double thickness;
final double padding;
final double trackBorderRadius;
static double trackRadius(Size size, double trackWidth) =>
math.min(size.width, size.height) / 2 - trackWidth / 2;
static double squareRadius(
double radius, double trackWidth, double padding) =>
(radius - trackWidth / 2 - padding) / math.sqrt(2);
@override
void paint(Canvas canvas, Size size) {
final Offset center = Offset(size.width / 2, size.height / 2);
final double radius = trackRadius(size, thickness);
final double effectiveSquareRadius = squareRadius(
radius,
thickness,
padding,
);
final Rect rectBox = Rect.fromLTWH(
center.dx - effectiveSquareRadius,
center.dy - effectiveSquareRadius,
effectiveSquareRadius * 2,
effectiveSquareRadius * 2);
final RRect rRect = RRect.fromRectAndRadius(
rectBox,
Radius.circular(trackBorderRadius),
);
final Shader horizontal = LinearGradient(
colors: <Color>[
Colors.white,
HSVColor.fromAHSV(1, colorHue, 1, 1).toColor()
],
).createShader(rectBox);
canvas.drawRRect(
rRect,
Paint()
..style = PaintingStyle.fill
..shader = horizontal);
final Shader vertical = const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[Colors.transparent, Colors.black],
).createShader(rectBox);
canvas.drawRRect(
rRect,
Paint()
..style = PaintingStyle.fill
..shader = vertical);
}
@override
bool shouldRepaint(_ShadePainter oldDelegate) {
return oldDelegate.thickness != thickness ||
oldDelegate.padding != padding ||
oldDelegate.trackBorderRadius != trackBorderRadius ||
oldDelegate.colorHue != colorHue ||
oldDelegate.colorSaturation != colorSaturation ||
oldDelegate.colorValue != colorValue;
}
}
class _TrackPainter extends CustomPainter {
const _TrackPainter({
this.ticks = 360,
required this.thickness,
}) : super();
final int ticks;
final double thickness;
@override
void paint(Canvas canvas, Size size) {
final Offset center = Offset(size.width / 2, size.height / 2);
const double rads = (2 * math.pi) / 360;
const double step = 1;
const double aliasing = 0.5;
final double shortestRectSide = math.min(size.width, size.height);
final Rect rectCircle = Rect.fromCenter(
center: center,
width: shortestRectSide - thickness,
height: shortestRectSide - thickness,
);
for (int i = 0; i < ticks; i++) {
final double sRad = (i - aliasing) * rads;
final double eRad = (i + step) * rads;
final Paint segmentPaint = Paint()
..color = HSVColor.fromAHSV(1, i.toDouble(), 1, 1).toColor()
..style = PaintingStyle.stroke
..strokeWidth = thickness;
canvas.drawArc(
rectCircle,
sRad,
sRad - eRad,
false,
segmentPaint,
);
}
}
@override
bool shouldRepaint(_TrackPainter oldDelegate) {
return oldDelegate.thickness != thickness || oldDelegate.ticks != ticks;
}
}
class _ShadeThumbPainter extends CustomPainter {
const _ShadeThumbPainter({
required this.colorSaturation,
required this.colorValue,
required this.thickness,
required this.padding,
}) : super();
final double colorSaturation;
final double colorValue;
final double thickness;
final double padding;
static double trackRadius(Size size, double thickness) =>
math.min(size.width, size.height) / 2 - thickness / 2;
static double squareRadius(
double radius, double thickness, double trackSquarePadding) =>
(radius - thickness / 2 - trackSquarePadding) / math.sqrt(2);
@override
void paint(Canvas canvas, Size size) {
final Offset center = Offset(size.width / 2, size.height / 2);
final double radius = trackRadius(size, thickness);
final double effectiveSquareRadius =
squareRadius(radius, thickness, padding);
final Paint paintBlack = Paint()
..color = Colors.black
..strokeWidth = 5
..style = PaintingStyle.stroke;
final Paint paintWhite = Paint()
..color = Colors.white
..strokeWidth = 3
..style = PaintingStyle.stroke;
final double paletteX = _Computer.saturationToVector(
colorSaturation, effectiveSquareRadius, center.dx);
final double paletteY =
_Computer.valueToVector(colorValue, effectiveSquareRadius, center.dy);
final Offset paletteVector = Offset(paletteX, paletteY);
canvas.drawCircle(paletteVector, 12, paintBlack);
canvas.drawCircle(paletteVector, 12, paintWhite);
}
@override
bool shouldRepaint(_ShadeThumbPainter oldDelegate) {
return oldDelegate.thickness != thickness ||
oldDelegate.colorSaturation != colorSaturation ||
oldDelegate.colorValue != colorValue ||
oldDelegate.padding != padding;
}
}
class _TrackThumbPainter extends CustomPainter {
const _TrackThumbPainter({
required this.colorHue,
required this.thickness,
}) : super();
final double colorHue;
final double thickness;
static double trackRadius(Size size, double thickness) =>
math.min(size.width, size.height) / 2 - thickness / 2;
@override
void paint(Canvas canvas, Size size) {
final Offset center = Offset(size.width / 2, size.height / 2);
final double radius = trackRadius(size, thickness);
final Paint paintBlack = Paint()
..color = Colors.black
..strokeWidth = 5
..style = PaintingStyle.stroke;
final Paint paintWhite = Paint()
..color = Colors.white
..strokeWidth = 3
..style = PaintingStyle.stroke;
final Offset track = _Computer.hueToVector(
(colorHue + 360.0) * math.pi / 180.0,
radius,
center,
);
canvas.drawCircle(track, thickness / 2 + 4, paintBlack);
canvas.drawCircle(track, thickness / 2 + 4, paintWhite);
}
@override
bool shouldRepaint(_TrackThumbPainter oldDelegate) {
return oldDelegate.thickness != thickness ||
oldDelegate.colorHue != colorHue;
}
}
class _Computer {
static double vectorLength(Offset vector) =>
math.sqrt(vector.dx * vector.dx + vector.dy * vector.dy);
static double vectorToHue(Offset vector) =>
(((math.atan2(vector.dy, vector.dx)) * 180.0 / math.pi) + 360.0) % 360.0;
static double vectorToSaturation(double vectorX, double squareRadius) =>
vectorX * 0.5 / squareRadius + 0.5;
static double vectorToValue(double vectorY, double squareRadius) =>
0.5 - vectorY * 0.5 / squareRadius;
static Offset hueToVector(double h, double radius, Offset center) => Offset(
math.cos(h) * radius + center.dx, math.sin(h) * radius + center.dy);
static double saturationToVector(
double s, double squareRadius, double centerX) =>
(s - 0.5) * squareRadius / 0.5 + centerX;
static double valueToVector(double l, double squareRadius, double centerY) =>
(0.5 - l) * squareRadius / 0.5 + centerY;
}

View File

@@ -126,7 +126,7 @@ class _CommonPopupBoxState extends State<CommonPopupBox> {
Navigator.of(context) Navigator.of(context)
.push( .push(
CommonPopupRoute( CommonPopupRoute(
barrierLabel: other.id, barrierLabel: utils.id,
builder: (BuildContext context) { builder: (BuildContext context) {
return widget.popup; return widget.popup;
}, },

View File

@@ -42,11 +42,13 @@ class CommonScaffold extends StatefulWidget {
required Widget body, required Widget body,
required String title, required String title,
required Function onBack, required Function onBack,
required List<Widget> actions,
}) : this( }) : this(
key: key, key: key,
body: body, body: body,
title: title, title: title,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: actions,
leading: IconButton( leading: IconButton(
icon: const BackButtonIcon(), icon: const BackButtonIcon(),
onPressed: () { onPressed: () {

View File

@@ -1,3 +1,7 @@
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/list.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class CommonScrollBar extends StatelessWidget { class CommonScrollBar extends StatelessWidget {
@@ -45,3 +49,197 @@ class CommonAutoHiddenScrollBar extends StatelessWidget {
); );
} }
} }
class ScrollToEndBox<T> extends StatefulWidget {
final ScrollController controller;
final List<T> dataSource;
final Widget child;
final Key cacheKey;
const ScrollToEndBox({
super.key,
required this.child,
required this.controller,
required this.cacheKey,
required this.dataSource,
});
@override
State<ScrollToEndBox<T>> createState() => _ScrollToEndBoxState<T>();
}
class _ScrollToEndBoxState<T> extends State<ScrollToEndBox<T>> {
final equals = ListEquality<T>();
_handleTryToEnd() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final double offset =
globalState.cacheScrollPosition[widget.cacheKey] ?? -1;
if (offset < 0) {
widget.controller.animateTo(
duration: kThemeAnimationDuration,
widget.controller.position.maxScrollExtent,
curve: Curves.easeOut,
);
}
});
}
@override
void initState() {
super.initState();
globalState.cacheScrollPosition[widget.cacheKey] = -1;
}
@override
void didUpdateWidget(ScrollToEndBox<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (!equals.equals(oldWidget.dataSource, widget.dataSource)) {
_handleTryToEnd();
}
}
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (details) {
double offset =
details.metrics.pixels == details.metrics.maxScrollExtent
? -1
: details.metrics.pixels;
globalState.cacheScrollPosition[widget.cacheKey] = offset;
return false;
},
child: widget.child,
);
}
}
class CacheItemExtentListView extends StatefulWidget {
final NullableIndexedWidgetBuilder itemBuilder;
final int itemCount;
final String Function(int index) keyBuilder;
final double Function(int index) itemExtentBuilder;
final ScrollPhysics? physics;
final bool shrinkWrap;
final bool reverse;
final ScrollController controller;
const CacheItemExtentListView({
super.key,
this.physics,
this.reverse = false,
this.shrinkWrap = false,
required this.itemBuilder,
required this.controller,
required this.keyBuilder,
required this.itemCount,
required this.itemExtentBuilder,
});
@override
State<CacheItemExtentListView> createState() =>
CacheItemExtentListViewState();
}
class CacheItemExtentListViewState extends State<CacheItemExtentListView> {
late final FixedMap<String, double> _cacheHeightMap;
@override
void initState() {
super.initState();
_cacheHeightMap = FixedMap(widget.itemCount);
}
clearCache() {
_cacheHeightMap.clear();
}
@override
Widget build(BuildContext context) {
_cacheHeightMap.updateMaxSize(widget.itemCount);
return ListView.builder(
itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount,
physics: widget.physics,
reverse: widget.reverse,
shrinkWrap: widget.shrinkWrap,
controller: widget.controller,
itemExtentBuilder: (index, __) {
final key = widget.keyBuilder(index);
if (_cacheHeightMap.containsKey(key)) {
return _cacheHeightMap.get(key);
}
return _cacheHeightMap.put(key, widget.itemExtentBuilder(index));
},
);
}
@override
void dispose() {
_cacheHeightMap.clear();
super.dispose();
}
}
class CacheItemExtentSliverReorderableList extends StatefulWidget {
final IndexedWidgetBuilder itemBuilder;
final int itemCount;
final String Function(int index) keyBuilder;
final double Function(int index) itemExtentBuilder;
final ReorderCallback onReorder;
final ReorderItemProxyDecorator? proxyDecorator;
const CacheItemExtentSliverReorderableList({
super.key,
required this.itemBuilder,
required this.keyBuilder,
required this.itemCount,
required this.itemExtentBuilder,
required this.onReorder,
this.proxyDecorator,
});
@override
State<CacheItemExtentSliverReorderableList> createState() =>
CacheItemExtentSliverReorderableListState();
}
class CacheItemExtentSliverReorderableListState
extends State<CacheItemExtentSliverReorderableList> {
late final FixedMap<String, double> _cacheHeightMap;
@override
void initState() {
super.initState();
_cacheHeightMap = FixedMap(widget.itemCount);
}
clearCache() {
_cacheHeightMap.clear();
}
@override
Widget build(BuildContext context) {
_cacheHeightMap.updateMaxSize(widget.itemCount);
return SliverReorderableList(
itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount,
itemExtentBuilder: (index, __) {
final key = widget.keyBuilder(index);
if (_cacheHeightMap.containsKey(key)) {
return _cacheHeightMap.get(key);
}
return _cacheHeightMap.put(key, widget.itemExtentBuilder(index));
},
onReorder: widget.onReorder,
proxyDecorator: widget.proxyDecorator,
);
}
@override
void dispose() {
_cacheHeightMap.clear();
super.dispose();
}
}

View File

@@ -29,3 +29,4 @@ export 'wave.dart';
export 'scroll.dart'; export 'scroll.dart';
export 'dialog.dart'; export 'dialog.dart';
export 'effect.dart'; export 'effect.dart';
export 'palette.dart';

View File

@@ -744,7 +744,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
LIBRARY_SEARCH_PATHS = ""; LIBRARY_SEARCH_PATHS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.follow.clash; PRODUCT_BUNDLE_IDENTIFIER = com.follow.clash.debug;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

@@ -1,7 +1,7 @@
name: fl_clash name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free. description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none' publish_to: 'none'
version: 0.8.81+202504081 version: 0.8.82+202504182
environment: environment:
sdk: '>=3.1.0 <4.0.0' sdk: '>=3.1.0 <4.0.0'

View File

@@ -19,16 +19,26 @@ media = []
files = {} files = {}
i = 1 i = 1
releaseKeywords = [
"windows-amd64-setup",
"android-arm64",
"macos-arm64",
"macos-amd64"
]
for file in os.listdir(DIST_DIR): for file in os.listdir(DIST_DIR):
file_path = os.path.join(DIST_DIR, file) file_path = os.path.join(DIST_DIR, file)
if os.path.isfile(file_path): if os.path.isfile(file_path):
file_key = f"file{i}" file_lower = file.lower()
media.append({ if any(kw in file_lower for kw in releaseKeywords):
"type": "document", file_key = f"file{i}"
"media": f"attach://{file_key}" media.append({
}) "type": "document",
files[file_key] = open(file_path, 'rb') "media": f"attach://{file_key}"
i += 1 })
files[file_key] = open(file_path, 'rb')
i += 1
if TAG: if TAG:
text += f"\n**{TAG}**\n" text += f"\n**{TAG}**\n"

View File

@@ -437,7 +437,7 @@ class BuildCommand extends Command {
await Build.exec( await Build.exec(
name: name, name: name,
Build.getExecutable( Build.getExecutable(
"flutter_distributor package --skip-clean --platform ${target.name} --targets $targets --flutter-build-args=verbose $args --build-dart-define=APP_ENV=$env", "flutter_distributor package --skip-clean --platform ${target.name} --targets $targets --flutter-build-args=verbose$args --build-dart-define=APP_ENV=$env",
), ),
); );
} }
@@ -485,7 +485,7 @@ class BuildCommand extends Command {
_buildDistributor( _buildDistributor(
target: target, target: target,
targets: "exe,zip", targets: "exe,zip",
args: "--description $archName", args: " --description $archName",
env: env, env: env,
); );
return; return;
@@ -507,7 +507,7 @@ class BuildCommand extends Command {
target: target, target: target,
targets: targets, targets: targets,
args: args:
"--description $archName --build-target-platform $defaultTarget", " --description $archName --build-target-platform $defaultTarget",
env: env, env: env,
); );
return; return;
@@ -526,7 +526,7 @@ class BuildCommand extends Command {
target: target, target: target,
targets: "apk", targets: "apk",
args: args:
"--flutter-build-args split-per-abi --build-target-platform ${defaultTargets.join(",")}", ",split-per-abi --build-target-platform ${defaultTargets.join(",")}",
env: env, env: env,
); );
return; return;
@@ -535,7 +535,7 @@ class BuildCommand extends Command {
_buildDistributor( _buildDistributor(
target: target, target: target,
targets: "dmg", targets: "dmg",
args: "--description $archName", args: " --description $archName",
env: env, env: env,
); );
return; return;