Compare commits
2 Commits
v0.8.83-pr
...
v0.8.83-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
094857e0d6 | ||
|
|
0a98515457 |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -396,5 +396,10 @@
|
||||
"textScale": "テキストスケーリング",
|
||||
"internet": "インターネット",
|
||||
"systemApp": "システムアプリ",
|
||||
"noNetworkApp": "ネットワークなしアプリ"
|
||||
"noNetworkApp": "ネットワークなしアプリ",
|
||||
"contactMe": "連絡する",
|
||||
"recoveryStrategy": "リカバリー戦略",
|
||||
"recoveryStrategy_override": "オーバーライド",
|
||||
"recoveryStrategy_compatible": "互換性",
|
||||
"logsTest": "ログテスト"
|
||||
}
|
||||
@@ -396,5 +396,10 @@
|
||||
"textScale": "Масштабирование текста",
|
||||
"internet": "Интернет",
|
||||
"systemApp": "Системное приложение",
|
||||
"noNetworkApp": "Приложение без сети"
|
||||
"noNetworkApp": "Приложение без сети",
|
||||
"contactMe": "Свяжитесь со мной",
|
||||
"recoveryStrategy": "Стратегия восстановления",
|
||||
"recoveryStrategy_override": "Переопределение",
|
||||
"recoveryStrategy_compatible": "Совместимый",
|
||||
"logsTest": "Тест журналов"
|
||||
}
|
||||
@@ -396,5 +396,10 @@
|
||||
"textScale": "文本缩放",
|
||||
"internet": "互联网",
|
||||
"systemApp": "系统应用",
|
||||
"noNetworkApp": "无网络应用"
|
||||
"noNetworkApp": "无网络应用",
|
||||
"contactMe": "联系我",
|
||||
"recoveryStrategy": "恢复策略",
|
||||
"recoveryStrategy_override": "覆盖",
|
||||
"recoveryStrategy_compatible": "兼容",
|
||||
"logsTest": "日志测试"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -245,7 +245,6 @@ abstract class ClashHandlerInterface with ClashInterface {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<bool> crash() {
|
||||
return invoke<bool>(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -43,6 +43,18 @@ class Measure {
|
||||
);
|
||||
}
|
||||
|
||||
double get bodyLargeHeight {
|
||||
return _measureMap.getCacheValue(
|
||||
"bodyLargeHeight",
|
||||
computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.bodyLarge,
|
||||
),
|
||||
).height,
|
||||
);
|
||||
}
|
||||
|
||||
double get bodySmallHeight {
|
||||
return _measureMap.getCacheValue(
|
||||
"bodySmallHeight",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
@@ -396,9 +394,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 +485,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 +938,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) {
|
||||
|
||||
@@ -293,6 +293,7 @@ enum WindowsHelperServiceStatus {
|
||||
|
||||
enum DebounceTag {
|
||||
updateClashConfig,
|
||||
updateStatus,
|
||||
updateGroups,
|
||||
addCheckIpNum,
|
||||
applyProfile,
|
||||
@@ -470,3 +471,9 @@ enum RuleTarget {
|
||||
DIRECT,
|
||||
REJECT,
|
||||
}
|
||||
|
||||
|
||||
enum RecoveryStrategy {
|
||||
compatible,
|
||||
override,
|
||||
}
|
||||
|
||||
@@ -47,6 +47,14 @@ class AboutFragment extends StatelessWidget {
|
||||
_checkUpdate(context);
|
||||
},
|
||||
),
|
||||
ListItem(
|
||||
title: Text(appLocalizations.contactMe),
|
||||
onTap: () {
|
||||
globalState.showMessage(
|
||||
message: TextSpan(text: "chen08209@gmail.com"),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListItem(
|
||||
title: const Text("Telegram"),
|
||||
onTap: () {
|
||||
|
||||
@@ -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}"),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -15,7 +16,6 @@ class DeveloperView extends ConsumerWidget {
|
||||
title: appLocalizations.options,
|
||||
items: [
|
||||
ListItem(
|
||||
leading: Icon(Icons.ac_unit),
|
||||
title: Text(appLocalizations.messageTest),
|
||||
onTap: () {
|
||||
context.showNotifier(
|
||||
@@ -24,14 +24,27 @@ class DeveloperView extends ConsumerWidget {
|
||||
},
|
||||
),
|
||||
ListItem(
|
||||
leading: Icon(Icons.heart_broken),
|
||||
title: Text(appLocalizations.logsTest),
|
||||
onTap: () {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
globalState.appController.addLog(
|
||||
Log.app(
|
||||
utils.generateRandomString(
|
||||
maxLength: 1000,
|
||||
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();
|
||||
|
||||
@@ -29,7 +29,9 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
super.initState();
|
||||
final preOffset = globalState.cacheScrollPosition[_cacheKey] ?? -1;
|
||||
_scrollController = ScrollController(
|
||||
initialScrollOffset: preOffset > 0 ? preOffset : double.maxFinite,
|
||||
initialScrollOffset: preOffset > 0
|
||||
? preOffset
|
||||
: double.maxFinite,
|
||||
);
|
||||
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
|
||||
logs: globalState.appState.logs.list,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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("正規表現"),
|
||||
|
||||
@@ -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(
|
||||
"Восстановление успешно",
|
||||
),
|
||||
|
||||
@@ -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("正则"),
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -27,6 +27,7 @@ Future<void> main() async {
|
||||
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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -147,6 +147,7 @@ class Tun with _$Tun {
|
||||
const factory Tun({
|
||||
@Default(false) bool enable,
|
||||
@Default(appName) String device,
|
||||
@JsonKey(name: "auto-route") @Default(false) bool autoRoute,
|
||||
@Default(TunStack.gvisor) TunStack stack,
|
||||
@JsonKey(name: "dns-hijack") @Default(["any:53"]) List<String> dnsHijack,
|
||||
@JsonKey(name: "route-address") @Default([]) List<String> routeAddress,
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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,6 +809,7 @@ class _$TunImpl implements _Tun {
|
||||
const _$TunImpl(
|
||||
{this.enable = false,
|
||||
this.device = appName,
|
||||
@JsonKey(name: "auto-route") this.autoRoute = false,
|
||||
this.stack = TunStack.gvisor,
|
||||
@JsonKey(name: "dns-hijack")
|
||||
final List<String> dnsHijack = const ["any:53"],
|
||||
@@ -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")
|
||||
|
||||
@@ -66,6 +66,7 @@ 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,
|
||||
dnsHijack: (json['dns-hijack'] as List<dynamic>?)
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -228,6 +228,7 @@ class ClashConfigState with _$ClashConfigState {
|
||||
required bool overrideDns,
|
||||
required ClashConfig clashConfig,
|
||||
required OverrideData overrideData,
|
||||
required RouteMode routeMode,
|
||||
}) = _ClashConfigState;
|
||||
}
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -83,7 +83,7 @@ final themeSettingProvider =
|
||||
);
|
||||
|
||||
typedef _$ThemeSetting = AutoDisposeNotifier<ThemeProps>;
|
||||
String _$profilesHash() => r'c416fda0f8deded24a715a8234efa0bcd0445449';
|
||||
String _$profilesHash() => r'a6514c89064e4f42fc31fe7d525088fd26c51016';
|
||||
|
||||
/// See also [Profiles].
|
||||
@ProviderFor(Profiles)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ class GlobalState {
|
||||
late Config config;
|
||||
late AppState appState;
|
||||
bool isPre = true;
|
||||
String? coreSHA256;
|
||||
late PackageInfo packageInfo;
|
||||
Function? updateCurrentDelayDebounce;
|
||||
late Measure measure;
|
||||
@@ -55,8 +56,8 @@ class GlobalState {
|
||||
appState = AppState(
|
||||
version: version,
|
||||
viewSize: Size.zero,
|
||||
requests: FixedList(1000),
|
||||
logs: FixedList(1000),
|
||||
requests: FixedList(500),
|
||||
logs: FixedList(500),
|
||||
traffics: FixedList(30),
|
||||
totalTraffic: Traffic(),
|
||||
);
|
||||
@@ -258,14 +259,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 ?? [],
|
||||
),
|
||||
|
||||
@@ -257,6 +257,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
leading: leading ?? this.leading,
|
||||
horizontalTitleGap: horizontalTitleGap,
|
||||
title: title,
|
||||
minTileHeight: 52,
|
||||
subtitle: subtitle,
|
||||
titleAlignment: tileTitleAlignment,
|
||||
onTap: onTap,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/common/list.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -10,7 +10,6 @@ keywords:
|
||||
|
||||
generic_name: FlClash
|
||||
|
||||
|
||||
categories:
|
||||
- Network
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ installed_size: 6604
|
||||
essential: false
|
||||
icon: ./assets/images/icon.png
|
||||
|
||||
dependencies:
|
||||
- libayatana-appindicator3-dev
|
||||
- libkeybinder-3.0-dev
|
||||
|
||||
keywords:
|
||||
- FlClash
|
||||
|
||||
Submodule plugins/flutter_distributor updated: c5c06ee67d...9daab581b0
@@ -279,7 +279,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.3.4+2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
|
||||
@@ -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+202504281
|
||||
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
|
||||
|
||||
14
services/helper/Cargo.lock
generated
14
services/helper/Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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
services/helper/build.rs
Normal file
4
services/helper/build.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
fn main() {
|
||||
let version = std::env::var("TOKEN").unwrap_or_default();
|
||||
println!("cargo:rustc-env=TOKEN={}", version);
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
62
setup.dart
62
setup.dart
@@ -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!);
|
||||
|
||||
Reference in New Issue
Block a user