Compare commits

...

3 Commits

Author SHA1 Message Date
chen08209 b44cef071d Optimize default option
Optimize computed text size
2025-04-30 14:47:19 +08:00
chen08209 21fbff52b6 Add windows server mode start process verify
Add linux deb dependencies

Add backup recovery strategy select
2025-04-29 18:31:32 +08:00
chen08209 0a98515457 Optimize startTun performance 2025-04-26 21:29:56 +08:00
76 changed files with 1376 additions and 543 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
os: windows-latest
arch: amd64
- platform: linux
os: ubuntu-latest
os: ubuntu-22.04
arch: amd64
- platform: macos
os: macos-13
+2 -2
View File
@@ -41,8 +41,8 @@ on Mobile:
⚠️ Make sure to install the following dependencies before using them
```bash
sudo apt-get install appindicator3-0.1 libappindicator3-dev
sudo apt-get install keybinder-3.0
sudo apt-get install libayatana-appindicator3-dev
sudo apt-get install libkeybinder-3.0-dev
```
### Android
+2 -2
View File
@@ -41,8 +41,8 @@ on Mobile:
⚠️ 使用前请确保安装以下依赖
```bash
sudo apt-get install appindicator3-0.1 libappindicator3-dev
sudo apt-get install keybinder-3.0
sudo apt-get install libayatana-appindicator3-dev
sudo apt-get install libkeybinder-3.0-dev
```
### Android
@@ -1,7 +1,6 @@
package com.follow.clash.services
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Intent
import android.net.ProxyInfo
import android.net.VpnService
@@ -34,6 +33,10 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
if (options.ipv4Address.isNotEmpty()) {
val cidr = options.ipv4Address.toCIDR()
addAddress(cidr.address, cidr.prefixLength)
Log.d(
"addAddress",
"address: ${cidr.address} prefixLength:${cidr.prefixLength}"
)
val routeAddress = options.getIpv4RouteAddress()
if (routeAddress.isNotEmpty()) {
try {
@@ -50,26 +53,39 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
} else {
addRoute("0.0.0.0", 0)
}
} else {
addRoute("0.0.0.0", 0)
}
if (options.ipv6Address.isNotEmpty()) {
val cidr = options.ipv6Address.toCIDR()
addAddress(cidr.address, cidr.prefixLength)
val routeAddress = options.getIpv6RouteAddress()
if (routeAddress.isNotEmpty()) {
try {
routeAddress.forEach { i ->
Log.d(
"addRoute6",
"address: ${i.address} prefixLength:${i.prefixLength}"
)
addRoute(i.address, i.prefixLength)
try {
if (options.ipv6Address.isNotEmpty()) {
val cidr = options.ipv6Address.toCIDR()
Log.d(
"addAddress6",
"address: ${cidr.address} prefixLength:${cidr.prefixLength}"
)
addAddress(cidr.address, cidr.prefixLength)
val routeAddress = options.getIpv6RouteAddress()
if (routeAddress.isNotEmpty()) {
try {
routeAddress.forEach { i ->
Log.d(
"addRoute6",
"address: ${i.address} prefixLength:${i.prefixLength}"
)
addRoute(i.address, i.prefixLength)
}
} catch (_: Exception) {
addRoute("::", 0)
}
} catch (_: Exception) {
} else {
addRoute("::", 0)
}
} else {
addRoute("::", 0)
}
}catch (_:Exception){
Log.d(
"addAddress6",
"IPv6 is not supported."
)
}
addDnsServer(options.dnsServerAddress)
setMtu(9000)
+9 -11
View File
@@ -1,5 +1,3 @@
import com.android.build.gradle.tasks.MergeSourceSetFolders
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
@@ -37,13 +35,17 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
jvmTarget = "17"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
dependencies {
implementation("androidx.annotation:annotation-jvm:1.9.1")
}
val copyNativeLibs by tasks.register<Copy>("copyNativeLibs") {
@@ -58,8 +60,4 @@ afterEvaluate {
tasks.named("preBuild") {
dependsOn(copyNativeLibs)
}
}
dependencies {
implementation("androidx.core:core-ktx:1.16.0")
}
+2
View File
@@ -21,6 +21,8 @@ if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
-Wl,--strip-all
-Wl,--exclude-libs=ALL
)
add_compile_options(-fvisibility=hidden -fvisibility-inlines-hidden)
endif ()
set(LIB_CLASH_PATH "${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libclash.so")
+6 -1
View File
@@ -395,5 +395,10 @@
"textScale": "Text Scaling",
"internet": "Internet",
"systemApp": "System APP",
"noNetworkApp": "No network App"
"noNetworkApp": "No network APP",
"contactMe": "Contact me",
"recoveryStrategy": "Recovery strategy",
"recoveryStrategy_override": "Override",
"recoveryStrategy_compatible": "Compatible",
"logsTest": "Logs test"
}
+6 -1
View File
@@ -396,5 +396,10 @@
"textScale": "テキストスケーリング",
"internet": "インターネット",
"systemApp": "システムアプリ",
"noNetworkApp": "ネットワークなしアプリ"
"noNetworkApp": "ネットワークなしアプリ",
"contactMe": "連絡する",
"recoveryStrategy": "リカバリー戦略",
"recoveryStrategy_override": "オーバーライド",
"recoveryStrategy_compatible": "互換性",
"logsTest": "ログテスト"
}
+6 -1
View File
@@ -396,5 +396,10 @@
"textScale": "Масштабирование текста",
"internet": "Интернет",
"systemApp": "Системное приложение",
"noNetworkApp": "Приложение без сети"
"noNetworkApp": "Приложение без сети",
"contactMe": "Свяжитесь со мной",
"recoveryStrategy": "Стратегия восстановления",
"recoveryStrategy_override": "Переопределение",
"recoveryStrategy_compatible": "Совместимый",
"logsTest": "Тест журналов"
}
+6 -1
View File
@@ -396,5 +396,10 @@
"textScale": "文本缩放",
"internet": "互联网",
"systemApp": "系统应用",
"noNetworkApp": "无网络应用"
"noNetworkApp": "无网络应用",
"contactMe": "联系我",
"recoveryStrategy": "恢复策略",
"recoveryStrategy_override": "覆盖",
"recoveryStrategy_compatible": "兼容",
"logsTest": "日志测试"
}
+11 -8
View File
@@ -98,13 +98,13 @@ func handleStopTun() {
}
}
func handleStartTun(fd int, callback unsafe.Pointer) bool {
func handleStartTun(fd int, callback unsafe.Pointer) {
handleStopTun()
tunLock.Lock()
defer tunLock.Unlock()
now := time.Now()
runTime = &now
if fd != 0 {
tunLock.Lock()
defer tunLock.Unlock()
tunHandler = &TunHandler{
callback: callback,
limit: semaphore.NewWeighted(4),
@@ -113,13 +113,11 @@ func handleStartTun(fd int, callback unsafe.Pointer) bool {
tunListener, _ := t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack)
if tunListener != nil {
log.Infoln("TUN address: %v", tunListener.Address())
tunHandler.listener = tunListener
} else {
removeTunHook()
return false
}
tunHandler.listener = tunListener
}
return true
}
func handleGetRunTime() string {
@@ -228,7 +226,10 @@ func quickStart(initParamsChar *C.char, paramsChar *C.char, stateParamsChar *C.c
//export startTUN
func startTUN(fd C.int, callback unsafe.Pointer) bool {
return handleStartTun(int(fd), callback)
go func() {
handleStartTun(int(fd), callback)
}()
return true
}
//export getRunTime
@@ -238,7 +239,9 @@ func getRunTime() *C.char {
//export stopTun
func stopTun() {
handleStopTun()
go func() {
handleStopTun()
}()
}
//export getCurrentProfileName
-1
View File
@@ -245,7 +245,6 @@ abstract class ClashHandlerInterface with ClashInterface {
);
}
@override
Future<bool> crash() {
return invoke<bool>(
+6 -7
View File
@@ -71,9 +71,9 @@ class ClashService extends ClashHandlerInterface {
}
}, (error, stack) {
commonPrint.log(error.toString());
if(error is SocketException){
if (error is SocketException) {
globalState.showNotifier(error.toString());
globalState.appController.restartCore();
// globalState.appController.restartCore();
}
});
}
@@ -92,12 +92,11 @@ class ClashService extends ClashHandlerInterface {
final arg = Platform.isWindows
? "${serverSocket.port}"
: serverSocket.address.address;
bool isSuccess = false;
if (Platform.isWindows && await system.checkIsAdmin()) {
isSuccess = await request.startCoreByHelper(arg);
}
if (isSuccess) {
return;
final isSuccess = await request.startCoreByHelper(arg);
if (isSuccess) {
return;
}
}
process = await Process.start(
appPath.corePath,
+1 -1
View File
@@ -12,7 +12,7 @@ export 'iterable.dart';
export 'keyboard.dart';
export 'launch.dart';
export 'link.dart';
export 'list.dart';
export 'fixed.dart';
export 'lock.dart';
export 'measure.dart';
export 'navigation.dart';
+7 -4
View File
@@ -16,7 +16,8 @@ const browserUa =
const packageName = "com.follow.clash";
final unixSocketPath = "/tmp/FlClashSocket_${Random().nextInt(10000)}.sock";
const helperPort = 47890;
const helperTag = "2024125";
const maxTextScale = 1.4;
const minTextScale = 0.8;
final baseInfoEdgeInsets = EdgeInsets.symmetric(
vertical: 16.ap,
horizontal: 16.ap,
@@ -75,22 +76,24 @@ const viewModeColumnsMap = {
ViewMode.desktop: [4, 3],
};
const defaultPrimaryColor = 0xFF795548;
const defaultPrimaryColor = 0XFFD8C0C3;
double getWidgetHeight(num lines) {
return max(lines * 84 + (lines - 1) * 16, 0).ap;
}
const maxLength = 150;
final mainIsolate = "FlClashMainIsolate";
final serviceIsolate = "FlClashServiceIsolate";
const defaultPrimaryColors = [
defaultPrimaryColor,
0xFF795548,
0xFF03A9F4,
0xFFFFFF00,
0XFFBBC9CC,
0XFFABD397,
0XFFD8C0C3,
defaultPrimaryColor,
0XFF665390,
];
+79
View File
@@ -0,0 +1,79 @@
import 'iterable.dart';
class FixedList<T> {
final int maxLength;
final List<T> _list;
FixedList(this.maxLength, {List<T>? list})
: _list = (list ?? [])..truncate(maxLength);
add(T item) {
_list.add(item);
_list.truncate(maxLength);
}
clear() {
_list.clear();
}
List<T> get list => List.unmodifiable(_list);
int get length => _list.length;
T operator [](int index) => _list[index];
FixedList<T> copyWith() {
return FixedList(
maxLength,
list: _list,
);
}
}
class FixedMap<K, V> {
int maxLength;
late Map<K, V> _map;
FixedMap(this.maxLength, {Map<K, V>? map}) {
_map = map ?? {};
}
updateCacheValue(K key, V Function() callback) {
final realValue = _map.updateCacheValue(
key,
callback,
);
_adjustMap();
return realValue;
}
clear() {
_map.clear();
}
updateMaxLength(int size) {
maxLength = size;
_adjustMap();
}
updateMap(Map<K, V> map) {
_map = map;
_adjustMap();
}
_adjustMap() {
if (_map.length > maxLength) {
_map = Map.fromEntries(
map.entries.toList()..truncate(maxLength),
);
}
}
V? get(K key) => _map[key];
bool containsKey(K key) => _map.containsKey(key);
int get length => _map.length;
Map<K, V> get map => Map.unmodifiable(_map);
}
+39 -2
View File
@@ -38,6 +38,43 @@ extension IterableExt<T> on Iterable<T> {
count++;
}
}
Iterable<T> takeLast({int count = 50}) {
if (count <= 0) return Iterable.empty();
return count >= length ? this : toList().skip(length - count);
}
}
extension ListExt<T> on List<T> {
void truncate(int maxLength) {
assert(maxLength > 0);
if (length > maxLength) {
removeRange(0, length - maxLength);
}
}
List<T> intersection(List<T> list) {
return where((item) => list.contains(item)).toList();
}
List<List<T>> batch(int maxConcurrent) {
final batches = (length / maxConcurrent).ceil();
final List<List<T>> res = [];
for (int i = 0; i < batches; i++) {
if (i != batches - 1) {
res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1)));
} else {
res.add(sublist(i * maxConcurrent, length));
}
}
return res;
}
List<T> safeSublist(int start) {
if (start <= 0) return this;
if (start > length) return [];
return sublist(start);
}
}
extension DoubleListExt on List<double> {
@@ -67,9 +104,9 @@ extension DoubleListExt on List<double> {
}
extension MapExt<K, V> on Map<K, V> {
getCacheValue(K key, V defaultValue) {
updateCacheValue(K key, V Function() callback) {
if (this[key] == null) {
this[key] = defaultValue;
this[key] = callback();
}
return this[key];
}
-93
View File
@@ -1,93 +0,0 @@
import 'dart:collection';
class FixedList<T> {
final int maxLength;
final List<T> _list;
FixedList(this.maxLength, {List<T>? list}) : _list = list ?? [];
add(T item) {
if (_list.length == maxLength) {
_list.removeAt(0);
}
_list.add(item);
}
clear() {
_list.clear();
}
List<T> get list => List.unmodifiable(_list);
int get length => _list.length;
T operator [](int index) => _list[index];
FixedList<T> copyWith() {
return FixedList(
maxLength,
list: _list,
);
}
}
class FixedMap<K, V> {
int maxSize;
final Map<K, V> _map = {};
final Queue<K> _queue = Queue<K>();
FixedMap(this.maxSize);
put(K key, V value) {
if (_map.length == maxSize) {
final oldestKey = _queue.removeFirst();
_map.remove(oldestKey);
}
_map[key] = value;
_queue.add(key);
return value;
}
clear() {
_map.clear();
_queue.clear();
}
updateMaxSize(int size){
maxSize = size;
}
V? get(K key) => _map[key];
bool containsKey(K key) => _map.containsKey(key);
int get length => _map.length;
Map<K, V> get map => Map.unmodifiable(_map);
}
extension ListExtension<T> on List<T> {
List<T> intersection(List<T> list) {
return where((item) => list.contains(item)).toList();
}
List<List<T>> batch(int maxConcurrent) {
final batches = (length / maxConcurrent).ceil();
final List<List<T>> res = [];
for (int i = 0; i < batches; i++) {
if (i != batches - 1) {
res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1)));
} else {
res.add(sublist(i * maxConcurrent, length));
}
}
return res;
}
List<T> safeSublist(int start) {
if (start <= 0) return this;
if (start > length) return [];
return sublist(start);
}
}
+24 -12
View File
@@ -32,9 +32,9 @@ class Measure {
}
double get bodyMediumHeight {
return _measureMap.getCacheValue(
return _measureMap.updateCacheValue(
"bodyMediumHeight",
computeTextSize(
() => computeTextSize(
Text(
"X",
style: context.textTheme.bodyMedium,
@@ -43,10 +43,22 @@ class Measure {
);
}
double get bodyLargeHeight {
return _measureMap.updateCacheValue(
"bodyLargeHeight",
() => computeTextSize(
Text(
"X",
style: context.textTheme.bodyLarge,
),
).height,
);
}
double get bodySmallHeight {
return _measureMap.getCacheValue(
return _measureMap.updateCacheValue(
"bodySmallHeight",
computeTextSize(
() => computeTextSize(
Text(
"X",
style: context.textTheme.bodySmall,
@@ -56,9 +68,9 @@ class Measure {
}
double get labelSmallHeight {
return _measureMap.getCacheValue(
return _measureMap.updateCacheValue(
"labelSmallHeight",
computeTextSize(
() => computeTextSize(
Text(
"X",
style: context.textTheme.labelSmall,
@@ -68,9 +80,9 @@ class Measure {
}
double get labelMediumHeight {
return _measureMap.getCacheValue(
return _measureMap.updateCacheValue(
"labelMediumHeight",
computeTextSize(
() => computeTextSize(
Text(
"X",
style: context.textTheme.labelMedium,
@@ -80,9 +92,9 @@ class Measure {
}
double get titleLargeHeight {
return _measureMap.getCacheValue(
return _measureMap.updateCacheValue(
"titleLargeHeight",
computeTextSize(
() => computeTextSize(
Text(
"X",
style: context.textTheme.titleLarge,
@@ -92,9 +104,9 @@ class Measure {
}
double get titleMediumHeight {
return _measureMap.getCacheValue(
return _measureMap.updateCacheValue(
"titleMediumHeight",
computeTextSize(
() => computeTextSize(
Text(
"X",
style: context.textTheme.titleMedium,
-1
View File
@@ -14,7 +14,6 @@ class Navigation {
const NavigationItem(
icon: Icon(Icons.space_dashboard),
label: PageLabel.dashboard,
keep: false,
fragment: DashboardFragment(
key: GlobalObjectKey(PageLabel.dashboard),
),
+1 -1
View File
@@ -130,7 +130,7 @@ class Request {
if (response.statusCode != HttpStatus.ok) {
return false;
}
return (response.data as String) == helperTag;
return (response.data as String) == globalState.coreSHA256;
} catch (_) {
return false;
}
+7 -1
View File
@@ -55,18 +55,24 @@ class System {
}
Future<AuthorizeCode> authorizeCore() async {
if (Platform.isAndroid) {
return AuthorizeCode.none;
}
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
final isAdmin = await checkIsAdmin();
if (isAdmin) {
return AuthorizeCode.none;
}
if (Platform.isWindows) {
final result = await windows?.registerService();
if (result == true) {
return AuthorizeCode.success;
}
return AuthorizeCode.error;
} else if (Platform.isMacOS) {
}
if (Platform.isMacOS) {
final shell = 'chown root:admin $corePath; chmod +sx $corePath';
final arguments = [
"-e",
+11 -8
View File
@@ -12,32 +12,35 @@ class CommonTheme {
) : _colorMap = {};
Color get darkenSecondaryContainer {
return _colorMap.getCacheValue(
return _colorMap.updateCacheValue(
"darkenSecondaryContainer",
context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.1),
() => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.1),
);
}
Color get darkenSecondaryContainerLighter {
return _colorMap.getCacheValue(
return _colorMap.updateCacheValue(
"darkenSecondaryContainerLighter",
context.colorScheme.secondaryContainer
() => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.1)
.opacity60,
);
}
Color get darken2SecondaryContainer {
return _colorMap.getCacheValue(
return _colorMap.updateCacheValue(
"darken2SecondaryContainer",
context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.2),
() => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.2),
);
}
Color get darken3PrimaryContainer {
return _colorMap.getCacheValue(
return _colorMap.updateCacheValue(
"darken3PrimaryContainer",
context.colorScheme.primaryContainer.blendDarken(context, factor: 0.3),
() => context.colorScheme.primaryContainer
.blendDarken(context, factor: 0.3),
);
}
}
+18 -12
View File
@@ -260,9 +260,7 @@ class AppController {
final patchConfig = _ref.read(patchClashConfigProvider);
final appSetting = _ref.read(appSettingProvider);
bool enableTun = patchConfig.tun.enable;
if (enableTun != lastTunEnable &&
lastTunEnable == false &&
!Platform.isAndroid) {
if (enableTun != lastTunEnable && lastTunEnable == false) {
final code = await system.authorizeCore();
switch (code) {
case AuthorizeCode.none:
@@ -314,6 +312,10 @@ class AppController {
handleChangeProfile() {
_ref.read(delayDataSourceProvider.notifier).value = {};
applyProfile();
_ref.read(logsProvider.notifier).value = FixedList(500);
_ref.read(requestsProvider.notifier).value = FixedList(500);
globalState.cacheHeightMap = {};
globalState.cacheScrollPosition = {};
}
updateBrightness(Brightness brightness) {
@@ -396,9 +398,9 @@ class AppController {
handleExit() async {
try {
await updateStatus(false);
await proxy?.stopProxy();
await clashCore.shutdown();
await clashService?.destroy();
await proxy?.stopProxy();
await savePreferences();
} finally {
system.exit();
@@ -487,10 +489,10 @@ class AppController {
Future<void> _initCore() async {
final isInit = await clashCore.isInit;
if (!isInit) {
await clashCore.init();
await clashCore.setState(
globalState.getCoreState(),
);
await clashCore.init();
}
await applyProfile();
}
@@ -940,14 +942,18 @@ class AppController {
}
_recovery(Config config, RecoveryOption recoveryOption) {
final recoveryStrategy = _ref.read(appSettingProvider.select(
(state) => state.recoveryStrategy,
));
final profiles = config.profiles;
final oldProfiles = List.from(globalState.config.profiles);
_ref.read(profilesProvider.notifier).value = profiles;
for (final profile in oldProfiles) {
_ref.read(profilesProvider.notifier).setProfile(
profile,
force: false,
);
if (recoveryStrategy == RecoveryStrategy.override) {
_ref.read(profilesProvider.notifier).value = profiles;
} else {
for (final profile in profiles) {
_ref.read(profilesProvider.notifier).setProfile(
profile,
);
}
}
final onlyProfiles = recoveryOption == RecoveryOption.onlyProfiles;
if (!onlyProfiles) {
+12
View File
@@ -293,6 +293,7 @@ enum WindowsHelperServiceStatus {
enum DebounceTag {
updateClashConfig,
updateStatus,
updateGroups,
addCheckIpNum,
applyProfile,
@@ -470,3 +471,14 @@ enum RuleTarget {
DIRECT,
REJECT,
}
enum RecoveryStrategy {
compatible,
override,
}
enum CacheTag {
logs,
rules,
requests,
}
+9
View File
@@ -47,6 +47,15 @@ class AboutFragment extends StatelessWidget {
_checkUpdate(context);
},
),
ListItem(
title: Text(appLocalizations.contactMe),
onTap: () {
globalState.showMessage(
title: appLocalizations.contactMe,
message: TextSpan(text: "chen08209@gmail.com"),
);
},
),
ListItem(
title: const Text("Telegram"),
onTap: () {
+46
View File
@@ -8,10 +8,12 @@ import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/dialog.dart';
import 'package:fl_clash/widgets/fade_box.dart';
import 'package:fl_clash/widgets/input.dart';
import 'package:fl_clash/widgets/list.dart';
import 'package:fl_clash/widgets/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
class BackupAndRecovery extends ConsumerWidget {
const BackupAndRecovery({super.key});
@@ -134,6 +136,30 @@ class BackupAndRecovery extends ConsumerWidget {
);
}
_handleUpdateRecoveryStrategy(WidgetRef ref) async {
final recoveryStrategy = ref.read(appSettingProvider.select(
(state) => state.recoveryStrategy,
));
final res = await globalState.showCommonDialog(
child: OptionsDialog<RecoveryStrategy>(
title: appLocalizations.recoveryStrategy,
options: RecoveryStrategy.values,
textBuilder: (mode) => Intl.message(
"recoveryStrategy_${mode.name}",
),
value: recoveryStrategy,
),
);
if (res == null) {
return;
}
ref.read(appSettingProvider.notifier).updateState(
(state) => state.copyWith(
recoveryStrategy: res,
),
);
}
@override
Widget build(BuildContext context, ref) {
final dav = ref.watch(appDAVSettingProvider);
@@ -256,6 +282,26 @@ class BackupAndRecovery extends ConsumerWidget {
title: Text(appLocalizations.recovery),
subtitle: Text(appLocalizations.localRecoveryDesc),
),
ListHeader(title: appLocalizations.options),
Consumer(builder: (_, ref, __) {
final recoveryStrategy = ref.watch(appSettingProvider.select(
(state) => state.recoveryStrategy,
));
return ListItem(
onTap: () {
_handleUpdateRecoveryStrategy(ref);
},
title: Text(appLocalizations.recoveryStrategy),
trailing: FilledButton(
onPressed: () {
_handleUpdateRecoveryStrategy(ref);
},
child: Text(
Intl.message("recoveryStrategy_${recoveryStrategy.name}"),
),
),
);
}),
],
);
}
+8 -3
View File
@@ -301,8 +301,11 @@ class RouteAddressItem extends ConsumerWidget {
title: appLocalizations.routeAddress,
widget: Consumer(
builder: (_, ref, __) {
final routeAddress = ref.watch(patchClashConfigProvider
.select((state) => state.tun.routeAddress));
final routeAddress = ref.watch(
patchClashConfigProvider.select(
(state) => state.tun.routeAddress,
),
);
return ListInputPage(
title: appLocalizations.routeAddress,
items: routeAddress,
@@ -371,7 +374,9 @@ class NetworkListView extends ConsumerWidget {
return;
}
ref.read(vpnSettingProvider.notifier).updateState(
(state) => defaultVpnProps,
(state) => defaultVpnProps.copyWith(
accessControl: state.accessControl,
),
);
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
+93 -56
View File
@@ -20,12 +20,13 @@ class RequestsFragment extends ConsumerStatefulWidget {
class _RequestsFragmentState extends ConsumerState<RequestsFragment>
with PageMixin {
final GlobalKey<CacheItemExtentListViewState> _key = GlobalKey();
final _requestsStateNotifier =
ValueNotifier<ConnectionsState>(const ConnectionsState());
final _requestsStateNotifier = ValueNotifier<ConnectionsState>(
const ConnectionsState(loading: true),
);
List<Connection> _requests = [];
final _cacheKey = ValueKey("requests_list");
final _tag = CacheTag.requests;
late ScrollController _scrollController;
bool _isLoad = false;
double _currentMaxWidth = 0;
@@ -45,12 +46,13 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
@override
void initState() {
super.initState();
final preOffset = globalState.cacheScrollPosition[_cacheKey] ?? -1;
final preOffset = globalState.cacheScrollPosition[_tag] ?? -1;
_scrollController = ScrollController(
initialScrollOffset: preOffset > 0 ? preOffset : double.maxFinite,
);
_requests = globalState.appState.requests.list;
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
connections: globalState.appState.requests.list,
connections: _requests,
);
ref.listenManual(
isCurrentPageProvider(
@@ -97,14 +99,7 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
final lines = (chainSize.height / baseHeight).round();
final computerHeight =
size.height + chainSize.height + 24 + 24 * (lines - 1);
return computerHeight;
}
_handleTryClearCache(double maxWidth) {
if (_currentMaxWidth != maxWidth) {
_currentMaxWidth = maxWidth;
_key.currentState?.clearCache();
}
return computerHeight + 8 + 32 + globalState.measure.bodyMediumHeight;
}
@override
@@ -132,6 +127,42 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
}, duration: commonDuration);
}
_preLoad() {
if (_isLoad == true) {
return;
}
_isLoad = true;
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!mounted) {
return;
}
final isMobileView = ref.read(isMobileViewProvider);
if (isMobileView) {
await Future.delayed(Duration(milliseconds: 300));
}
final parts = _requests.batch(10);
globalState.cacheHeightMap[_tag] ??= FixedMap(
_requests.length,
);
for (int i = 0; i < parts.length; i++) {
final part = parts[i];
await Future(
() {
for (final request in part) {
globalState.cacheHeightMap[_tag]?.updateCacheValue(
request.id,
() => _calcCacheHeight(request),
);
}
},
);
}
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
loading: false,
);
});
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
@@ -145,13 +176,14 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
Platform.isAndroid,
),
);
_handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0));
_currentMaxWidth = constraints.maxWidth - 40 - (value ? 60 : 0);
return child!;
},
child: TextScaleNotification(
child: ValueListenableBuilder<ConnectionsState>(
valueListenable: _requestsStateNotifier,
builder: (_, state, __) {
_preLoad();
final connections = state.list;
if (connections.isEmpty) {
return NullStatus(
@@ -174,51 +206,56 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
),
)
.toList();
return Align(
alignment: Alignment.topCenter,
child: ScrollToEndBox(
controller: _scrollController,
cacheKey: _cacheKey,
dataSource: connections,
child: CommonScrollBar(
controller: _scrollController,
child: CacheItemExtentListView(
key: _key,
reverse: true,
shrinkWrap: true,
physics: NextClampingScrollPhysics(),
controller: _scrollController,
itemExtentBuilder: (index) {
final widget = items[index];
if (widget.runtimeType == Divider) {
return 0;
}
final measure = globalState.measure;
final bodyMediumHeight = measure.bodyMediumHeight;
final connection = connections[(index / 2).floor()];
final height = _calcCacheHeight(connection);
return height + bodyMediumHeight + 32;
},
itemBuilder: (_, index) {
return items[index];
},
itemCount: items.length,
keyBuilder: (int index) {
final widget = items[index];
if (widget.runtimeType == Divider) {
return "divider";
}
final connection = connections[(index / 2).floor()];
return connection.id;
},
),
),
),
final content = connections.isEmpty
? NullStatus(
label: appLocalizations.nullRequestsDesc,
)
: Align(
alignment: Alignment.topCenter,
child: ScrollToEndBox(
controller: _scrollController,
tag: _tag,
dataSource: connections,
child: CommonScrollBar(
controller: _scrollController,
child: CacheItemExtentListView(
tag: _tag,
reverse: true,
shrinkWrap: true,
physics: NextClampingScrollPhysics(),
controller: _scrollController,
itemExtentBuilder: (index) {
if (index.isOdd) {
return 0;
}
return _calcCacheHeight(
connections[index ~/ 2]);
},
itemBuilder: (_, index) {
return items[index];
},
itemCount: items.length,
keyBuilder: (int index) {
if (index.isOdd) {
return "divider";
}
return connections[index ~/ 2].id;
},
),
),
),
);
return FadeBox(
child: state.loading
? Center(
child: CircularProgressIndicator(),
)
: content,
);
},
),
onNotification: (_) {
_key.currentState?.clearCache();
globalState.cacheHeightMap[_tag]?.clear();
},
),
);
@@ -1,5 +1,5 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
@@ -35,11 +35,15 @@ class _StartButtonState extends State<StartButton>
}
handleSwitchStart() {
if (isStart == globalState.appState.isStart) {
isStart = !isStart;
updateController();
globalState.appController.updateStatus(isStart);
}
isStart = !isStart;
updateController();
debouncer.call(
DebounceTag.updateStatus,
() {
globalState.appController.updateStatus(isStart);
},
duration: moreDuration,
);
}
updateController() {
@@ -126,9 +130,11 @@ class _StartButtonState extends State<StartButton>
final text = utils.getTimeText(runTime);
return Text(
text,
style: Theme.of(context).textTheme.titleMedium?.toSoftBold.copyWith(
color: context.colorScheme.onPrimaryContainer
),
style: Theme.of(context)
.textTheme
.titleMedium
?.toSoftBold
.copyWith(color: context.colorScheme.onPrimaryContainer),
);
},
),
+39 -5
View File
@@ -1,21 +1,23 @@
import 'package:fl_clash/clash/core.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/app.dart';
class DeveloperView extends ConsumerWidget {
const DeveloperView({super.key});
Widget _getDeveloperList(BuildContext context) {
Widget _getDeveloperList(BuildContext context, WidgetRef ref) {
return generateSectionV2(
title: appLocalizations.options,
items: [
ListItem(
leading: Icon(Icons.ac_unit),
title: Text(appLocalizations.messageTest),
onTap: () {
context.showNotifier(
@@ -24,14 +26,46 @@ class DeveloperView extends ConsumerWidget {
},
),
ListItem(
leading: Icon(Icons.heart_broken),
title: Text(appLocalizations.logsTest),
onTap: () {
for (int i = 0; i < 1000; i++) {
ref.read(requestsProvider.notifier).addRequest(Connection(
id: utils.id,
start: DateTime.now(),
metadata: Metadata(
uid: i * i,
network: utils.generateRandomString(
maxLength: 1000,
minLength: 20,
),
sourceIP: '',
sourcePort: '',
destinationIP: '',
destinationPort: '',
host: '',
process: '',
remoteDestination: "",
),
chains: ["chains"],
));
globalState.appController.addLog(
Log.app(
utils.generateRandomString(
maxLength: 200,
minLength: 20,
),
),
);
}
},
),
ListItem(
title: Text(appLocalizations.crashTest),
onTap: () {
clashCore.clashInterface.crash();
},
),
ListItem(
leading: Icon(Icons.delete_forever),
title: Text(appLocalizations.clearData),
onTap: () async {
await globalState.appController.handleClear();
@@ -78,7 +112,7 @@ class DeveloperView extends ConsumerWidget {
SizedBox(
height: 16,
),
_getDeveloperList(context)
_getDeveloperList(context, ref)
],
),
);
+121 -86
View File
@@ -1,3 +1,5 @@
import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/providers/providers.dart';
@@ -16,23 +18,27 @@ class LogsFragment extends ConsumerStatefulWidget {
}
class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
final _logsStateNotifier = ValueNotifier<LogsState>(LogsState());
final _cacheKey = ValueKey("logs_list");
final _logsStateNotifier = ValueNotifier<LogsState>(
LogsState(loading: true),
);
late ScrollController _scrollController;
double _currentMaxWidth = 0;
final GlobalKey<CacheItemExtentListViewState> _key = GlobalKey();
final _tag = CacheTag.rules;
bool _isLoad = false;
List<Log> _logs = [];
@override
void initState() {
super.initState();
final preOffset = globalState.cacheScrollPosition[_cacheKey] ?? -1;
final position = globalState.cacheScrollPosition[_tag] ?? -1;
_scrollController = ScrollController(
initialScrollOffset: preOffset > 0 ? preOffset : double.maxFinite,
initialScrollOffset: position > 0 ? position : double.maxFinite,
);
_logs = globalState.appState.logs.list;
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
logs: globalState.appState.logs.list,
logs: _logs,
);
ref.listenManual(
isCurrentPageProvider(
@@ -93,13 +99,6 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
super.dispose();
}
_handleTryClearCache(double maxWidth) {
if (_currentMaxWidth != maxWidth) {
_currentMaxWidth = maxWidth;
_key.currentState?.clearCache();
}
}
_handleExport() async {
final commonScaffoldState = context.commonScaffoldState;
final res = await commonScaffoldState?.loadingRun<bool>(
@@ -123,12 +122,12 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
.computeTextSize(
Text(
log.payload,
style: globalState.appController.context.textTheme.bodyLarge,
style: context.textTheme.bodyLarge,
),
maxWidth: _currentMaxWidth,
)
.height;
return height + bodySmallHeight + 8 + bodyMediumHeight + 40;
return height + bodySmallHeight + 8 + bodyMediumHeight + 40 + 8;
}
updateLogsThrottler() {
@@ -141,86 +140,122 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
logs: _logs,
);
if (mounted) {
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
logs: _logs,
);
}
});
}, duration: commonDuration);
}
_preLoad() {
if (_isLoad == true) {
return;
}
_isLoad = true;
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!mounted) {
return;
}
final isMobileView = ref.read(isMobileViewProvider);
if (isMobileView) {
await Future.delayed(Duration(milliseconds: 300));
}
final parts = _logs.batch(10);
globalState.cacheHeightMap[_tag] ??= FixedMap(
_logs.length,
);
for (int i = 0; i < parts.length; i++) {
final part = parts[i];
await Future(
() {
for (final log in part) {
globalState.cacheHeightMap[_tag]?.updateCacheValue(
log.payload,
() => _getItemHeight(log),
);
}
},
);
}
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
loading: false,
);
});
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (_, constraints) {
_handleTryClearCache(constraints.maxWidth - 40);
return TextScaleNotification(
child: ValueListenableBuilder<LogsState>(
valueListenable: _logsStateNotifier,
builder: (_, state, __) {
final logs = state.list;
if (logs.isEmpty) {
return NullStatus(
label: appLocalizations.nullLogsDesc,
);
}
final items = logs
.map<Widget>(
(log) => LogItem(
key: Key(log.dateTime),
log: log,
onClick: (value) {
context.commonScaffoldState?.addKeyword(value);
},
),
)
.separated(
const Divider(
height: 0,
),
)
.toList();
return Align(
alignment: Alignment.topCenter,
child: ScrollToEndBox<Log>(
controller: _scrollController,
cacheKey: _cacheKey,
dataSource: logs,
child: CommonScrollBar(
controller: _scrollController,
child: CacheItemExtentListView(
key: _key,
reverse: true,
shrinkWrap: true,
physics: NextClampingScrollPhysics(),
controller: _scrollController,
itemBuilder: (_, index) {
return items[index];
},
itemExtentBuilder: (index) {
final item = items[index];
if (item.runtimeType == Divider) {
return 0;
}
final log = logs[(index / 2).floor()];
return _getItemHeight(log);
},
itemCount: items.length,
keyBuilder: (int index) {
final item = items[index];
if (item.runtimeType == Divider) {
return "divider";
}
final log = logs[(index / 2).floor()];
return log.payload;
},
),
_currentMaxWidth = constraints.maxWidth - 40;
return ValueListenableBuilder<LogsState>(
valueListenable: _logsStateNotifier,
builder: (_, state, __) {
_preLoad();
final logs = state.list;
final items = logs
.map<Widget>(
(log) => LogItem(
key: Key(log.dateTime),
log: log,
onClick: (value) {
context.commonScaffoldState?.addKeyword(value);
},
),
),
);
},
),
onNotification: (_) {
_key.currentState?.clearCache();
)
.separated(
const Divider(
height: 0,
),
)
.toList();
final content = logs.isEmpty
? NullStatus(
label: appLocalizations.nullLogsDesc,
)
: Align(
alignment: Alignment.topCenter,
child: CommonScrollBar(
controller: _scrollController,
child: ScrollToEndBox(
controller: _scrollController,
tag: _tag,
dataSource: logs,
child: CacheItemExtentListView(
tag: _tag,
reverse: true,
shrinkWrap: true,
physics: NextClampingScrollPhysics(),
controller: _scrollController,
itemBuilder: (_, index) {
return items[index];
},
itemExtentBuilder: (index) {
if (index.isOdd) {
return 0;
}
return _getItemHeight(logs[index ~/ 2]);
},
itemCount: items.length,
keyBuilder: (int index) {
if (index.isOdd) {
return "divider";
}
return logs[index ~/ 2].payload;
},
),
),
),
);
return FadeBox(
child: state.loading
? Center(
child: CircularProgressIndicator(),
)
: content,
);
},
);
},
+2 -13
View File
@@ -23,7 +23,6 @@ class OverrideProfile extends StatefulWidget {
}
class _OverrideProfileState extends State<OverrideProfile> {
final GlobalKey<CacheItemExtentListViewState> _ruleListKey = GlobalKey();
final _controller = ScrollController();
double _currentMaxWidth = 0;
@@ -86,13 +85,6 @@ class _OverrideProfileState extends State<OverrideProfile> {
);
}
_handleTryClearCache(double maxWidth) {
if (_currentMaxWidth != maxWidth) {
_currentMaxWidth = maxWidth;
_ruleListKey.currentState?.clearCache();
}
}
_buildContent() {
return Consumer(
builder: (_, ref, child) {
@@ -116,7 +108,7 @@ class _OverrideProfileState extends State<OverrideProfile> {
},
child: LayoutBuilder(
builder: (_, constraints) {
_handleTryClearCache(constraints.maxWidth - 104);
_currentMaxWidth = constraints.maxWidth - 104;
return CommonAutoHiddenScrollBar(
controller: _controller,
child: CustomScrollView(
@@ -148,7 +140,6 @@ class _OverrideProfileState extends State<OverrideProfile> {
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 0),
sliver: RuleContent(
maxWidth: _currentMaxWidth,
ruleListKey: _ruleListKey,
),
),
SliverToBoxAdapter(
@@ -449,12 +440,10 @@ class RuleTitle extends ConsumerWidget {
}
class RuleContent extends ConsumerWidget {
final Key ruleListKey;
final double maxWidth;
const RuleContent({
super.key,
required this.ruleListKey,
required this.maxWidth,
});
@@ -602,7 +591,7 @@ class RuleContent extends ConsumerWidget {
);
}
return CacheItemExtentSliverReorderableList(
key: ruleListKey,
tag: CacheTag.rules,
itemBuilder: (context, index) {
final rule = rules[index];
return GestureDetector(
+1 -1
View File
@@ -370,7 +370,7 @@ class ProfileItem extends StatelessWidget {
),
),
title: Container(
padding: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(vertical: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
+7 -2
View File
@@ -123,8 +123,13 @@ class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
@override
Widget build(BuildContext context) {
final proxiesType =
ref.watch(proxiesStyleSettingProvider.select((state) => state.type));
final proxiesType = ref.watch(
proxiesStyleSettingProvider.select(
(state) => state.type,
),
);
ref.watch(themeSettingProvider.select((state) => state.textScale));
return switch (proxiesType) {
ProxiesType.tab => ProxiesTabFragment(
key: _proxiesTabKey,
+13 -14
View File
@@ -147,22 +147,21 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
FutureBuilder<FileInfo>(
future: _getGeoFileLastModified(geoItem.fileName),
builder: (_, snapshot) {
final height = globalState.measure.bodyMediumHeight;
return SizedBox(
height: 24,
child: FadeThroughBox(
key: Key("fade_box_${geoItem.label}"),
child: snapshot.data == null
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: Text(
snapshot.data!.desc,
height: height,
child: snapshot.data == null
? SizedBox(
width: height,
height: height,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
)
: Text(
snapshot.data!.desc,
style: context.textTheme.bodyMedium,
),
);
},
),
+2 -2
View File
@@ -526,8 +526,8 @@ class _TextScaleFactorItem extends ConsumerWidget {
data: _SliderDefaultsM3(context),
child: Slider(
padding: EdgeInsets.zero,
min: 0.8,
max: 1.2,
min: minTextScale,
max: maxTextScale,
value: textScale.scale,
onChanged: (value) {
ref.read(themeSettingProvider.notifier).updateState(
+12 -1
View File
@@ -164,6 +164,7 @@ class MessageLookup extends MessageLookupByLibrary {
"View current connections data",
),
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
"contactMe": MessageLookupByLibrary.simpleMessage("Contact me"),
"content": MessageLookupByLibrary.simpleMessage("Content"),
"contentEmptyTip": MessageLookupByLibrary.simpleMessage(
"Content cannot be empty",
@@ -363,6 +364,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"logs": MessageLookupByLibrary.simpleMessage("Logs"),
"logsDesc": MessageLookupByLibrary.simpleMessage("Log capture records"),
"logsTest": MessageLookupByLibrary.simpleMessage("Logs test"),
"loopback": MessageLookupByLibrary.simpleMessage("Loopback unlock tool"),
"loopbackDesc": MessageLookupByLibrary.simpleMessage(
"Used for UWP loopback unlocking",
@@ -410,7 +412,7 @@ class MessageLookup extends MessageLookupByLibrary {
"noInfo": MessageLookupByLibrary.simpleMessage("No info"),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("No more info"),
"noNetwork": MessageLookupByLibrary.simpleMessage("No network"),
"noNetworkApp": MessageLookupByLibrary.simpleMessage("No network App"),
"noNetworkApp": MessageLookupByLibrary.simpleMessage("No network APP"),
"noProxy": MessageLookupByLibrary.simpleMessage("No proxy"),
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
"Please create a profile or add a valid profile",
@@ -538,6 +540,15 @@ class MessageLookup extends MessageLookupByLibrary {
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
"Only recovery profiles",
),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage(
"Recovery strategy",
),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage(
"Compatible",
),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
"Override",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("Recovery success"),
"redo": MessageLookupByLibrary.simpleMessage("redo"),
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
+7
View File
@@ -118,6 +118,7 @@ class MessageLookup extends MessageLookupByLibrary {
"connections": MessageLookupByLibrary.simpleMessage("接続"),
"connectionsDesc": MessageLookupByLibrary.simpleMessage("現在の接続データを表示"),
"connectivity": MessageLookupByLibrary.simpleMessage("接続性:"),
"contactMe": MessageLookupByLibrary.simpleMessage("連絡する"),
"content": MessageLookupByLibrary.simpleMessage("内容"),
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容は必須です"),
"contentScheme": MessageLookupByLibrary.simpleMessage("コンテンツテーマ"),
@@ -261,6 +262,7 @@ class MessageLookup extends MessageLookupByLibrary {
"logcatDesc": MessageLookupByLibrary.simpleMessage("無効化するとログエントリを非表示"),
"logs": MessageLookupByLibrary.simpleMessage("ログ"),
"logsDesc": MessageLookupByLibrary.simpleMessage("ログキャプチャ記録"),
"logsTest": MessageLookupByLibrary.simpleMessage("ログテスト"),
"loopback": MessageLookupByLibrary.simpleMessage("ループバック解除ツール"),
"loopbackDesc": MessageLookupByLibrary.simpleMessage("UWPループバック解除用"),
"loose": MessageLookupByLibrary.simpleMessage("疎"),
@@ -394,6 +396,11 @@ class MessageLookup extends MessageLookupByLibrary {
"recovery": MessageLookupByLibrary.simpleMessage("復元"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("全データ復元"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("プロファイルのみ復元"),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage("リカバリー戦略"),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("互換性"),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
"オーバーライド",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("復元成功"),
"redo": MessageLookupByLibrary.simpleMessage("やり直す"),
"regExp": MessageLookupByLibrary.simpleMessage("正規表現"),
+11
View File
@@ -170,6 +170,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Просмотр текущих данных о соединениях",
),
"connectivity": MessageLookupByLibrary.simpleMessage("Связь:"),
"contactMe": MessageLookupByLibrary.simpleMessage("Свяжитесь со мной"),
"content": MessageLookupByLibrary.simpleMessage("Содержание"),
"contentEmptyTip": MessageLookupByLibrary.simpleMessage(
"Содержание не может быть пустым",
@@ -385,6 +386,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"logs": MessageLookupByLibrary.simpleMessage("Логи"),
"logsDesc": MessageLookupByLibrary.simpleMessage("Записи захвата логов"),
"logsTest": MessageLookupByLibrary.simpleMessage("Тест журналов"),
"loopback": MessageLookupByLibrary.simpleMessage(
"Инструмент разблокировки Loopback",
),
@@ -572,6 +574,15 @@ class MessageLookup extends MessageLookupByLibrary {
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
"Только восстановление профилей",
),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage(
"Стратегия восстановления",
),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage(
"Совместимый",
),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
"Переопределение",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage(
"Восстановление успешно",
),
+5
View File
@@ -108,6 +108,7 @@ class MessageLookup extends MessageLookupByLibrary {
"connections": MessageLookupByLibrary.simpleMessage("连接"),
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
"contactMe": MessageLookupByLibrary.simpleMessage("联系我"),
"content": MessageLookupByLibrary.simpleMessage("内容"),
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容不能为空"),
"contentScheme": MessageLookupByLibrary.simpleMessage("内容主题"),
@@ -233,6 +234,7 @@ class MessageLookup extends MessageLookupByLibrary {
"logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"),
"logs": MessageLookupByLibrary.simpleMessage("日志"),
"logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"),
"logsTest": MessageLookupByLibrary.simpleMessage("日志测试"),
"loopback": MessageLookupByLibrary.simpleMessage("回环解锁工具"),
"loopbackDesc": MessageLookupByLibrary.simpleMessage("用于UWP回环解锁"),
"loose": MessageLookupByLibrary.simpleMessage("宽松"),
@@ -346,6 +348,9 @@ class MessageLookup extends MessageLookupByLibrary {
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage("恢复策略"),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("兼容"),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage("覆盖"),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
"redo": MessageLookupByLibrary.simpleMessage("重做"),
"regExp": MessageLookupByLibrary.simpleMessage("正则"),
+42 -2
View File
@@ -3070,15 +3070,55 @@ class AppLocalizations {
return Intl.message('System APP', name: 'systemApp', desc: '', args: []);
}
/// `No network App`
/// `No network APP`
String get noNetworkApp {
return Intl.message(
'No network App',
'No network APP',
name: 'noNetworkApp',
desc: '',
args: [],
);
}
/// `Contact me`
String get contactMe {
return Intl.message('Contact me', name: 'contactMe', desc: '', args: []);
}
/// `Recovery strategy`
String get recoveryStrategy {
return Intl.message(
'Recovery strategy',
name: 'recoveryStrategy',
desc: '',
args: [],
);
}
/// `Override`
String get recoveryStrategy_override {
return Intl.message(
'Override',
name: 'recoveryStrategy_override',
desc: '',
args: [],
);
}
/// `Compatible`
String get recoveryStrategy_compatible {
return Intl.message(
'Compatible',
name: 'recoveryStrategy_compatible',
desc: '',
args: [],
);
}
/// `Logs test`
String get logsTest {
return Intl.message('Logs test', name: 'logsTest', desc: '', args: []);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
+4
View File
@@ -21,12 +21,16 @@ import 'common/common.dart';
Future<void> main() async {
globalState.isService = false;
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = (details) {
commonPrint.log(details.stack.toString());
};
final version = await system.version;
await clashCore.preload();
await globalState.initApp(version);
await android?.init();
await window?.init(version);
globalState.isPre = const String.fromEnvironment("APP_ENV") != 'stable';
globalState.coreSHA256 = const String.fromEnvironment("CORE_SHA256");
HttpOverrides.global = FlClashHttpOverrides();
runApp(ProviderScope(
child: const Application(),
+12 -3
View File
@@ -1,11 +1,13 @@
import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class AppStateManager extends StatefulWidget {
class AppStateManager extends ConsumerStatefulWidget {
final Widget child;
const AppStateManager({
@@ -14,15 +16,22 @@ class AppStateManager extends StatefulWidget {
});
@override
State<AppStateManager> createState() => _AppStateManagerState();
ConsumerState<AppStateManager> createState() => _AppStateManagerState();
}
class _AppStateManagerState extends State<AppStateManager>
class _AppStateManagerState extends ConsumerState<AppStateManager>
with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
ref.listenManual(layoutChangeProvider, (prev, next) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (prev != next) {
globalState.cacheHeightMap = {};
}
});
});
}
@override
+3 -3
View File
@@ -71,7 +71,7 @@ class _ClashContainerState extends ConsumerState<ClashManager>
@override
void onLog(Log log) {
ref.watch(logsProvider.notifier).addLog(log);
ref.read(logsProvider.notifier).addLog(log);
if (log.logLevel == LogLevel.error) {
globalState.showNotifier(log.payload);
}
@@ -80,13 +80,13 @@ class _ClashContainerState extends ConsumerState<ClashManager>
@override
void onRequest(Connection connection) async {
ref.watch(requestsProvider.notifier).addRequest(connection);
ref.read(requestsProvider.notifier).addRequest(connection);
super.onRequest(connection);
}
@override
Future<void> onLoaded(String providerName) async {
ref.watch(providersProvider.notifier).setProvider(
ref.read(providersProvider.notifier).setProvider(
await clashCore.getExternalProvider(
providerName,
),
+2 -2
View File
@@ -23,9 +23,9 @@ class ThemeManager extends ConsumerWidget {
final double textScaleFactor = max(
min(
textScale.enable ? textScale.scale : defaultTextScaleFactor,
1.2,
maxTextScale,
),
0.8,
minTextScale,
);
globalState.measure = Measure.of(context, textScaleFactor);
+2 -1
View File
@@ -147,7 +147,8 @@ class Tun with _$Tun {
const factory Tun({
@Default(false) bool enable,
@Default(appName) String device,
@Default(TunStack.gvisor) TunStack stack,
@JsonKey(name: "auto-route") @Default(false) bool autoRoute,
@Default(TunStack.mixed) TunStack stack,
@JsonKey(name: "dns-hijack") @Default(["any:53"]) List<String> dnsHijack,
@JsonKey(name: "route-address") @Default([]) List<String> routeAddress,
}) = _Tun;
+16
View File
@@ -120,6 +120,7 @@ class LogsState with _$LogsState {
@Default([]) List<Log> logs,
@Default([]) List<String> keywords,
@Default("") String query,
@Default(false) bool loading,
}) = _LogsState;
}
@@ -143,6 +144,7 @@ class ConnectionsState with _$ConnectionsState {
@Default([]) List<Connection> connections,
@Default([]) List<String> keywords,
@Default("") String query,
@Default(false) bool loading,
}) = _ConnectionsState;
}
@@ -512,3 +514,17 @@ class PopupMenuItemData {
final IconData? icon;
final PopupMenuItemType? type;
}
@freezed
class TextPainterParams with _$TextPainterParams {
const factory TextPainterParams({
required String? text,
required double? fontSize,
required double textScaleFactor,
@Default(double.infinity) double maxWidth,
int? maxLines,
}) = _TextPainterParams;
factory TextPainterParams.fromJson(Map<String, Object?> json) =>
_$TextPainterParamsFromJson(json);
}
+3 -2
View File
@@ -75,7 +75,7 @@ class AppSettingProps with _$AppSettingProps {
@Default(false) bool autoLaunch,
@Default(false) bool silentLaunch,
@Default(false) bool autoRun,
@Default(true) bool openLogs,
@Default(false) bool openLogs,
@Default(true) bool closeConnections,
@Default(defaultTestUrl) String testUrl,
@Default(true) bool isAnimateToPage,
@@ -85,6 +85,7 @@ class AppSettingProps with _$AppSettingProps {
@Default(true) bool minimizeOnExit,
@Default(false) bool hidden,
@Default(false) bool developerMode,
@Default(RecoveryStrategy.compatible) RecoveryStrategy recoveryStrategy,
}) = _AppSettingProps;
factory AppSettingProps.fromJson(Map<String, Object?> json) =>
@@ -191,7 +192,7 @@ class ThemeProps with _$ThemeProps {
int? primaryColor,
@Default(defaultPrimaryColors) List<int> primaryColors,
@Default(ThemeMode.dark) ThemeMode themeMode,
@Default(DynamicSchemeVariant.tonalSpot) DynamicSchemeVariant schemeVariant,
@Default(DynamicSchemeVariant.content) DynamicSchemeVariant schemeVariant,
@Default(false) bool pureBlack,
@Default(TextScale()) TextScale textScale,
}) = _ThemeProps;
+27 -2
View File
@@ -660,6 +660,8 @@ Tun _$TunFromJson(Map<String, dynamic> json) {
mixin _$Tun {
bool get enable => throw _privateConstructorUsedError;
String get device => throw _privateConstructorUsedError;
@JsonKey(name: "auto-route")
bool get autoRoute => throw _privateConstructorUsedError;
TunStack get stack => throw _privateConstructorUsedError;
@JsonKey(name: "dns-hijack")
List<String> get dnsHijack => throw _privateConstructorUsedError;
@@ -683,6 +685,7 @@ abstract class $TunCopyWith<$Res> {
$Res call(
{bool enable,
String device,
@JsonKey(name: "auto-route") bool autoRoute,
TunStack stack,
@JsonKey(name: "dns-hijack") List<String> dnsHijack,
@JsonKey(name: "route-address") List<String> routeAddress});
@@ -704,6 +707,7 @@ class _$TunCopyWithImpl<$Res, $Val extends Tun> implements $TunCopyWith<$Res> {
$Res call({
Object? enable = null,
Object? device = null,
Object? autoRoute = null,
Object? stack = null,
Object? dnsHijack = null,
Object? routeAddress = null,
@@ -717,6 +721,10 @@ class _$TunCopyWithImpl<$Res, $Val extends Tun> implements $TunCopyWith<$Res> {
? _value.device
: device // ignore: cast_nullable_to_non_nullable
as String,
autoRoute: null == autoRoute
? _value.autoRoute
: autoRoute // ignore: cast_nullable_to_non_nullable
as bool,
stack: null == stack
? _value.stack
: stack // ignore: cast_nullable_to_non_nullable
@@ -742,6 +750,7 @@ abstract class _$$TunImplCopyWith<$Res> implements $TunCopyWith<$Res> {
$Res call(
{bool enable,
String device,
@JsonKey(name: "auto-route") bool autoRoute,
TunStack stack,
@JsonKey(name: "dns-hijack") List<String> dnsHijack,
@JsonKey(name: "route-address") List<String> routeAddress});
@@ -760,6 +769,7 @@ class __$$TunImplCopyWithImpl<$Res> extends _$TunCopyWithImpl<$Res, _$TunImpl>
$Res call({
Object? enable = null,
Object? device = null,
Object? autoRoute = null,
Object? stack = null,
Object? dnsHijack = null,
Object? routeAddress = null,
@@ -773,6 +783,10 @@ class __$$TunImplCopyWithImpl<$Res> extends _$TunCopyWithImpl<$Res, _$TunImpl>
? _value.device
: device // ignore: cast_nullable_to_non_nullable
as String,
autoRoute: null == autoRoute
? _value.autoRoute
: autoRoute // ignore: cast_nullable_to_non_nullable
as bool,
stack: null == stack
? _value.stack
: stack // ignore: cast_nullable_to_non_nullable
@@ -795,7 +809,8 @@ class _$TunImpl implements _Tun {
const _$TunImpl(
{this.enable = false,
this.device = appName,
this.stack = TunStack.gvisor,
@JsonKey(name: "auto-route") this.autoRoute = false,
this.stack = TunStack.mixed,
@JsonKey(name: "dns-hijack")
final List<String> dnsHijack = const ["any:53"],
@JsonKey(name: "route-address")
@@ -813,6 +828,9 @@ class _$TunImpl implements _Tun {
@JsonKey()
final String device;
@override
@JsonKey(name: "auto-route")
final bool autoRoute;
@override
@JsonKey()
final TunStack stack;
final List<String> _dnsHijack;
@@ -835,7 +853,7 @@ class _$TunImpl implements _Tun {
@override
String toString() {
return 'Tun(enable: $enable, device: $device, stack: $stack, dnsHijack: $dnsHijack, routeAddress: $routeAddress)';
return 'Tun(enable: $enable, device: $device, autoRoute: $autoRoute, stack: $stack, dnsHijack: $dnsHijack, routeAddress: $routeAddress)';
}
@override
@@ -845,6 +863,8 @@ class _$TunImpl implements _Tun {
other is _$TunImpl &&
(identical(other.enable, enable) || other.enable == enable) &&
(identical(other.device, device) || other.device == device) &&
(identical(other.autoRoute, autoRoute) ||
other.autoRoute == autoRoute) &&
(identical(other.stack, stack) || other.stack == stack) &&
const DeepCollectionEquality()
.equals(other._dnsHijack, _dnsHijack) &&
@@ -858,6 +878,7 @@ class _$TunImpl implements _Tun {
runtimeType,
enable,
device,
autoRoute,
stack,
const DeepCollectionEquality().hash(_dnsHijack),
const DeepCollectionEquality().hash(_routeAddress));
@@ -882,6 +903,7 @@ abstract class _Tun implements Tun {
const factory _Tun(
{final bool enable,
final String device,
@JsonKey(name: "auto-route") final bool autoRoute,
final TunStack stack,
@JsonKey(name: "dns-hijack") final List<String> dnsHijack,
@JsonKey(name: "route-address") final List<String> routeAddress}) =
@@ -894,6 +916,9 @@ abstract class _Tun implements Tun {
@override
String get device;
@override
@JsonKey(name: "auto-route")
bool get autoRoute;
@override
TunStack get stack;
@override
@JsonKey(name: "dns-hijack")
+3 -1
View File
@@ -66,8 +66,9 @@ Map<String, dynamic> _$$RuleProviderImplToJson(_$RuleProviderImpl instance) =>
_$TunImpl _$$TunImplFromJson(Map<String, dynamic> json) => _$TunImpl(
enable: json['enable'] as bool? ?? false,
device: json['device'] as String? ?? appName,
autoRoute: json['auto-route'] as bool? ?? false,
stack: $enumDecodeNullable(_$TunStackEnumMap, json['stack']) ??
TunStack.gvisor,
TunStack.mixed,
dnsHijack: (json['dns-hijack'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
@@ -81,6 +82,7 @@ _$TunImpl _$$TunImplFromJson(Map<String, dynamic> json) => _$TunImpl(
Map<String, dynamic> _$$TunImplToJson(_$TunImpl instance) => <String, dynamic>{
'enable': instance.enable,
'device': instance.device,
'auto-route': instance.autoRoute,
'stack': _$TunStackEnumMap[instance.stack]!,
'dns-hijack': instance.dnsHijack,
'route-address': instance.routeAddress,
+302 -14
View File
@@ -1320,6 +1320,7 @@ mixin _$LogsState {
List<Log> get logs => throw _privateConstructorUsedError;
List<String> get keywords => throw _privateConstructorUsedError;
String get query => throw _privateConstructorUsedError;
bool get loading => throw _privateConstructorUsedError;
/// Create a copy of LogsState
/// with the given fields replaced by the non-null parameter values.
@@ -1333,7 +1334,8 @@ abstract class $LogsStateCopyWith<$Res> {
factory $LogsStateCopyWith(LogsState value, $Res Function(LogsState) then) =
_$LogsStateCopyWithImpl<$Res, LogsState>;
@useResult
$Res call({List<Log> logs, List<String> keywords, String query});
$Res call(
{List<Log> logs, List<String> keywords, String query, bool loading});
}
/// @nodoc
@@ -1354,6 +1356,7 @@ class _$LogsStateCopyWithImpl<$Res, $Val extends LogsState>
Object? logs = null,
Object? keywords = null,
Object? query = null,
Object? loading = null,
}) {
return _then(_value.copyWith(
logs: null == logs
@@ -1368,6 +1371,10 @@ class _$LogsStateCopyWithImpl<$Res, $Val extends LogsState>
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String,
loading: null == loading
? _value.loading
: loading // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
@@ -1380,7 +1387,8 @@ abstract class _$$LogsStateImplCopyWith<$Res>
__$$LogsStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({List<Log> logs, List<String> keywords, String query});
$Res call(
{List<Log> logs, List<String> keywords, String query, bool loading});
}
/// @nodoc
@@ -1399,6 +1407,7 @@ class __$$LogsStateImplCopyWithImpl<$Res>
Object? logs = null,
Object? keywords = null,
Object? query = null,
Object? loading = null,
}) {
return _then(_$LogsStateImpl(
logs: null == logs
@@ -1413,6 +1422,10 @@ class __$$LogsStateImplCopyWithImpl<$Res>
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String,
loading: null == loading
? _value.loading
: loading // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -1423,7 +1436,8 @@ class _$LogsStateImpl implements _LogsState {
const _$LogsStateImpl(
{final List<Log> logs = const [],
final List<String> keywords = const [],
this.query = ""})
this.query = "",
this.loading = false})
: _logs = logs,
_keywords = keywords;
@@ -1448,10 +1462,13 @@ class _$LogsStateImpl implements _LogsState {
@override
@JsonKey()
final String query;
@override
@JsonKey()
final bool loading;
@override
String toString() {
return 'LogsState(logs: $logs, keywords: $keywords, query: $query)';
return 'LogsState(logs: $logs, keywords: $keywords, query: $query, loading: $loading)';
}
@override
@@ -1461,7 +1478,8 @@ class _$LogsStateImpl implements _LogsState {
other is _$LogsStateImpl &&
const DeepCollectionEquality().equals(other._logs, _logs) &&
const DeepCollectionEquality().equals(other._keywords, _keywords) &&
(identical(other.query, query) || other.query == query));
(identical(other.query, query) || other.query == query) &&
(identical(other.loading, loading) || other.loading == loading));
}
@override
@@ -1469,7 +1487,8 @@ class _$LogsStateImpl implements _LogsState {
runtimeType,
const DeepCollectionEquality().hash(_logs),
const DeepCollectionEquality().hash(_keywords),
query);
query,
loading);
/// Create a copy of LogsState
/// with the given fields replaced by the non-null parameter values.
@@ -1484,7 +1503,8 @@ abstract class _LogsState implements LogsState {
const factory _LogsState(
{final List<Log> logs,
final List<String> keywords,
final String query}) = _$LogsStateImpl;
final String query,
final bool loading}) = _$LogsStateImpl;
@override
List<Log> get logs;
@@ -1492,6 +1512,8 @@ abstract class _LogsState implements LogsState {
List<String> get keywords;
@override
String get query;
@override
bool get loading;
/// Create a copy of LogsState
/// with the given fields replaced by the non-null parameter values.
@@ -1506,6 +1528,7 @@ mixin _$ConnectionsState {
List<Connection> get connections => throw _privateConstructorUsedError;
List<String> get keywords => throw _privateConstructorUsedError;
String get query => throw _privateConstructorUsedError;
bool get loading => throw _privateConstructorUsedError;
/// Create a copy of ConnectionsState
/// with the given fields replaced by the non-null parameter values.
@@ -1521,7 +1544,10 @@ abstract class $ConnectionsStateCopyWith<$Res> {
_$ConnectionsStateCopyWithImpl<$Res, ConnectionsState>;
@useResult
$Res call(
{List<Connection> connections, List<String> keywords, String query});
{List<Connection> connections,
List<String> keywords,
String query,
bool loading});
}
/// @nodoc
@@ -1542,6 +1568,7 @@ class _$ConnectionsStateCopyWithImpl<$Res, $Val extends ConnectionsState>
Object? connections = null,
Object? keywords = null,
Object? query = null,
Object? loading = null,
}) {
return _then(_value.copyWith(
connections: null == connections
@@ -1556,6 +1583,10 @@ class _$ConnectionsStateCopyWithImpl<$Res, $Val extends ConnectionsState>
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String,
loading: null == loading
? _value.loading
: loading // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
@@ -1569,7 +1600,10 @@ abstract class _$$ConnectionsStateImplCopyWith<$Res>
@override
@useResult
$Res call(
{List<Connection> connections, List<String> keywords, String query});
{List<Connection> connections,
List<String> keywords,
String query,
bool loading});
}
/// @nodoc
@@ -1588,6 +1622,7 @@ class __$$ConnectionsStateImplCopyWithImpl<$Res>
Object? connections = null,
Object? keywords = null,
Object? query = null,
Object? loading = null,
}) {
return _then(_$ConnectionsStateImpl(
connections: null == connections
@@ -1602,6 +1637,10 @@ class __$$ConnectionsStateImplCopyWithImpl<$Res>
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String,
loading: null == loading
? _value.loading
: loading // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -1612,7 +1651,8 @@ class _$ConnectionsStateImpl implements _ConnectionsState {
const _$ConnectionsStateImpl(
{final List<Connection> connections = const [],
final List<String> keywords = const [],
this.query = ""})
this.query = "",
this.loading = false})
: _connections = connections,
_keywords = keywords;
@@ -1637,10 +1677,13 @@ class _$ConnectionsStateImpl implements _ConnectionsState {
@override
@JsonKey()
final String query;
@override
@JsonKey()
final bool loading;
@override
String toString() {
return 'ConnectionsState(connections: $connections, keywords: $keywords, query: $query)';
return 'ConnectionsState(connections: $connections, keywords: $keywords, query: $query, loading: $loading)';
}
@override
@@ -1651,7 +1694,8 @@ class _$ConnectionsStateImpl implements _ConnectionsState {
const DeepCollectionEquality()
.equals(other._connections, _connections) &&
const DeepCollectionEquality().equals(other._keywords, _keywords) &&
(identical(other.query, query) || other.query == query));
(identical(other.query, query) || other.query == query) &&
(identical(other.loading, loading) || other.loading == loading));
}
@override
@@ -1659,7 +1703,8 @@ class _$ConnectionsStateImpl implements _ConnectionsState {
runtimeType,
const DeepCollectionEquality().hash(_connections),
const DeepCollectionEquality().hash(_keywords),
query);
query,
loading);
/// Create a copy of ConnectionsState
/// with the given fields replaced by the non-null parameter values.
@@ -1675,7 +1720,8 @@ abstract class _ConnectionsState implements ConnectionsState {
const factory _ConnectionsState(
{final List<Connection> connections,
final List<String> keywords,
final String query}) = _$ConnectionsStateImpl;
final String query,
final bool loading}) = _$ConnectionsStateImpl;
@override
List<Connection> get connections;
@@ -1683,6 +1729,8 @@ abstract class _ConnectionsState implements ConnectionsState {
List<String> get keywords;
@override
String get query;
@override
bool get loading;
/// Create a copy of ConnectionsState
/// with the given fields replaced by the non-null parameter values.
@@ -3178,3 +3226,243 @@ abstract class _Field implements Field {
_$$FieldImplCopyWith<_$FieldImpl> get copyWith =>
throw _privateConstructorUsedError;
}
TextPainterParams _$TextPainterParamsFromJson(Map<String, dynamic> json) {
return _TextPainterParams.fromJson(json);
}
/// @nodoc
mixin _$TextPainterParams {
String? get text => throw _privateConstructorUsedError;
double? get fontSize => throw _privateConstructorUsedError;
double get textScaleFactor => throw _privateConstructorUsedError;
double get maxWidth => throw _privateConstructorUsedError;
int? get maxLines => throw _privateConstructorUsedError;
/// Serializes this TextPainterParams to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of TextPainterParams
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$TextPainterParamsCopyWith<TextPainterParams> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TextPainterParamsCopyWith<$Res> {
factory $TextPainterParamsCopyWith(
TextPainterParams value, $Res Function(TextPainterParams) then) =
_$TextPainterParamsCopyWithImpl<$Res, TextPainterParams>;
@useResult
$Res call(
{String? text,
double? fontSize,
double textScaleFactor,
double maxWidth,
int? maxLines});
}
/// @nodoc
class _$TextPainterParamsCopyWithImpl<$Res, $Val extends TextPainterParams>
implements $TextPainterParamsCopyWith<$Res> {
_$TextPainterParamsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of TextPainterParams
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? text = freezed,
Object? fontSize = freezed,
Object? textScaleFactor = null,
Object? maxWidth = null,
Object? maxLines = freezed,
}) {
return _then(_value.copyWith(
text: freezed == text
? _value.text
: text // ignore: cast_nullable_to_non_nullable
as String?,
fontSize: freezed == fontSize
? _value.fontSize
: fontSize // ignore: cast_nullable_to_non_nullable
as double?,
textScaleFactor: null == textScaleFactor
? _value.textScaleFactor
: textScaleFactor // ignore: cast_nullable_to_non_nullable
as double,
maxWidth: null == maxWidth
? _value.maxWidth
: maxWidth // ignore: cast_nullable_to_non_nullable
as double,
maxLines: freezed == maxLines
? _value.maxLines
: maxLines // ignore: cast_nullable_to_non_nullable
as int?,
) as $Val);
}
}
/// @nodoc
abstract class _$$TextPainterParamsImplCopyWith<$Res>
implements $TextPainterParamsCopyWith<$Res> {
factory _$$TextPainterParamsImplCopyWith(_$TextPainterParamsImpl value,
$Res Function(_$TextPainterParamsImpl) then) =
__$$TextPainterParamsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String? text,
double? fontSize,
double textScaleFactor,
double maxWidth,
int? maxLines});
}
/// @nodoc
class __$$TextPainterParamsImplCopyWithImpl<$Res>
extends _$TextPainterParamsCopyWithImpl<$Res, _$TextPainterParamsImpl>
implements _$$TextPainterParamsImplCopyWith<$Res> {
__$$TextPainterParamsImplCopyWithImpl(_$TextPainterParamsImpl _value,
$Res Function(_$TextPainterParamsImpl) _then)
: super(_value, _then);
/// Create a copy of TextPainterParams
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? text = freezed,
Object? fontSize = freezed,
Object? textScaleFactor = null,
Object? maxWidth = null,
Object? maxLines = freezed,
}) {
return _then(_$TextPainterParamsImpl(
text: freezed == text
? _value.text
: text // ignore: cast_nullable_to_non_nullable
as String?,
fontSize: freezed == fontSize
? _value.fontSize
: fontSize // ignore: cast_nullable_to_non_nullable
as double?,
textScaleFactor: null == textScaleFactor
? _value.textScaleFactor
: textScaleFactor // ignore: cast_nullable_to_non_nullable
as double,
maxWidth: null == maxWidth
? _value.maxWidth
: maxWidth // ignore: cast_nullable_to_non_nullable
as double,
maxLines: freezed == maxLines
? _value.maxLines
: maxLines // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$TextPainterParamsImpl implements _TextPainterParams {
const _$TextPainterParamsImpl(
{required this.text,
required this.fontSize,
required this.textScaleFactor,
this.maxWidth = double.infinity,
this.maxLines});
factory _$TextPainterParamsImpl.fromJson(Map<String, dynamic> json) =>
_$$TextPainterParamsImplFromJson(json);
@override
final String? text;
@override
final double? fontSize;
@override
final double textScaleFactor;
@override
@JsonKey()
final double maxWidth;
@override
final int? maxLines;
@override
String toString() {
return 'TextPainterParams(text: $text, fontSize: $fontSize, textScaleFactor: $textScaleFactor, maxWidth: $maxWidth, maxLines: $maxLines)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TextPainterParamsImpl &&
(identical(other.text, text) || other.text == text) &&
(identical(other.fontSize, fontSize) ||
other.fontSize == fontSize) &&
(identical(other.textScaleFactor, textScaleFactor) ||
other.textScaleFactor == textScaleFactor) &&
(identical(other.maxWidth, maxWidth) ||
other.maxWidth == maxWidth) &&
(identical(other.maxLines, maxLines) ||
other.maxLines == maxLines));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, text, fontSize, textScaleFactor, maxWidth, maxLines);
/// Create a copy of TextPainterParams
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$TextPainterParamsImplCopyWith<_$TextPainterParamsImpl> get copyWith =>
__$$TextPainterParamsImplCopyWithImpl<_$TextPainterParamsImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TextPainterParamsImplToJson(
this,
);
}
}
abstract class _TextPainterParams implements TextPainterParams {
const factory _TextPainterParams(
{required final String? text,
required final double? fontSize,
required final double textScaleFactor,
final double maxWidth,
final int? maxLines}) = _$TextPainterParamsImpl;
factory _TextPainterParams.fromJson(Map<String, dynamic> json) =
_$TextPainterParamsImpl.fromJson;
@override
String? get text;
@override
double? get fontSize;
@override
double get textScaleFactor;
@override
double get maxWidth;
@override
int? get maxLines;
/// Create a copy of TextPainterParams
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$TextPainterParamsImplCopyWith<_$TextPainterParamsImpl> get copyWith =>
throw _privateConstructorUsedError;
}
+20
View File
@@ -198,3 +198,23 @@ const _$KeyboardModifierEnumMap = {
KeyboardModifier.meta: 'meta',
KeyboardModifier.shift: 'shift',
};
_$TextPainterParamsImpl _$$TextPainterParamsImplFromJson(
Map<String, dynamic> json) =>
_$TextPainterParamsImpl(
text: json['text'] as String?,
fontSize: (json['fontSize'] as num?)?.toDouble(),
textScaleFactor: (json['textScaleFactor'] as num).toDouble(),
maxWidth: (json['maxWidth'] as num?)?.toDouble() ?? double.infinity,
maxLines: (json['maxLines'] as num?)?.toInt(),
);
Map<String, dynamic> _$$TextPainterParamsImplToJson(
_$TextPainterParamsImpl instance) =>
<String, dynamic>{
'text': instance.text,
'fontSize': instance.fontSize,
'textScaleFactor': instance.textScaleFactor,
'maxWidth': instance.maxWidth,
'maxLines': instance.maxLines,
};
+32 -9
View File
@@ -38,6 +38,7 @@ mixin _$AppSettingProps {
bool get minimizeOnExit => throw _privateConstructorUsedError;
bool get hidden => throw _privateConstructorUsedError;
bool get developerMode => throw _privateConstructorUsedError;
RecoveryStrategy get recoveryStrategy => throw _privateConstructorUsedError;
/// Serializes this AppSettingProps to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -72,7 +73,8 @@ abstract class $AppSettingPropsCopyWith<$Res> {
bool disclaimerAccepted,
bool minimizeOnExit,
bool hidden,
bool developerMode});
bool developerMode,
RecoveryStrategy recoveryStrategy});
}
/// @nodoc
@@ -106,6 +108,7 @@ class _$AppSettingPropsCopyWithImpl<$Res, $Val extends AppSettingProps>
Object? minimizeOnExit = null,
Object? hidden = null,
Object? developerMode = null,
Object? recoveryStrategy = null,
}) {
return _then(_value.copyWith(
locale: freezed == locale
@@ -172,6 +175,10 @@ class _$AppSettingPropsCopyWithImpl<$Res, $Val extends AppSettingProps>
? _value.developerMode
: developerMode // ignore: cast_nullable_to_non_nullable
as bool,
recoveryStrategy: null == recoveryStrategy
? _value.recoveryStrategy
: recoveryStrategy // ignore: cast_nullable_to_non_nullable
as RecoveryStrategy,
) as $Val);
}
}
@@ -201,7 +208,8 @@ abstract class _$$AppSettingPropsImplCopyWith<$Res>
bool disclaimerAccepted,
bool minimizeOnExit,
bool hidden,
bool developerMode});
bool developerMode,
RecoveryStrategy recoveryStrategy});
}
/// @nodoc
@@ -233,6 +241,7 @@ class __$$AppSettingPropsImplCopyWithImpl<$Res>
Object? minimizeOnExit = null,
Object? hidden = null,
Object? developerMode = null,
Object? recoveryStrategy = null,
}) {
return _then(_$AppSettingPropsImpl(
locale: freezed == locale
@@ -299,6 +308,10 @@ class __$$AppSettingPropsImplCopyWithImpl<$Res>
? _value.developerMode
: developerMode // ignore: cast_nullable_to_non_nullable
as bool,
recoveryStrategy: null == recoveryStrategy
? _value.recoveryStrategy
: recoveryStrategy // ignore: cast_nullable_to_non_nullable
as RecoveryStrategy,
));
}
}
@@ -314,7 +327,7 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
this.autoLaunch = false,
this.silentLaunch = false,
this.autoRun = false,
this.openLogs = true,
this.openLogs = false,
this.closeConnections = true,
this.testUrl = defaultTestUrl,
this.isAnimateToPage = true,
@@ -323,7 +336,8 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
this.disclaimerAccepted = false,
this.minimizeOnExit = true,
this.hidden = false,
this.developerMode = false})
this.developerMode = false,
this.recoveryStrategy = RecoveryStrategy.compatible})
: _dashboardWidgets = dashboardWidgets;
factory _$AppSettingPropsImpl.fromJson(Map<String, dynamic> json) =>
@@ -383,10 +397,13 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
@override
@JsonKey()
final bool developerMode;
@override
@JsonKey()
final RecoveryStrategy recoveryStrategy;
@override
String toString() {
return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode)';
return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, recoveryStrategy: $recoveryStrategy)';
}
@override
@@ -421,7 +438,9 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
other.minimizeOnExit == minimizeOnExit) &&
(identical(other.hidden, hidden) || other.hidden == hidden) &&
(identical(other.developerMode, developerMode) ||
other.developerMode == developerMode));
other.developerMode == developerMode) &&
(identical(other.recoveryStrategy, recoveryStrategy) ||
other.recoveryStrategy == recoveryStrategy));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -443,7 +462,8 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
disclaimerAccepted,
minimizeOnExit,
hidden,
developerMode);
developerMode,
recoveryStrategy);
/// Create a copy of AppSettingProps
/// with the given fields replaced by the non-null parameter values.
@@ -480,7 +500,8 @@ abstract class _AppSettingProps implements AppSettingProps {
final bool disclaimerAccepted,
final bool minimizeOnExit,
final bool hidden,
final bool developerMode}) = _$AppSettingPropsImpl;
final bool developerMode,
final RecoveryStrategy recoveryStrategy}) = _$AppSettingPropsImpl;
factory _AppSettingProps.fromJson(Map<String, dynamic> json) =
_$AppSettingPropsImpl.fromJson;
@@ -518,6 +539,8 @@ abstract class _AppSettingProps implements AppSettingProps {
bool get hidden;
@override
bool get developerMode;
@override
RecoveryStrategy get recoveryStrategy;
/// Create a copy of AppSettingProps
/// with the given fields replaced by the non-null parameter values.
@@ -2104,7 +2127,7 @@ class _$ThemePropsImpl implements _ThemeProps {
{this.primaryColor,
final List<int> primaryColors = defaultPrimaryColors,
this.themeMode = ThemeMode.dark,
this.schemeVariant = DynamicSchemeVariant.tonalSpot,
this.schemeVariant = DynamicSchemeVariant.content,
this.pureBlack = false,
this.textScale = const TextScale()})
: _primaryColors = primaryColors;
+11 -2
View File
@@ -17,7 +17,7 @@ _$AppSettingPropsImpl _$$AppSettingPropsImplFromJson(
autoLaunch: json['autoLaunch'] as bool? ?? false,
silentLaunch: json['silentLaunch'] as bool? ?? false,
autoRun: json['autoRun'] as bool? ?? false,
openLogs: json['openLogs'] as bool? ?? true,
openLogs: json['openLogs'] as bool? ?? false,
closeConnections: json['closeConnections'] as bool? ?? true,
testUrl: json['testUrl'] as String? ?? defaultTestUrl,
isAnimateToPage: json['isAnimateToPage'] as bool? ?? true,
@@ -27,6 +27,9 @@ _$AppSettingPropsImpl _$$AppSettingPropsImplFromJson(
minimizeOnExit: json['minimizeOnExit'] as bool? ?? true,
hidden: json['hidden'] as bool? ?? false,
developerMode: json['developerMode'] as bool? ?? false,
recoveryStrategy: $enumDecodeNullable(
_$RecoveryStrategyEnumMap, json['recoveryStrategy']) ??
RecoveryStrategy.compatible,
);
Map<String, dynamic> _$$AppSettingPropsImplToJson(
@@ -50,8 +53,14 @@ Map<String, dynamic> _$$AppSettingPropsImplToJson(
'minimizeOnExit': instance.minimizeOnExit,
'hidden': instance.hidden,
'developerMode': instance.developerMode,
'recoveryStrategy': _$RecoveryStrategyEnumMap[instance.recoveryStrategy]!,
};
const _$RecoveryStrategyEnumMap = {
RecoveryStrategy.compatible: 'compatible',
RecoveryStrategy.override: 'override',
};
const _$DashboardWidgetEnumMap = {
DashboardWidget.networkSpeed: 'networkSpeed',
DashboardWidget.outboundModeV2: 'outboundModeV2',
@@ -248,7 +257,7 @@ _$ThemePropsImpl _$$ThemePropsImplFromJson(Map<String, dynamic> json) =>
ThemeMode.dark,
schemeVariant: $enumDecodeNullable(
_$DynamicSchemeVariantEnumMap, json['schemeVariant']) ??
DynamicSchemeVariant.tonalSpot,
DynamicSchemeVariant.content,
pureBlack: json['pureBlack'] as bool? ?? false,
textScale: json['textScale'] == null
? const TextScale()
+33 -8
View File
@@ -3522,6 +3522,7 @@ mixin _$ClashConfigState {
bool get overrideDns => throw _privateConstructorUsedError;
ClashConfig get clashConfig => throw _privateConstructorUsedError;
OverrideData get overrideData => throw _privateConstructorUsedError;
RouteMode get routeMode => throw _privateConstructorUsedError;
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
@@ -3537,7 +3538,10 @@ abstract class $ClashConfigStateCopyWith<$Res> {
_$ClashConfigStateCopyWithImpl<$Res, ClashConfigState>;
@useResult
$Res call(
{bool overrideDns, ClashConfig clashConfig, OverrideData overrideData});
{bool overrideDns,
ClashConfig clashConfig,
OverrideData overrideData,
RouteMode routeMode});
$ClashConfigCopyWith<$Res> get clashConfig;
$OverrideDataCopyWith<$Res> get overrideData;
@@ -3561,6 +3565,7 @@ class _$ClashConfigStateCopyWithImpl<$Res, $Val extends ClashConfigState>
Object? overrideDns = null,
Object? clashConfig = null,
Object? overrideData = null,
Object? routeMode = null,
}) {
return _then(_value.copyWith(
overrideDns: null == overrideDns
@@ -3575,6 +3580,10 @@ class _$ClashConfigStateCopyWithImpl<$Res, $Val extends ClashConfigState>
? _value.overrideData
: overrideData // ignore: cast_nullable_to_non_nullable
as OverrideData,
routeMode: null == routeMode
? _value.routeMode
: routeMode // ignore: cast_nullable_to_non_nullable
as RouteMode,
) as $Val);
}
@@ -3608,7 +3617,10 @@ abstract class _$$ClashConfigStateImplCopyWith<$Res>
@override
@useResult
$Res call(
{bool overrideDns, ClashConfig clashConfig, OverrideData overrideData});
{bool overrideDns,
ClashConfig clashConfig,
OverrideData overrideData,
RouteMode routeMode});
@override
$ClashConfigCopyWith<$Res> get clashConfig;
@@ -3632,6 +3644,7 @@ class __$$ClashConfigStateImplCopyWithImpl<$Res>
Object? overrideDns = null,
Object? clashConfig = null,
Object? overrideData = null,
Object? routeMode = null,
}) {
return _then(_$ClashConfigStateImpl(
overrideDns: null == overrideDns
@@ -3646,6 +3659,10 @@ class __$$ClashConfigStateImplCopyWithImpl<$Res>
? _value.overrideData
: overrideData // ignore: cast_nullable_to_non_nullable
as OverrideData,
routeMode: null == routeMode
? _value.routeMode
: routeMode // ignore: cast_nullable_to_non_nullable
as RouteMode,
));
}
}
@@ -3656,7 +3673,8 @@ class _$ClashConfigStateImpl implements _ClashConfigState {
const _$ClashConfigStateImpl(
{required this.overrideDns,
required this.clashConfig,
required this.overrideData});
required this.overrideData,
required this.routeMode});
@override
final bool overrideDns;
@@ -3664,10 +3682,12 @@ class _$ClashConfigStateImpl implements _ClashConfigState {
final ClashConfig clashConfig;
@override
final OverrideData overrideData;
@override
final RouteMode routeMode;
@override
String toString() {
return 'ClashConfigState(overrideDns: $overrideDns, clashConfig: $clashConfig, overrideData: $overrideData)';
return 'ClashConfigState(overrideDns: $overrideDns, clashConfig: $clashConfig, overrideData: $overrideData, routeMode: $routeMode)';
}
@override
@@ -3680,12 +3700,14 @@ class _$ClashConfigStateImpl implements _ClashConfigState {
(identical(other.clashConfig, clashConfig) ||
other.clashConfig == clashConfig) &&
(identical(other.overrideData, overrideData) ||
other.overrideData == overrideData));
other.overrideData == overrideData) &&
(identical(other.routeMode, routeMode) ||
other.routeMode == routeMode));
}
@override
int get hashCode =>
Object.hash(runtimeType, overrideDns, clashConfig, overrideData);
int get hashCode => Object.hash(
runtimeType, overrideDns, clashConfig, overrideData, routeMode);
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
@@ -3701,7 +3723,8 @@ abstract class _ClashConfigState implements ClashConfigState {
const factory _ClashConfigState(
{required final bool overrideDns,
required final ClashConfig clashConfig,
required final OverrideData overrideData}) = _$ClashConfigStateImpl;
required final OverrideData overrideData,
required final RouteMode routeMode}) = _$ClashConfigStateImpl;
@override
bool get overrideDns;
@@ -3709,6 +3732,8 @@ abstract class _ClashConfigState implements ClashConfigState {
ClashConfig get clashConfig;
@override
OverrideData get overrideData;
@override
RouteMode get routeMode;
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
+1
View File
@@ -228,6 +228,7 @@ class ClashConfigState with _$ClashConfigState {
required bool overrideDns,
required ClashConfig clashConfig,
required OverrideData overrideData,
required RouteMode routeMode,
}) = _ClashConfigState;
}
+2 -1
View File
@@ -127,6 +127,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
);
},
popup: CommonPopupMenu(
minWidth: 180,
items: [
PopupMenuItemData(
icon: Icons.search,
@@ -189,7 +190,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
shortcutsActivatorsBuilder: DefaultCodeShortcutsActivatorsBuilder(),
controller: _controller,
style: CodeEditorStyle(
fontSize: 14,
fontSize: 14.ap,
fontFamily: FontFamily.jetBrainsMono.value,
codeTheme: CodeHighlightTheme(
languages: {
+2 -2
View File
@@ -126,7 +126,7 @@ class Profiles extends _$Profiles with AutoDisposeNotifierMixin {
}
}
setProfile(Profile profile, {bool force = true}) {
setProfile(Profile profile) {
final List<Profile> profilesTemp = List.from(state);
final index =
profilesTemp.indexWhere((element) => element.id == profile.id);
@@ -135,7 +135,7 @@ class Profiles extends _$Profiles with AutoDisposeNotifierMixin {
);
if (index == -1) {
profilesTemp.add(updateProfile);
} else if (force == true) {
} else {
profilesTemp[index] = updateProfile;
}
state = profilesTemp;
+1 -1
View File
@@ -83,7 +83,7 @@ final themeSettingProvider =
);
typedef _$ThemeSetting = AutoDisposeNotifier<ThemeProps>;
String _$profilesHash() => r'c416fda0f8deded24a715a8234efa0bcd0445449';
String _$profilesHash() => r'a6514c89064e4f42fc31fe7d525088fd26c51016';
/// See also [Profiles].
@ProviderFor(Profiles)
+17 -1
View File
@@ -78,7 +78,7 @@ final coreStateProvider = AutoDisposeProvider<CoreState>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef CoreStateRef = AutoDisposeProviderRef<CoreState>;
String _$clashConfigStateHash() => r'848f6b2f734d99fb11ec05f73d614be415e9658f';
String _$clashConfigStateHash() => r'fbbcd7221b0b9b18db523e59c9021e8e56e119ca';
/// See also [clashConfigState].
@ProviderFor(clashConfigState)
@@ -1765,6 +1765,22 @@ class _GetProfileOverrideDataProviderElement
String get profileId => (origin as GetProfileOverrideDataProvider).profileId;
}
String _$layoutChangeHash() => r'f25182e1dfaf3c70000404d7635bb1e1db09efbb';
/// See also [layoutChange].
@ProviderFor(layoutChange)
final layoutChangeProvider = AutoDisposeProvider<VM2?>.internal(
layoutChange,
name: r'layoutChangeProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$layoutChangeHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef LayoutChangeRef = AutoDisposeProviderRef<VM2?>;
String _$genColorSchemeHash() => r'b18f15c938a8132ee4ed02cdfc02f3b9f01724e2';
/// See also [genColorScheme].
+16 -3
View File
@@ -75,13 +75,15 @@ CoreState coreState(Ref ref) {
ClashConfigState clashConfigState(Ref ref) {
final clashConfig = ref.watch(patchClashConfigProvider);
final overrideDns = ref.watch(overrideDnsProvider);
final overrideData = ref.watch(currentProfileProvider.select(
(state) => state?.overrideData,
));
final overrideData =
ref.watch(currentProfileProvider.select((state) => state?.overrideData));
final routeMode =
ref.watch(networkSettingProvider.select((state) => state.routeMode));
return ClashConfigState(
overrideDns: overrideDns,
clashConfig: clashConfig,
overrideData: overrideData ?? OverrideData(),
routeMode: routeMode,
);
}
@@ -508,6 +510,17 @@ OverrideData? getProfileOverrideData(Ref ref, String profileId) {
);
}
@riverpod
VM2? layoutChange(Ref ref) {
final viewWidth = ref.watch(viewWidthProvider);
final textScale =
ref.watch(themeSettingProvider.select((state) => state.textScale));
return VM2(
a: viewWidth,
b: textScale,
);
}
@riverpod
ColorScheme genColorScheme(
Ref ref,
+11 -8
View File
@@ -1,5 +1,4 @@
import 'dart:async';
import 'package:animations/animations.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/clash/clash.dart';
@@ -22,14 +21,15 @@ typedef UpdateTasks = List<FutureOr Function()>;
class GlobalState {
static GlobalState? _instance;
Map<Key, double> cacheScrollPosition = {};
Map<Key, FixedMap<String, double>> cacheHeightMap = {};
Map<CacheTag, double> cacheScrollPosition = {};
Map<CacheTag, FixedMap<String, double>> cacheHeightMap = {};
bool isService = false;
Timer? timer;
Timer? groupsUpdateTimer;
late Config config;
late AppState appState;
bool isPre = true;
String? coreSHA256;
late PackageInfo packageInfo;
Function? updateCurrentDelayDebounce;
late Measure measure;
@@ -55,8 +55,8 @@ class GlobalState {
appState = AppState(
version: version,
viewSize: Size.zero,
requests: FixedList(1000),
logs: FixedList(1000),
requests: FixedList(maxLength),
logs: FixedList(maxLength),
traffics: FixedList(30),
totalTraffic: Traffic(),
);
@@ -258,14 +258,17 @@ class GlobalState {
getUpdateConfigParams([bool? isPatch]) {
final currentProfile = config.currentProfile;
final clashConfig = config.patchClashConfig;
final routeAddress =
config.networkProps.routeMode == RouteMode.bypassPrivate
? defaultBypassPrivateRouteAddress
: clashConfig.tun.routeAddress;
return UpdateConfigParams(
profileId: config.currentProfileId ?? "",
config: clashConfig.copyWith(
globalUa: ua,
tun: clashConfig.tun.copyWith(
routeAddress: config.networkProps.routeMode == RouteMode.bypassPrivate
? defaultBypassPrivateRouteAddress
: clashConfig.tun.routeAddress,
autoRoute: routeAddress.isEmpty ? true : false,
routeAddress: routeAddress,
),
rule: currentProfile?.overrideData.runningRule ?? [],
),
+1
View File
@@ -257,6 +257,7 @@ class ListItem<T> extends StatelessWidget {
leading: leading ?? this.leading,
horizontalTitleGap: horizontalTitleGap,
title: title,
minVerticalPadding: 12,
subtitle: subtitle,
titleAlignment: tileTitleAlignment,
onTap: onTap,
+2 -2
View File
@@ -16,7 +16,7 @@ class TextScaleNotification extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer(
builder: (_, ref, __) {
builder: (_, ref, child) {
ref.listen(
themeSettingProvider.select((state) => state.textScale),
(prev, next) {
@@ -25,7 +25,7 @@ class TextScaleNotification extends StatelessWidget {
}
},
);
return child;
return child!;
},
child: child,
);
+25 -44
View File
@@ -1,6 +1,6 @@
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/list.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
@@ -54,13 +54,13 @@ class ScrollToEndBox<T> extends StatefulWidget {
final ScrollController controller;
final List<T> dataSource;
final Widget child;
final Key cacheKey;
final CacheTag tag;
const ScrollToEndBox({
super.key,
required this.child,
required this.controller,
required this.cacheKey,
required this.tag,
required this.dataSource,
});
@@ -73,8 +73,7 @@ class _ScrollToEndBoxState<T> extends State<ScrollToEndBox<T>> {
_handleTryToEnd() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final double offset =
globalState.cacheScrollPosition[widget.cacheKey] ?? -1;
final double offset = globalState.cacheScrollPosition[widget.tag] ?? -1;
if (offset < 0) {
widget.controller.animateTo(
duration: kThemeAnimationDuration,
@@ -85,12 +84,6 @@ class _ScrollToEndBoxState<T> extends State<ScrollToEndBox<T>> {
});
}
@override
void initState() {
super.initState();
globalState.cacheScrollPosition[widget.cacheKey] = -1;
}
@override
void didUpdateWidget(ScrollToEndBox<T> oldWidget) {
super.didUpdateWidget(oldWidget);
@@ -103,11 +96,10 @@ class _ScrollToEndBoxState<T> extends State<ScrollToEndBox<T>> {
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (details) {
double offset =
globalState.cacheScrollPosition[widget.tag] =
details.metrics.pixels == details.metrics.maxScrollExtent
? -1
: details.metrics.pixels;
globalState.cacheScrollPosition[widget.cacheKey] = offset;
return false;
},
child: widget.child,
@@ -124,6 +116,7 @@ class CacheItemExtentListView extends StatefulWidget {
final bool shrinkWrap;
final bool reverse;
final ScrollController controller;
final CacheTag tag;
const CacheItemExtentListView({
super.key,
@@ -135,6 +128,7 @@ class CacheItemExtentListView extends StatefulWidget {
required this.keyBuilder,
required this.itemCount,
required this.itemExtentBuilder,
required this.tag,
});
@override
@@ -143,21 +137,19 @@ class CacheItemExtentListView extends StatefulWidget {
}
class CacheItemExtentListViewState extends State<CacheItemExtentListView> {
late final FixedMap<String, double> _cacheHeightMap;
@override
void initState() {
super.initState();
_cacheHeightMap = FixedMap(widget.itemCount);
_updateCacheHeightMap();
}
clearCache() {
_cacheHeightMap.clear();
_updateCacheHeightMap() {
globalState.cacheHeightMap[widget.tag]?.updateMaxLength(widget.itemCount);
globalState.cacheHeightMap[widget.tag] ??= FixedMap(widget.itemCount);
}
@override
Widget build(BuildContext context) {
_cacheHeightMap.updateMaxSize(widget.itemCount);
return ListView.builder(
itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount,
@@ -166,20 +158,14 @@ class CacheItemExtentListViewState extends State<CacheItemExtentListView> {
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));
_updateCacheHeightMap();
return globalState.cacheHeightMap[widget.tag]?.updateCacheValue(
widget.keyBuilder(index),
() => widget.itemExtentBuilder(index),
);
},
);
}
@override
void dispose() {
_cacheHeightMap.clear();
super.dispose();
}
}
class CacheItemExtentSliverReorderableList extends StatefulWidget {
@@ -189,6 +175,7 @@ class CacheItemExtentSliverReorderableList extends StatefulWidget {
final double Function(int index) itemExtentBuilder;
final ReorderCallback onReorder;
final ReorderItemProxyDecorator? proxyDecorator;
final CacheTag tag;
const CacheItemExtentSliverReorderableList({
super.key,
@@ -198,6 +185,7 @@ class CacheItemExtentSliverReorderableList extends StatefulWidget {
required this.itemExtentBuilder,
required this.onReorder,
this.proxyDecorator,
required this.tag,
});
@override
@@ -207,30 +195,24 @@ class CacheItemExtentSliverReorderableList extends StatefulWidget {
class CacheItemExtentSliverReorderableListState
extends State<CacheItemExtentSliverReorderableList> {
late final FixedMap<String, double> _cacheHeightMap;
@override
void initState() {
super.initState();
_cacheHeightMap = FixedMap(widget.itemCount);
}
clearCache() {
_cacheHeightMap.clear();
globalState.cacheHeightMap[widget.tag]?.updateMaxLength(widget.itemCount);
globalState.cacheHeightMap[widget.tag] ??= FixedMap(widget.itemCount);
}
@override
Widget build(BuildContext context) {
_cacheHeightMap.updateMaxSize(widget.itemCount);
globalState.cacheHeightMap[widget.tag]?.updateMaxLength(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));
return globalState.cacheHeightMap[widget.tag]?.updateCacheValue(
widget.keyBuilder(index),
() => widget.itemExtentBuilder(index),
);
},
onReorder: widget.onReorder,
proxyDecorator: widget.proxyDecorator,
@@ -239,7 +221,6 @@ class CacheItemExtentSliverReorderableListState
@override
void dispose() {
_cacheHeightMap.clear();
super.dispose();
}
}
@@ -10,7 +10,6 @@ keywords:
generic_name: FlClash
categories:
- Network
+3
View File
@@ -10,6 +10,9 @@ installed_size: 6604
essential: false
icon: ./assets/images/icon.png
dependencies:
- libayatana-appindicator3-dev
- libkeybinder-3.0-dev
keywords:
- FlClash
+1 -1
View File
@@ -279,7 +279,7 @@ packages:
source: hosted
version: "0.3.4+2"
crypto:
dependency: transitive
dependency: "direct dev"
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
+2 -1
View File
@@ -1,7 +1,7 @@
name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none'
version: 0.8.83+202504252
version: 0.8.83+202504301
environment:
sdk: '>=3.1.0 <4.0.0'
@@ -66,6 +66,7 @@ dev_dependencies:
riverpod_generator: ^2.6.3
custom_lint: ^0.7.0
riverpod_lint: ^2.6.3
crypto: ^3.0.3
flutter:
uses-material-design: true
+13 -1
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@@ -284,6 +284,7 @@ dependencies = [
"anyhow",
"once_cell",
"serde",
"sha2",
"tokio",
"warp",
"windows-service",
@@ -822,6 +823,17 @@ dependencies = [
"digest",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
+2 -1
View File
@@ -14,10 +14,11 @@ anyhow = "1.0.93"
warp = "0.3.7"
serde = { version = "1.0.215", features = ["derive"] }
once_cell = "1.20.2"
sha2 = "0.10.8"
[profile.release]
panic = "abort"
codegen-units = 1
lto = true
opt-level = "s"
opt-level = "s"
+4
View File
@@ -0,0 +1,4 @@
fn main() {
let version = std::env::var("TOKEN").unwrap_or_default();
println!("cargo:rustc-env=TOKEN={}", version);
}
+40 -26
View File
@@ -1,11 +1,13 @@
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::VecDeque;
use std::{io, thread};
use std::io::BufRead;
use std::fs::File;
use std::io::{BufRead, Error, Read};
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::{io, thread};
use warp::{Filter, Reply};
use serde::{Deserialize, Serialize};
use once_cell::sync::Lazy;
const LISTEN_PORT: u16 = 47890;
@@ -15,10 +17,31 @@ pub struct StartParams {
pub arg: String,
}
static LOGS: Lazy<Arc<Mutex<VecDeque<String>>>> = Lazy::new(|| Arc::new(Mutex::new(VecDeque::with_capacity(100))));
static PROCESS: Lazy<Arc<Mutex<Option<std::process::Child>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
fn sha256_file(path: &str) -> Result<String, Error> {
let mut file = File::open(path)?;
let mut hasher = Sha256::new();
let mut buffer = [0; 4096];
loop {
let bytes_read = file.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
Ok(format!("{:x}", hasher.finalize()))
}
static LOGS: Lazy<Arc<Mutex<VecDeque<String>>>> =
Lazy::new(|| Arc::new(Mutex::new(VecDeque::with_capacity(100))));
static PROCESS: Lazy<Arc<Mutex<Option<std::process::Child>>>> =
Lazy::new(|| Arc::new(Mutex::new(None)));
fn start(start_params: StartParams) -> impl Reply {
if sha256_file(start_params.path.as_str()).unwrap_or("".to_string()) != env!("TOKEN") {
return "Only FlClashCore is allowed to run.".to_string();
}
stop();
let mut process = PROCESS.lock().unwrap();
match Command::new(&start_params.path)
@@ -73,38 +96,29 @@ fn log_message(message: String) {
fn get_logs() -> impl Reply {
let log_buffer = LOGS.lock().unwrap();
let value = log_buffer.iter().cloned().collect::<Vec<String>>().join("\n");
let value = log_buffer
.iter()
.cloned()
.collect::<Vec<String>>()
.join("\n");
warp::reply::with_header(value, "Content-Type", "text/plain")
}
pub async fn run_service() -> anyhow::Result<()> {
let api_ping = warp::get()
.and(warp::path("ping"))
.map(|| "2024125");
let api_ping = warp::get().and(warp::path("ping")).map(|| env!("TOKEN"));
let api_start = warp::post()
.and(warp::path("start"))
.and(warp::body::json())
.map(|start_params: StartParams| {
start(start_params)
});
.map(|start_params: StartParams| start(start_params));
let api_stop = warp::post()
.and(warp::path("stop"))
.map(|| stop());
let api_stop = warp::post().and(warp::path("stop")).map(|| stop());
let api_logs = warp::get()
.and(warp::path("logs"))
.map(|| get_logs());
let api_logs = warp::get().and(warp::path("logs")).map(|| get_logs());
warp::serve(
api_ping
.or(api_start)
.or(api_stop)
.or(api_logs)
)
warp::serve(api_ping.or(api_start).or(api_stop).or(api_logs))
.run(([127, 0, 0, 1], LISTEN_PORT))
.await;
Ok(())
}
}
+41 -21
View File
@@ -5,6 +5,7 @@ import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:path/path.dart';
import 'package:crypto/crypto.dart';
enum Target {
windows,
@@ -195,7 +196,16 @@ class Build {
if (exitCode != 0 && name != null) throw "$name error";
}
static buildCore({
static Future<String> calcSha256(String filePath) async {
final file = File(filePath);
if (!await file.exists()) {
throw "File not exists";
}
final stream = file.openRead();
return sha256.convert(await stream.reduce((a, b) => a + b)).toString();
}
static Future<List<String>> buildCore({
required Mode mode,
required Target target,
Arch? arch,
@@ -209,6 +219,8 @@ class Build {
},
).toList();
final List<String> corePaths = [];
for (final item in items) {
final outFileDir = join(
outDir,
@@ -228,6 +240,7 @@ class Build {
outFileDir,
fileName,
);
corePaths.add(outPath);
final Map<String, String> env = {};
env["GOOS"] = item.target.os;
@@ -258,9 +271,11 @@ class Build {
workingDirectory: _coreDir,
);
}
return corePaths;
}
static buildHelper(Target target) async {
static buildHelper(Target target, String token) async {
await exec(
[
"cargo",
@@ -269,6 +284,9 @@ class Build {
"--features",
"windows-service",
],
environment: {
"TOKEN": token,
},
name: "build helper",
workingDirectory: _servicesDir,
);
@@ -278,13 +296,15 @@ class Build {
"release",
"helper${target.executableExtensionName}",
);
final targetPath = join(outDir, target.name,
"FlClashHelperService${target.executableExtensionName}");
final targetPath = join(
outDir,
target.name,
"FlClashHelperService${target.executableExtensionName}",
);
await File(outPath).copy(targetPath);
}
static List<String> getExecutable(String command) {
print(command);
return command.split(" ");
}
@@ -402,7 +422,8 @@ class BuildCommand extends Command {
await Build.exec(
Build.getExecutable("sudo apt install -y libfuse2"),
);
final downloadName = arch == Arch.amd64 ? "x86_64" : "aarch_64";
final downloadName = arch == Arch.amd64 ? "x86_64" : "aarch64";
await Build.exec(
Build.getExecutable(
"wget -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$downloadName.AppImage",
@@ -413,12 +434,12 @@ class BuildCommand extends Command {
"chmod +x appimagetool",
),
);
await Build.exec(
Build.getExecutable(
"sudo mv appimagetool /usr/local/bin/",
),
);
}
await Build.exec(
Build.getExecutable(
"sudo mv appimagetool /usr/local/bin/",
),
);
}
_getMacosDependencies() async {
@@ -466,26 +487,27 @@ class BuildCommand extends Command {
throw "Invalid arch parameter";
}
await Build.buildCore(
final corePaths = await Build.buildCore(
target: target,
arch: arch,
mode: mode,
);
if (target == Target.windows) {
await Build.buildHelper(target);
}
if (out != "app") {
return;
}
switch (target) {
case Target.windows:
final token = target != Target.android
? await Build.calcSha256(corePaths.first)
: null;
Build.buildHelper(target, token!);
_buildDistributor(
target: target,
targets: "exe,zip",
args: " --description $archName",
args:
" --description $archName --build-dart-define=CORE_SHA256=$token",
env: env,
);
return;
@@ -496,10 +518,8 @@ class BuildCommand extends Command {
};
final targets = [
"deb",
if (arch == Arch.amd64) ...[
"appimage",
"rpm",
],
if (arch == Arch.amd64) "appimage",
if (arch == Arch.amd64) "rpm",
].join(",");
final defaultTarget = targetMap[arch];
await _getLinuxDependencies(arch!);