diff --git a/.github/release_template.md b/.github/release_template.md
index 19aaa09..d130084 100644
--- a/.github/release_template.md
+++ b/.github/release_template.md
@@ -31,9 +31,10 @@
- | macOS (v10.15+) |
+ macOS |
- 
+ 
+ 
|
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index dcd1d42..ed2c399 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -84,6 +84,70 @@ jobs:
path: ./dist
overwrite: true
+ changelog:
+ runs-on: ubuntu-latest
+ needs: [ build ]
+ steps:
+ - name: Checkout
+ if: ${{ !contains(github.ref, '+') }}
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ ref: refs/heads/main
+
+ - name: Generate
+ if: ${{ !contains(github.ref, '+') }}
+ run: |
+ tags=($(git tag --merged $(git rev-parse HEAD) --sort=-creatordate))
+ preTag=$(grep -oP '^## \K.*' CHANGELOG.md | head -n 1)
+ currentTag=""
+ for ((i = 0; i <= ${#tags[@]}; i++)); do
+ if (( i < ${#tags[@]} )); then
+ tag=${tags[$i]}
+ else
+ tag=""
+ fi
+ if [ -n "$currentTag" ]; then
+ if [ "$(echo -e "$currentTag\n$preTag" | sort -V | head -n 1)" == "$currentTag" ]; then
+ break
+ fi
+ fi
+ if [ -n "$currentTag" ]; then
+ echo "## $currentTag" >> NEW_CHANGELOG.md
+ echo "" >> NEW_CHANGELOG.md
+ if [ -n "$tag" ]; then
+ git log --pretty=format:"%B" "$tag..$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> NEW_CHANGELOG.md
+ else
+ git log --pretty=format:"%B" "$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> NEW_CHANGELOG.md
+ fi
+ echo "" >> NEW_CHANGELOG.md
+ fi
+ currentTag=$tag
+ done
+ cat CHANGELOG.md >> NEW_CHANGELOG.md
+ cat NEW_CHANGELOG.md > CHANGELOG.md
+
+ - name: Commit
+ if: ${{ !contains(github.ref, '+') }}
+ run: |
+ git add CHANGELOG.md
+ if ! git diff --cached --quiet; then
+ echo "Commit pushing"
+ git config --local user.email "chen08209@gmail.com"
+ git config --local user.name "chen08209"
+ git commit -m "Update changelog"
+ git push
+ if [ $? -eq 0 ]; then
+ echo "Push succeeded"
+ else
+ echo "Push failed"
+ exit 1
+ fi
+ fi
+
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
upload:
permissions: write-all
needs: [ build ]
@@ -177,4 +241,5 @@ jobs:
user-email: 'github-actions[bot]@users.noreply.github.com'
target-branch: action-pr
commit-message: Update from ${{ github.ref_name }}
- target-directory: /tmp/
\ No newline at end of file
+ target-directory: /tmp/
+
diff --git a/.github/workflows/change.yaml b/.github/workflows/change.yaml
deleted file mode 100644
index 0fe2fd7..0000000
--- a/.github/workflows/change.yaml
+++ /dev/null
@@ -1,66 +0,0 @@
-name: changelog
-
-on:
- push:
- tags:
- - 'v*'
-
-jobs:
- changelog:
- if: ${{ !contains(github.ref, '+') }}
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Generate
- run: |
- tags=($(git tag --merged $(git rev-parse HEAD) --sort=-creatordate))
- preTag=$(grep -oP '^## \K.*' CHANGELOG.md | head -n 1)
- currentTag=""
- for ((i = 0; i <= ${#tags[@]}; i++)); do
- if (( i < ${#tags[@]} )); then
- tag=${tags[$i]}
- else
- tag=""
- fi
- if [ -n "$currentTag" ]; then
- if [ "$(echo -e "$currentTag\n$preTag" | sort -V | head -n 1)" == "$currentTag" ]; then
- break
- fi
- fi
- if [ -n "$currentTag" ]; then
- echo "## $currentTag" >> NEW_CHANGELOG.md
- echo "" >> NEW_CHANGELOG.md
- if [ -n "$tag" ]; then
- git log --pretty=format:"%B" "$tag..$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> NEW_CHANGELOG.md
- else
- git log --pretty=format:"%B" "$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> NEW_CHANGELOG.md
- fi
- echo "" >> NEW_CHANGELOG.md
- fi
- currentTag=$tag
- done
- cat CHANGELOG.md >> NEW_CHANGELOG.md
- cat NEW_CHANGELOG.md > CHANGELOG.md
-
- - name: Commit
- run: |
- git add CHANGELOG.md
- if ! git diff --cached --quiet; then
- echo "Commit pushing"
- git config --local user.email "chen08209@gmail.com"
- git config --local user.name "chen08209"
- git commit -m "Update changelog"
- git push
- if [ $? -eq 0 ]; then
- echo "Push succeeded"
- else
- echo "Push failed"
- exit 1
- fi
- fi
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd5e0c4..d67e2be 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,37 @@
+## v0.8.69
+
+- Remake desktop
+
+- Optimize change proxy
+
+- Optimize network check
+
+- Fix fallback issues
+
+- Optimize lots of details
+
+- Update change.yaml
+
+- Fix android tile issues
+
+- Fix windows tray issues
+
+- Support setting bypassDomain
+
+- Update flutter version
+
+- Fix android service issues
+
+- Fix macos dock exit button issues
+
+- Add route address setting
+
+- Optimize provider view
+
+- Update changelog
+
+- Update CHANGELOG.md
+
## v0.8.67
- Add android shortcuts
diff --git a/README.md b/README.md
index 14ef7cc..b45f03c 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,29 @@ on Mobile:
✨ Support subscription link, Dark mode
+## Use
+
+### Linux
+
+⚠️ 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
+ ```
+
+### Android
+
+Support the following actions
+
+ ```bash
+ com.follow.clash.action.START
+
+ com.follow.clash.action.STOP
+
+ com.follow.clash.action.CHANGE
+ ```
+
## Download
@@ -70,7 +93,7 @@ on Mobile:
3. Run build script
```bash
- dart .\setup.dart
+ dart .\setup.dart windows --arch
```
- linux
@@ -80,7 +103,7 @@ on Mobile:
2. Run build script
```bash
- dart .\setup.dart
+ dart .\setup.dart linux --arch
```
- macOS
@@ -90,7 +113,7 @@ on Mobile:
2. Run build script
```bash
- dart .\setup.dart
+ dart .\setup.dart macos --arch
```
## Star
diff --git a/README_zh_CN.md b/README_zh_CN.md
index 378da62..59ccb34 100644
--- a/README_zh_CN.md
+++ b/README_zh_CN.md
@@ -10,7 +10,6 @@
[](https://t.me/FlClash)
-
基于ClashMeta的多平台代理客户端,简单易用,开源无广告。
on Desktop:
@@ -35,6 +34,29 @@ on Mobile:
✨ 支持一键导入订阅, 深色模式
+## Use
+
+### Linux
+
+⚠️ 使用前请确保安装以下依赖
+
+ ```bash
+ sudo apt-get install appindicator3-0.1 libappindicator3-dev
+ sudo apt-get install keybinder-3.0
+ ```
+
+### Android
+
+支持下列操作
+
+ ```bash
+ com.follow.clash.action.START
+
+ com.follow.clash.action.STOP
+
+ com.follow.clash.action.CHANGE
+ ```
+
## Download
@@ -71,7 +93,7 @@ on Mobile:
3. 运行构建脚本
```bash
- dart .\setup.dart
+ dart .\setup.dart windows --arch
```
- linux
@@ -81,7 +103,7 @@ on Mobile:
2. 运行构建脚本
```bash
- dart .\setup.dart
+ dart .\setup.dart linux --arch
```
- macOS
@@ -91,7 +113,7 @@ on Mobile:
2. 运行构建脚本
```bash
- dart .\setup.dart
+ dart .\setup.dart macos --arch
```
## Star History
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 909c0d8..5ad72c8 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -72,6 +72,10 @@
android:name=".TempActivity"
android:exported="true"
android:theme="@style/TransparentTheme">
+
+
+
+
@@ -88,7 +92,8 @@
android:foregroundServiceType="specialUse"
android:icon="@drawable/ic_stat_name"
android:label="FlClash"
- android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
+ tools:targetApi="n">
diff --git a/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt b/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt
index 2dc28b6..e535b51 100644
--- a/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt
@@ -20,8 +20,6 @@ enum class RunState {
object GlobalState {
-
- private val lock = ReentrantLock()
val runLock = ReentrantLock()
val runState: MutableLiveData = MutableLiveData(RunState.STOP)
@@ -47,35 +45,46 @@ object GlobalState {
}
fun handleToggle(context: Context) {
+ val starting = handleStart(context)
+ if (!starting) {
+ handleStop()
+ }
+ }
+
+ fun handleStart(context: Context): Boolean {
if (runState.value == RunState.STOP) {
runState.value = RunState.PENDING
+ runLock.lock()
val tilePlugin = getCurrentTilePlugin()
if (tilePlugin != null) {
tilePlugin.handleStart()
} else {
initServiceEngine(context)
}
- } else {
- handleStop()
+ return true
}
+ return false
}
fun handleStop() {
if (runState.value == RunState.START) {
runState.value = RunState.PENDING
+ runLock.lock()
getCurrentTilePlugin()?.handleStop()
}
}
fun destroyServiceEngine() {
- serviceEngine?.destroy()
- serviceEngine = null
+ runLock.withLock {
+ serviceEngine?.destroy()
+ serviceEngine = null
+ }
}
fun initServiceEngine(context: Context) {
if (serviceEngine != null) return
- lock.withLock {
- destroyServiceEngine()
+ destroyServiceEngine()
+ runLock.withLock {
serviceEngine = FlutterEngine(context)
serviceEngine?.plugins?.add(VpnPlugin())
serviceEngine?.plugins?.add(AppPlugin())
diff --git a/android/app/src/main/kotlin/com/follow/clash/TempActivity.kt b/android/app/src/main/kotlin/com/follow/clash/TempActivity.kt
index 913e874..bbf7990 100644
--- a/android/app/src/main/kotlin/com/follow/clash/TempActivity.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/TempActivity.kt
@@ -8,6 +8,10 @@ class TempActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
when (intent.action) {
+ wrapAction("START") -> {
+ GlobalState.handleStart(applicationContext)
+ }
+
wrapAction("STOP") -> {
GlobalState.handleStop()
}
diff --git a/android/app/src/main/kotlin/com/follow/clash/extensions/Ext.kt b/android/app/src/main/kotlin/com/follow/clash/extensions/Ext.kt
index 0bbc4e8..a047e0f 100644
--- a/android/app/src/main/kotlin/com/follow/clash/extensions/Ext.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/extensions/Ext.kt
@@ -23,6 +23,7 @@ import java.io.ByteArrayOutputStream
import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
+import java.util.concurrent.locks.ReentrantLock
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@@ -179,4 +180,19 @@ suspend fun MethodChannel.awaitResult(
}
})
}
+}
+
+fun ReentrantLock.safeLock() {
+ if (this.isLocked) {
+ return
+ }
+ this.lock()
+}
+
+fun ReentrantLock.safeUnlock() {
+ if (!this.isLocked) {
+ return
+ }
+
+ this.unlock()
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt
index 0cc7831..82914e2 100644
--- a/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt
@@ -46,8 +46,6 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
private var activity: Activity? = null
- private var toast: Toast? = null
-
private lateinit var context: Context
private lateinit var channel: MethodChannel
diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt
index 4b44338..248e8f0 100644
--- a/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt
@@ -16,6 +16,8 @@ import com.follow.clash.GlobalState
import com.follow.clash.RunState
import com.follow.clash.extensions.getProtocol
import com.follow.clash.extensions.resolveDns
+import com.follow.clash.models.Process
+import com.follow.clash.models.VpnOptions
import com.follow.clash.services.FlClashService
import com.follow.clash.services.FlClashVpnService
import com.google.gson.Gson
@@ -28,8 +30,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.InetSocketAddress
import kotlin.concurrent.withLock
-import com.follow.clash.models.Process
-import com.follow.clash.models.VpnOptions
class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
@@ -111,11 +111,9 @@ class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
"resolverProcess" -> {
val data = call.argument("data")
- val process =
- if (data != null) Gson().fromJson(
- data,
- Process::class.java
- ) else null
+ val process = if (data != null) Gson().fromJson(
+ data, Process::class.java
+ ) else null
val metadata = process?.metadata
if (metadata == null) {
result.success(null)
@@ -173,9 +171,7 @@ class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
fun onUpdateNetwork() {
val dns = networks.flatMap { network ->
connectivity?.resolveDns(network) ?: emptyList()
- }
- .toSet()
- .joinToString(",")
+ }.toSet().joinToString(",")
scope.launch {
withContext(Dispatchers.Main) {
flutterMethodChannel.invokeMethod("dnsChanged", dns)
@@ -239,8 +235,7 @@ class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
GlobalState.runState.value = RunState.START
val fd = flClashService?.start(options)
flutterMethodChannel.invokeMethod(
- "started",
- fd
+ "started", fd
)
}
}
diff --git a/lib/common/other.dart b/lib/common/other.dart
index 8f62e99..91f7774 100644
--- a/lib/common/other.dart
+++ b/lib/common/other.dart
@@ -116,13 +116,11 @@ class Other {
return "assets/images/icon_white.png";
}
final suffix = Platform.isWindows ? "ico" : "png";
- if (Platform.isWindows) {
- return "assets/images/icon.$suffix";
- }
- return switch (brightness) {
- Brightness.dark => "assets/images/icon_white.$suffix",
- Brightness.light => "assets/images/icon_black.$suffix",
- };
+ return "assets/images/icon.$suffix";
+ // return switch (brightness) {
+ // Brightness.dark => "assets/images/icon_white.$suffix",
+ // Brightness.light => "assets/images/icon_black.$suffix",
+ // };
}
int compareVersions(String version1, String version2) {
diff --git a/lib/common/tray.dart b/lib/common/tray.dart
index dfa470a..8ae4e83 100644
--- a/lib/common/tray.dart
+++ b/lib/common/tray.dart
@@ -78,7 +78,7 @@ class Tray {
MenuItem.checkbox(
label: Intl.message(mode.name),
onClick: (_) {
- globalState.appController.clashConfig.mode = mode;
+ globalState.appController.changeMode(mode);
},
checked: mode == clashConfig.mode,
),
diff --git a/lib/common/window.dart b/lib/common/window.dart
index 4c222e6..e509de7 100755
--- a/lib/common/window.dart
+++ b/lib/common/window.dart
@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/config.dart';
import 'package:flutter/material.dart';
+import 'package:screen_retriever/screen_retriever.dart';
import 'package:window_manager/window_manager.dart';
class Window {
@@ -23,6 +24,35 @@ class Window {
);
if (!Platform.isMacOS || version > 10) {
await windowManager.setTitleBarStyle(TitleBarStyle.hidden);
+ final left = props.left ?? 0;
+ final top = props.top ?? 0;
+ final right = left + props.width;
+ final bottom = top + props.height;
+ if (left == 0 && top == 0) {
+ await windowManager.setAlignment(Alignment.center);
+ } else {
+ final displays = await screenRetriever.getAllDisplays();
+ final isPositionValid = displays.any(
+ (display) {
+ final displayBounds = Rect.fromLTWH(
+ display.visiblePosition!.dx,
+ display.visiblePosition!.dy,
+ display.size.width,
+ display.size.height,
+ );
+ return displayBounds.contains(Offset(left, top)) ||
+ displayBounds.contains(Offset(right, bottom));
+ },
+ );
+ if (isPositionValid) {
+ await windowManager.setPosition(
+ Offset(
+ left,
+ top,
+ ),
+ );
+ }
+ }
}
await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.setPreventClose(true);
diff --git a/lib/controller.dart b/lib/controller.dart
index 68083dc..96f5478 100644
--- a/lib/controller.dart
+++ b/lib/controller.dart
@@ -582,6 +582,14 @@ class AppController {
updateStatus(!appFlowingState.isStart);
}
+ changeMode(Mode mode) {
+ clashConfig.mode = mode;
+ if (mode == Mode.global) {
+ config.updateCurrentGroupName(GroupName.GLOBAL.name);
+ }
+ addCheckIpNumDebounce();
+ }
+
updateAutoLaunch() {
config.appSetting = config.appSetting.copyWith(
autoLaunch: !config.appSetting.autoLaunch,
diff --git a/lib/fragments/connections.dart b/lib/fragments/connections.dart
index 81c1b57..32ec5be 100644
--- a/lib/fragments/connections.dart
+++ b/lib/fragments/connections.dart
@@ -38,6 +38,9 @@ class _ConnectionsFragmentState extends State {
timer = Timer.periodic(
const Duration(seconds: 1),
(timer) async {
+ if (!context.mounted) {
+ return;
+ }
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: await clashCore.getConnections(),
);
diff --git a/lib/fragments/dashboard/outbound_mode.dart b/lib/fragments/dashboard/outbound_mode.dart
index e09a4af..acec28a 100644
--- a/lib/fragments/dashboard/outbound_mode.dart
+++ b/lib/fragments/dashboard/outbound_mode.dart
@@ -10,14 +10,6 @@ import 'package:provider/provider.dart';
class OutboundMode extends StatelessWidget {
const OutboundMode({super.key});
- _changeMode(BuildContext context, Mode? value) async {
- final appController = globalState.appController;
- final clashConfig = appController.clashConfig;
- if (value == null || clashConfig.mode == value) return;
- clashConfig.mode = value;
- appController.addCheckIpNumDebounce();
- }
-
@override
Widget build(BuildContext context) {
return Selector(
@@ -50,7 +42,10 @@ class OutboundMode extends StatelessWidget {
value: item,
groupValue: mode,
onChanged: (value) async {
- _changeMode(context, value);
+ if (value == null) {
+ return;
+ }
+ globalState.appController.changeMode(value);
},
),
title: Text(
diff --git a/lib/fragments/proxies/list.dart b/lib/fragments/proxies/list.dart
index d45120c..738d2ac 100644
--- a/lib/fragments/proxies/list.dart
+++ b/lib/fragments/proxies/list.dart
@@ -299,7 +299,7 @@ class _ProxiesListFragmentState extends State {
headerState.currentIndex > state.groupNames.length - 1
? 0
: headerState.currentIndex;
- if (index < 0) {
+ if (index < 0 || state.groupNames.isEmpty) {
return Container();
}
return Stack(
diff --git a/lib/fragments/proxies/tab.dart b/lib/fragments/proxies/tab.dart
index 09a091a..6ecc388 100644
--- a/lib/fragments/proxies/tab.dart
+++ b/lib/fragments/proxies/tab.dart
@@ -117,9 +117,11 @@ class ProxiesTabFragmentState extends State
}
final currentGroup = currentGroups[index ?? _tabController!.index];
currentProxies = currentGroup.all;
- appController.config.updateCurrentGroupName(
- currentGroup.name,
- );
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ appController.config.updateCurrentGroupName(
+ currentGroup.name,
+ );
+ });
}
_destroyTabController() {
@@ -129,6 +131,10 @@ class ProxiesTabFragmentState extends State
}
_updateTabController(int length, int index) {
+ if (length == 0) {
+ _destroyTabController();
+ return;
+ }
final realIndex = index == -1 ? 0 : index;
_tabController ??= TabController(
length: length,
@@ -162,6 +168,9 @@ class ProxiesTabFragmentState extends State
(item) => item == state.currentGroupName,
);
_updateTabController(state.groupNames.length, index);
+ if (state.groupNames.isEmpty) {
+ return Container();
+ }
final GroupNameKeyMap keyMap = {};
final children = state.groupNames.map((groupName) {
keyMap[groupName] = GlobalObjectKey(groupName);
@@ -281,12 +290,15 @@ class ProxyGroupViewState extends State {
if (_controller.position.maxScrollExtent == 0) {
return;
}
+ final sortedProxies = globalState.appController.getSortProxies(
+ currentProxies,
+ );
_controller.animateTo(
min(
16 +
getScrollToSelectedOffset(
groupName: groupName,
- proxies: currentProxies,
+ proxies: sortedProxies,
),
_controller.position.maxScrollExtent,
),
diff --git a/lib/manager/window_manager.dart b/lib/manager/window_manager.dart
index 487b931..8cacdea 100644
--- a/lib/manager/window_manager.dart
+++ b/lib/manager/window_manager.dart
@@ -67,6 +67,12 @@ class _WindowContainerState extends State
@override
Future onWindowMoved() async {
super.onWindowMoved();
+ final offset = await windowManager.getPosition();
+ final config = globalState.appController.config;
+ config.windowProps = config.windowProps.copyWith(
+ top: offset.dy,
+ left: offset.dx,
+ );
}
@override
diff --git a/lib/models/app.dart b/lib/models/app.dart
index ee418ec..87c8f58 100644
--- a/lib/models/app.dart
+++ b/lib/models/app.dart
@@ -212,9 +212,7 @@ class AppState with ChangeNotifier {
case Mode.direct:
return [];
case Mode.global:
- return groups
- .where((element) => element.name == GroupName.GLOBAL.name)
- .toList();
+ return groups.toList();
case Mode.rule:
return groups
.where((item) => item.hidden == false)
diff --git a/plugins/flutter_distributor b/plugins/flutter_distributor
index 98d508b..7e7bcad 160000
--- a/plugins/flutter_distributor
+++ b/plugins/flutter_distributor
@@ -1 +1 @@
-Subproject commit 98d508b0886957540d1f15b9630b90c72c721912
+Subproject commit 7e7bcadf2909bc8acf77a0ba5ae056bea9a60567
diff --git a/pubspec.lock b/pubspec.lock
index 8609f87..199f900 100755
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1002,7 +1002,7 @@ packages:
source: hosted
version: "0.28.0"
screen_retriever:
- dependency: transitive
+ dependency: "direct main"
description:
name: screen_retriever
sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c"
diff --git a/pubspec.yaml b/pubspec.yaml
index 1bfb5b0..20dee14 100755
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -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.69+202412061
+version: 0.8.70+202412091
environment:
sdk: '>=3.1.0 <4.0.0'
@@ -52,6 +52,7 @@ dependencies:
uni_platform: ^0.1.3
device_info_plus: ^10.1.2
connectivity_plus: ^6.1.0
+ screen_retriever: ^0.2.0
dev_dependencies:
flutter_test:
sdk: flutter
diff --git a/setup.dart b/setup.dart
index 27d128c..30951a0 100755
--- a/setup.dart
+++ b/setup.dart
@@ -21,6 +21,22 @@ extension TargetExt on Target {
return name;
}
+ bool get same {
+ if (this == Target.android) {
+ return true;
+ }
+ if (Platform.isWindows && this == Target.windows) {
+ return true;
+ }
+ if (Platform.isLinux && this == Target.linux) {
+ return true;
+ }
+ if (Platform.isMacOS && this == Target.macos) {
+ return true;
+ }
+ return false;
+ }
+
String get dynamicLibExtensionName {
final String extensionName;
switch (this) {
@@ -76,12 +92,27 @@ class Build {
static List get buildItems => [
BuildItem(
target: Target.macos,
+ arch: Arch.arm64,
),
BuildItem(
- target: Target.windows,
+ target: Target.macos,
+ arch: Arch.amd64,
),
BuildItem(
target: Target.linux,
+ arch: Arch.arm64,
+ ),
+ BuildItem(
+ target: Target.linux,
+ arch: Arch.amd64,
+ ),
+ BuildItem(
+ target: Target.windows,
+ arch: Arch.amd64,
+ ),
+ BuildItem(
+ target: Target.windows,
+ arch: Arch.arm64,
),
BuildItem(
target: Target.android,
@@ -200,11 +231,10 @@ class Build {
final Map env = {};
env["GOOS"] = item.target.os;
-
+ if (item.arch != null) {
+ env["GOARCH"] = item.arch!.name;
+ }
if (isLib) {
- if (item.arch != null) {
- env["GOARCH"] = item.arch!.name;
- }
env["CGO_ENABLED"] = "1";
env["CC"] = _getCc(item);
env["CFLAGS"] = "-O3 -Werror";
@@ -272,6 +302,11 @@ class Build {
Build.getExecutable("flutter clean"),
workingDirectory: distributorDir,
);
+ await exec(
+ name: "upgrade distributor",
+ Build.getExecutable("flutter pub upgrade"),
+ workingDirectory: distributorDir,
+ );
await exec(
name: "get distributor",
Build.getExecutable("dart pub global activate -s path $distributorDir"),
@@ -303,7 +338,7 @@ class BuildCommand extends Command {
BuildCommand({
required this.target,
}) {
- if (target == Target.android) {
+ if (target == Target.android || target == Target.linux) {
argParser.addOption(
"arch",
valueHelp: arches.map((e) => e.name).join(','),
@@ -315,11 +350,10 @@ class BuildCommand extends Command {
help: 'The $name build archName',
);
}
-
argParser.addOption(
"out",
valueHelp: [
- "app",
+ if (target.same) "app",
"core",
].join(','),
help: 'The $name build arch',
@@ -337,7 +371,7 @@ class BuildCommand extends Command {
.map((e) => e.arch!)
.toList();
- _getLinuxDependencies() async {
+ _getLinuxDependencies(Arch arch) async {
await Build.exec(
Build.getExecutable("sudo apt update -y"),
);
@@ -351,7 +385,7 @@ class BuildCommand extends Command {
Build.getExecutable("sudo apt install -y rpm patchelf"),
);
await Build.exec(
- Build.getExecutable("sudo apt-get install -y libkeybinder-3.0"),
+ Build.getExecutable("sudo apt-get install -y libkeybinder-3.0-dev"),
);
await Build.exec(
Build.getExecutable("sudo apt install -y locate"),
@@ -359,9 +393,10 @@ class BuildCommand extends Command {
await Build.exec(
Build.getExecutable("sudo apt install -y libfuse2"),
);
+ final downloadName = arch == Arch.amd64 ? "x86_64" : "aarch_64";
await Build.exec(
Build.getExecutable(
- "wget -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage",
+ "wget -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$downloadName.AppImage",
),
);
await Build.exec(
@@ -409,14 +444,14 @@ class BuildCommand extends Command {
@override
Future run() async {
final mode = target == Target.android ? Mode.lib : Mode.core;
- final String out = argResults?['out'] ?? 'app';
- Arch? arch;
- var archName = argResults?['arch'];
+ final String out = argResults?["out"] ?? (target.same ? "app" : "core");
+ final archName = argResults?["arch"];
+ final currentArches =
+ arches.where((element) => element.name == archName).toList();
+ final arch = currentArches.isEmpty ? null : currentArches.first;
- if (target == Target.android) {
- final currentArches =
- arches.where((element) => element.name == archName).toList();
- arch = currentArches.isEmpty ? null : currentArches.first;
+ if (arch == null && target != Target.android) {
+ throw "Invalid arch parameter";
}
await Build.buildCore(
@@ -440,13 +475,21 @@ class BuildCommand extends Command {
targets: "exe,zip",
args: "--description $archName",
);
+ return;
case Target.linux:
- await _getLinuxDependencies();
+ final targetMap = {
+ Arch.arm64: "linux-arm64",
+ Arch.amd64: "linux-x64",
+ };
+ final defaultTarget = targetMap[arch];
+ await _getLinuxDependencies(arch!);
_buildDistributor(
target: target,
- targets: "appimage,deb,rpm",
- args: "--description $archName",
+ targets: "appimage,deb",
+ args:
+ "--description $archName --build-target-platform $defaultTarget",
);
+ return;
case Target.android:
final targetMap = {
Arch.arm: "android-arm",
@@ -464,6 +507,7 @@ class BuildCommand extends Command {
args:
"--flutter-build-args split-per-abi --build-target-platform ${defaultTargets.join(",")}",
);
+ return;
case Target.macos:
await _getMacosDependencies();
_buildDistributor(
@@ -471,6 +515,7 @@ class BuildCommand extends Command {
targets: "dmg",
args: "--description $archName",
);
+ return;
}
}
}
@@ -478,14 +523,8 @@ class BuildCommand extends Command {
main(args) async {
final runner = CommandRunner("setup", "build Application");
runner.addCommand(BuildCommand(target: Target.android));
- if (Platform.isWindows) {
- runner.addCommand(BuildCommand(target: Target.windows));
- }
- if (Platform.isLinux) {
- runner.addCommand(BuildCommand(target: Target.linux));
- }
- if (Platform.isMacOS) {
- runner.addCommand(BuildCommand(target: Target.macos));
- }
+ runner.addCommand(BuildCommand(target: Target.linux));
+ runner.addCommand(BuildCommand(target: Target.windows));
+ runner.addCommand(BuildCommand(target: Target.macos));
runner.run(args);
}