From 375c4e088462c3c59539bf21715b1adae38dd2a4 Mon Sep 17 00:00:00 2001 From: chen08209 Date: Fri, 6 Dec 2024 22:35:28 +0800 Subject: [PATCH] Support better window position memory Add windows arm64 and linux arm64 build script Optimize some details --- .github/release_template.md | 5 +- .github/workflows/build.yaml | 67 ++++++++++++- .github/workflows/change.yaml | 66 ------------- CHANGELOG.md | 34 +++++++ README.md | 29 +++++- README_zh_CN.md | 30 +++++- android/app/src/main/AndroidManifest.xml | 7 +- .../kotlin/com/follow/clash/GlobalState.kt | 25 +++-- .../kotlin/com/follow/clash/TempActivity.kt | 4 + .../kotlin/com/follow/clash/extensions/Ext.kt | 16 +++ .../com/follow/clash/plugins/AppPlugin.kt | 2 - .../com/follow/clash/plugins/VpnPlugin.kt | 19 ++-- lib/common/other.dart | 12 +-- lib/common/tray.dart | 2 +- lib/common/window.dart | 30 ++++++ lib/controller.dart | 8 ++ lib/fragments/connections.dart | 3 + lib/fragments/dashboard/outbound_mode.dart | 13 +-- lib/fragments/proxies/list.dart | 2 +- lib/fragments/proxies/tab.dart | 20 +++- lib/manager/window_manager.dart | 6 ++ lib/models/app.dart | 4 +- plugins/flutter_distributor | 2 +- pubspec.lock | 2 +- pubspec.yaml | 3 +- setup.dart | 99 +++++++++++++------ 26 files changed, 353 insertions(+), 157 deletions(-) delete mode 100644 .github/workflows/change.yaml 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 Get it on F-Droid Get it on GitHub @@ -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 @@ [![Channel](https://img.shields.io/badge/Telegram-Channel-blue?style=flat-square&logo=telegram)](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 Get it on F-Droid Get it on GitHub @@ -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); }