Compare commits

..

60 Commits

Author SHA1 Message Date
chen08209
d3991a9360 Update build.yml 2024-06-08 15:09:29 +08:00
chen08209
7ea226ef4a Add one-click update all profiles
Add expire show
2024-06-08 15:04:18 +08:00
chen08209
9bf50e0f3b Optimize delay test2 2024-06-08 12:12:16 +08:00
chen08209
10203b6c87 cache 2024-06-08 12:11:32 +08:00
chen08209
86aaf7ded1 Use system fontFamily 2024-06-07 17:22:55 +08:00
chen08209
bc337c3999 Change go version 2024-06-07 17:20:53 +08:00
chen08209
38b1ce75d8 Temp remove tun mode
(cherry picked from commit d2d9bdab02)
2024-06-07 17:20:12 +08:00
chen08209
a3d75d276c Remove macos in workflow
(cherry picked from commit d0f8444b6d)
2024-06-07 17:20:11 +08:00
chen08209
3c46cd792c Optimize delay test
Add check ip
2024-06-06 16:33:32 +08:00
chen08209
42944b425f Fix tun unable to open 2024-06-06 16:33:32 +08:00
chen08209
4c72877e58 Fix the problem that the download of remote resources failed after GeodataMode was turned on, which caused the application to flash back.
Fix edit profile error
2024-06-06 16:33:32 +08:00
chen08209
81a19631e1 Fix quickStart change proxy error 2024-06-06 16:33:31 +08:00
chen08209
7f6a74b777 add check ip request 2024-06-06 16:33:30 +08:00
chen08209
eb9c3232ec Fix core version 2024-06-05 14:29:38 +08:00
chen08209
e8c9c619bf Fix core version 2024-06-05 14:13:04 +08:00
chen08209
17cd0cf1ed Update file_picker
Add resources page

Optimize more detail
2024-06-05 13:42:33 +08:00
chen08209
925c331498 Add access selected sorted 2024-06-03 14:46:16 +08:00
chen08209
0512d692f2 Fix notification duplicate creation issue
Fix AccessControl click issue
2024-06-03 11:43:41 +08:00
chen08209
844ce8ffb0 Fix Linux unable to open 2024-05-31 22:12:06 +08:00
chen08209
d2588596df Update README.md 3 2024-05-31 15:40:47 +08:00
chen08209
0e999e3deb Create LICENSE
(cherry picked from commit 3ef3190785)
2024-05-31 15:11:45 +08:00
chen08209
8109bbf46c Update README.md 2
(cherry picked from commit b1c763fcfa)
2024-05-31 15:11:45 +08:00
chen08209
32ea45e5ea Update README.md
(cherry picked from commit 9452ffa514)
2024-05-31 15:11:44 +08:00
chen08209
ecce17ad75 optimize checkUpdate 2024-05-31 09:59:18 +08:00
chen08209
96738090cf Fix submit error
(cherry picked from commit fd3040283c)
2024-05-31 09:09:05 +08:00
chen08209
4a8ea37142 add WebDAV
add Auto check updates

Optimize more details
2024-05-30 17:06:53 +08:00
chen08209
a5fdb90da5 optimize delayTest 2024-05-17 19:54:26 +08:00
chen08209
f9722cc761 upgrade flutter version
(cherry picked from commit 9a07c785f2)
2024-05-17 09:35:22 +08:00
chen08209
f01fb2ed1d Update kernel
Add import profile via QR code image
2024-05-15 20:19:50 +08:00
chen08209
74f4481071 Add compatibility mode and adapt clash scheme. 2024-05-11 14:08:13 +08:00
chen08209
9018f512ae Reconstruction application proxy logic 2024-05-07 17:59:11 +08:00
chen08209
265fc4a701 Fix Tab destroy error 2024-05-06 19:03:49 +08:00
chen08209
755974fc9e Optimize repeat healthcheck 2024-05-06 17:15:42 +08:00
chen08209
ba8eab4fc9 Optimize Direct mode ui 2024-05-06 15:26:38 +08:00
chen08209
f5cb46710f Optimize Healthcheck 2024-05-06 14:31:20 +08:00
chen08209
6483e80416 Remove proxies position animation, improve performance
Add Telegram Link
2024-05-06 14:31:19 +08:00
chen08209
535e6dc3a5 Update healthcheck policy 2024-05-06 14:31:19 +08:00
chen08209
ad86c20cfb New Check URLTest 2024-05-05 21:40:12 +08:00
chen08209
665330e17a Fix the problem of invalid auto-selection 2024-05-05 16:13:52 +08:00
chen08209
0eb001e717 New Async UpdateConfig 2024-05-05 03:12:45 +08:00
chen08209
a563991d74 add changeProfileDebounce 2024-05-04 21:51:40 +08:00
chen08209
3e2a30008c Update Workflow 2024-05-04 16:50:37 +08:00
chen08209
ff68d573d6 Fix ChangeProfile block 2024-05-04 16:39:21 +08:00
chen08209
3223fca7ba Fix Release Message Error
(cherry picked from commit aef50fe0e3)
2024-05-04 16:38:03 +08:00
chen08209
006f9127fc Update Selector 2
(cherry picked from commit fc0767ed25)
2024-05-04 16:37:59 +08:00
chen08209
a2709e155c Update Version
(cherry picked from commit dbf1724cca)
2024-05-04 16:37:57 +08:00
chen08209
684fa7b58e Fix Proxies Select Error
(cherry picked from commit 909aa4038e)
2024-05-04 16:37:55 +08:00
chen08209
03b4da54b5 Fix the problem that the proxy group is empty in global mode.
(cherry picked from commit 2d0a7d8d46)
2024-05-04 16:37:54 +08:00
chen08209
a904b55d11 Fix the problem that the proxy group is empty in global mode.
(cherry picked from commit ca96cd1d82)
2024-05-04 16:37:54 +08:00
chen08209
d711935e2e Add ProxyProvider2
(cherry picked from commit 91ab1e5dac)
2024-05-04 16:37:53 +08:00
chen08209
98b1496eff Add ProxyProvider
(cherry picked from commit b3a5f74df8)
2024-05-03 21:28:41 +08:00
chen08209
442c32b6eb Update Version 2024-05-03 15:32:12 +08:00
chen08209
949a2aaac3 Update ProxyGroup Sort 2024-05-03 14:31:10 +08:00
chen08209
c77463f337 Fix Android quickStart VpnService some problems 2024-05-02 00:46:42 +08:00
chen08209
00377d6070 Update version 2024-05-01 23:39:21 +08:00
chen08209
f393b4b3e9 Set Android notification low importance 2024-05-01 23:29:32 +08:00
chen08209
75e6cfde15 Add Telegram in README_zh_CN.md
(cherry picked from commit 8a188a37c9)
2024-05-01 21:52:22 +08:00
chen08209
7bfe5617d9 Add Telegram 2024-05-01 21:49:18 +08:00
chen08209
97cc96c243 Fix the issue that VpnService can't be closed correctly in special cases 2024-05-01 21:29:54 +08:00
chen08209
1821ee2f61 Fix the problem that TileService is not destroyed correctly in some cases
Adjust tab animation defaults
2024-05-01 15:13:09 +08:00
47 changed files with 387 additions and 842 deletions

View File

@@ -3,7 +3,7 @@ name: build
on:
push:
tags:
- 'v*'
- '*'
jobs:
build:
@@ -21,12 +21,6 @@ jobs:
os: macos-13
steps:
- name: Check Matrix
run: |
echo "Running on ${{ matrix.os }}"
echo "Arch: ${{ runner.arch }}"
gcc --version
- name: Checkout
uses: actions/checkout@v4
with:
@@ -88,7 +82,7 @@ jobs:
upload-release:
if: ${{ !contains(github.ref, '+') }}
if: ${{ !endsWith(github.ref, '-debug') }}
permissions: write-all
needs: [ build ]
runs-on: ubuntu-latest

2
.gitmodules vendored
View File

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

View File

@@ -34,8 +34,6 @@ on Mobile:
💡 Based on Material You Design, [Surfboard](https://github.com/getsurfboard/surfboard)-like UI
☁️ Supports data sync via WebDAV
✨ Support subscription link, Dark mode
## Contact

View File

@@ -34,8 +34,6 @@ on Mobile:
💡 基本 Material You 设计, 类[Surfboard](https://github.com/getsurfboard/surfboard)用户界面
☁️ 支持通过WebDAV同步数据
✨ 支持一键导入订阅, 深色模式
## Contact

View File

@@ -62,7 +62,7 @@ android {
defaultConfig {
applicationId "com.follow.clash"
minSdkVersion 24
minSdkVersion 21
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View File

@@ -1,6 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
@@ -14,15 +17,12 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
tools:ignore="SystemPermissionTypo" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission"/>
<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:extractNativeLibs="true"
android:networkSecurityConfig="@xml/network_security_config"
android:label="FlClash">
<activity
@@ -73,8 +73,7 @@
<service
android:name=".services.FlClashTileService"
android:exported="true"
android:icon="@drawable/icon"
android:foregroundServiceType="specialUse"
android:icon="@drawable/tile_icon"
android:label="FlClash"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>

View File

@@ -10,8 +10,3 @@ data class AccessControl(
val acceptList: List<String>,
val rejectList: List<String>,
)
data class Props (
val accessControl: AccessControl?,
val allowBypass: Boolean?,
)

View File

@@ -17,7 +17,6 @@ import androidx.core.content.ContextCompat
import com.follow.clash.GlobalState
import com.follow.clash.RunState
import com.follow.clash.models.AccessControl
import com.follow.clash.models.Props
import com.follow.clash.services.FlClashVpnService
import com.google.gson.Gson
import io.flutter.embedding.android.FlutterActivity
@@ -42,7 +41,7 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
private var flClashVpnService: FlClashVpnService? = null
private var isBound = false
private var port: Int? = null
private var props: Props? = null
private var accessControl: AccessControl? = null
private lateinit var title: String
private lateinit var content: String
@@ -74,8 +73,8 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
"StartProxy" -> {
port = call.argument<Int>("port")
val args = call.argument<String>("args")
props =
if (args != null) Gson().fromJson(args, Props::class.java) else null
accessControl =
if (args != null) Gson().fromJson(args, AccessControl::class.java) else null
handleStartVpn()
result.success(true)
}
@@ -122,7 +121,7 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
private fun startVpn(port: Int) {
if (GlobalState.runState.value == RunState.START) return;
flClashVpnService?.start(port, props)
flClashVpnService?.start(port, accessControl)
GlobalState.runState.value = RunState.START
GlobalState.runTime = Date()
startAfter()

View File

@@ -19,6 +19,7 @@ import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
@RequiresApi(Build.VERSION_CODES.N)
class FlClashTileService : TileService() {
private val observer = Observer<RunState> { runState ->
@@ -42,27 +43,19 @@ class FlClashTileService : TileService() {
GlobalState.runState.observeForever(observer)
}
@SuppressLint("StartActivityAndCollapseDeprecated")
private fun activityTransfer() {
val intent = Intent(this, TempActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
PendingIntent.getActivity(
if (Build.VERSION.SDK_INT >= 34) {
val pendingIntent = PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_IMMUTABLE
)
} else {
PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startActivityAndCollapse(pendingIntent)
}else{
} else {
startActivityAndCollapse(intent)
}
}

View File

@@ -1,9 +1,10 @@
package com.follow.clash.services
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
import android.net.ProxyInfo
@@ -12,14 +13,15 @@ import android.os.Binder
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
import androidx.core.graphics.drawable.IconCompat
import com.follow.clash.GlobalState
import com.follow.clash.MainActivity
import com.follow.clash.R
import com.follow.clash.models.AccessControl
import com.follow.clash.models.AccessControlMode
import com.follow.clash.models.Props
@SuppressLint("WrongConstant")
class FlClashVpnService : VpnService() {
@@ -52,12 +54,12 @@ class FlClashVpnService : VpnService() {
return START_STICKY
}
fun start(port: Int, props: Props?) {
fun start(port: Int, accessControl: AccessControl?) {
fd = with(Builder()) {
addAddress("172.16.0.1", 30)
setMtu(9000)
addRoute("0.0.0.0", 0)
props?.accessControl?.let { accessControl ->
if (accessControl != null) {
when (accessControl.mode) {
AccessControlMode.acceptSelected -> {
(accessControl.acceptList + packageName).forEach {
@@ -78,9 +80,7 @@ class FlClashVpnService : VpnService() {
if (Build.VERSION.SDK_INT >= 29) {
setMetered(false)
}
if (props?.allowBypass == true) {
allowBypass()
}
allowBypass()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
setHttpProxy(
ProxyInfo.buildDirectProxy(
@@ -100,10 +100,10 @@ class FlClashVpnService : VpnService() {
}
private val notificationBuilder: NotificationCompat.Builder by lazy {
private val notificationBuilder by lazy {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getActivity(
this,
0,
@@ -119,34 +119,34 @@ class FlClashVpnService : VpnService() {
)
}
val icon = IconCompat.createWithResource(this, this.applicationInfo.icon)
with(NotificationCompat.Builder(this, CHANNEL)) {
setSmallIcon(R.drawable.ic_stat_name)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setSmallIcon(icon)
}
setContentTitle("FlClash")
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
setContentIntent(pendingIntent)
setCategory(NotificationCompat.CATEGORY_SERVICE)
priority = NotificationCompat.PRIORITY_MIN
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
}
priority = NotificationCompat.PRIORITY_LOW
setOngoing(true)
setShowWhen(false)
setOnlyAlertOnce(true)
setAutoCancel(true);
}
}
@SuppressLint("ForegroundServiceType", "WrongConstant")
fun startForeground(title: String, content: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
channel.setShowBadge(false)
}
val notification =
notificationBuilder.setContentTitle(title).setContentText(content).build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else {
startForeground(notificationId, notification)
@@ -155,7 +155,7 @@ class FlClashVpnService : VpnService() {
private fun stopForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
stopForeground(STOP_FOREGROUND_REMOVE)
stopForeground(Service.STOP_FOREGROUND_REMOVE)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -336,8 +336,8 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
targetConfig.Mode = patchConfig.Mode
targetConfig.Tun.Enable = patchConfig.Tun.Enable
targetConfig.Tun.Device = patchConfig.Tun.Device
//targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
//targetConfig.Tun.Stack = patchConfig.Tun.Stack
targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
targetConfig.Tun.Stack = patchConfig.Tun.Stack
targetConfig.GeodataLoader = "standard"
targetConfig.Profile.StoreSelected = false
if targetConfig.DNS.Enable == false {

View File

@@ -1,6 +1,6 @@
module core
go 1.21.0
go 1.20
replace github.com/metacubex/mihomo => ./Clash.Meta

View File

@@ -10,7 +10,8 @@ class Picker {
if (Platform.isAndroid) {
filePickerResult = await FilePicker.platform.pickFiles(
withData: true,
allowMultiple: false,
type: FileType.custom,
allowedExtensions: ['txt', 'conf'],
);
} else {
filePickerResult = await FilePicker.platform.pickFiles(

View File

@@ -1,6 +1,6 @@
// ignore_for_file: constant_identifier_names
enum GroupType { Selector, URLTest, Fallback, LoadBalance, Relay }
enum GroupType { Selector, URLTest, Fallback }
enum GroupName { GLOBAL, Proxy, Auto, Fallback }
@@ -61,4 +61,4 @@ enum MessageType { log, tun, delay, process, now }
enum RecoveryOption {
all,
onlyProfiles,
}
}

View File

@@ -8,13 +8,6 @@ import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
extension AccessControlExtension on AccessControl {
List<String> get currentList => switch (mode) {
AccessControlMode.acceptSelected => acceptList,
AccessControlMode.rejectSelected => rejectList,
};
}
class AccessFragment extends StatefulWidget {
const AccessFragment({super.key});
@@ -90,64 +83,137 @@ class _AccessFragmentState extends State<AccessFragment> {
);
}
Widget _buildSearchButton(List<Package> packages) {
return IconButton(
tooltip: appLocalizations.search,
onPressed: () {
showSearch(
context: context,
delegate: AccessControlSearchDelegate(
packages: packages,
),
).then((_) => {setState(() {})});
Widget _buildSelectedAllButton({
required bool isSelectedAll,
required List<String> allValueList,
}) {
return Builder(
builder: (context) {
final tooltip = isSelectedAll
? appLocalizations.cancelSelectAll
: appLocalizations.selectAll;
return IconButton(
tooltip: tooltip,
onPressed: () {
final config = globalState.appController.config;
final isAccept =
config.accessControl.mode == AccessControlMode.acceptSelected;
if (isSelectedAll) {
config.accessControl = switch (isAccept) {
true => config.accessControl.copyWith(
acceptList: [],
),
false => config.accessControl.copyWith(
rejectList: [],
),
};
} else {
config.accessControl = switch (isAccept) {
true => config.accessControl.copyWith(
acceptList: allValueList,
),
false => config.accessControl.copyWith(
rejectList: allValueList,
),
};
}
},
icon: isSelectedAll
? const Icon(Icons.deselect)
: const Icon(Icons.select_all),
);
},
icon: const Icon(Icons.search),
);
}
// Widget _buildSelectedAllButton({
// required bool isSelectedAll,
// required List<String> allValueList,
// }) {
// return Builder(
// builder: (context) {
// final tooltip = isSelectedAll
// ? appLocalizations.cancelSelectAll
// : appLocalizations.selectAll;
// return IconButton(
// tooltip: tooltip,
// onPressed: () {
// final config = globalState.appController.config;
// final isAccept =
// config.accessControl.mode == AccessControlMode.acceptSelected;
//
// if (isSelectedAll) {
// config.accessControl = switch (isAccept) {
// true => config.accessControl.copyWith(
// acceptList: [],
// ),
// false => config.accessControl.copyWith(
// rejectList: [],
// ),
// };
// } else {
// config.accessControl = switch (isAccept) {
// true => config.accessControl.copyWith(
// acceptList: allValueList,
// ),
// false => config.accessControl.copyWith(
// rejectList: allValueList,
// ),
// };
// }
// },
// icon: isSelectedAll
// ? const Icon(Icons.deselect)
// : const Icon(Icons.select_all),
// );
// },
// );
// }
Widget _actionHeader({
required bool isAccessControl,
required List<String> valueList,
required String describe,
required List<String> packageNameList,
}) {
return AbsorbPointer(
absorbing: !isAccessControl,
child: Padding(
padding: const EdgeInsets.only(
top: 4,
bottom: 4,
left: 16,
right: 8,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: IntrinsicHeight(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Row(
children: [
Flexible(
child: Text(
appLocalizations.selected,
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color:
Theme.of(context).colorScheme.primary,
),
),
),
const Flexible(
child: SizedBox(
width: 8,
),
),
Flexible(
child: Text(
"${valueList.length}",
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color:
Theme.of(context).colorScheme.primary,
),
),
),
],
),
),
Flexible(
child: Text(describe),
)
],
),
),
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Flexible(
child: _buildSelectedAllButton(
isSelectedAll: const ListEquality<String>()
.equals(valueList, packageNameList),
allValueList: packageNameList,
),
),
Flexible(child: _buildFilterSystemAppButton()),
Flexible(child: _buildAppProxyModePopup()),
],
),
],
),
),
);
}
Widget _buildPackageList() {
return ValueListenableBuilder(
@@ -186,11 +252,14 @@ class _AccessFragmentState extends State<AccessFragment> {
accessControlMode == AccessControlMode.acceptSelected
? acceptPackages
: rejectPackages;
final currentList = accessControl.currentList;
final currentList =
accessControlMode == AccessControlMode.acceptSelected
? accessControl.acceptList
: accessControl.rejectList;
final currentPackages = isFilterSystemApp
? packages
.where((element) => element.isSystem == false)
.toList()
.where((element) => element.isSystem == false)
.toList()
: packages;
final packageNameList =
currentPackages.map((e) => e.packageName).toList();
@@ -203,82 +272,11 @@ class _AccessFragmentState extends State<AccessFragment> {
status: !isAccessControl,
child: Column(
children: [
AbsorbPointer(
absorbing: !isAccessControl,
child: Padding(
padding: const EdgeInsets.only(
top: 4,
bottom: 4,
left: 16,
right: 8,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: IntrinsicHeight(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Row(
children: [
Flexible(
child: Text(
appLocalizations.selected,
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: Theme.of(context)
.colorScheme
.primary,
),
),
),
const Flexible(
child: SizedBox(
width: 8,
),
),
Flexible(
child: Text(
"${valueList.length}",
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: Theme.of(context)
.colorScheme
.primary,
),
),
),
],
),
),
Flexible(
child: Text(describe),
)
],
),
),
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Flexible(
child: _buildSearchButton(currentPackages)),
Flexible(child: _buildFilterSystemAppButton()),
Flexible(child: _buildAppProxyModePopup()),
],
),
],
),
),
_actionHeader(
isAccessControl: isAccessControl,
valueList: valueList,
describe: describe,
packageNameList: packageNameList,
),
Expanded(
flex: 1,
@@ -431,110 +429,3 @@ class PackageListItem extends StatelessWidget {
);
}
}
class AccessControlSearchDelegate extends SearchDelegate {
final List<Package> packages;
AccessControlSearchDelegate({
required this.packages,
});
List<Package> get _results {
final lowQuery = query.toLowerCase();
return packages
.where(
(package) =>
package.label.toLowerCase().contains(lowQuery) ||
package.packageName.contains(lowQuery),
)
.toList();
}
@override
List<Widget>? buildActions(BuildContext context) {
return [
IconButton(
onPressed: () {
if (query.isEmpty) {
close(context, null);
return;
}
query = '';
},
icon: const Icon(Icons.clear),
),
const SizedBox(
width: 8,
)
];
}
@override
Widget? buildLeading(BuildContext context) {
return IconButton(
onPressed: () {
close(context, null);
},
icon: const Icon(Icons.arrow_back),
);
}
Widget _packageList(List<Package> packages) {
return Selector<Config, PackageListSelectorState>(
selector: (_, config) => PackageListSelectorState(
accessControl: config.accessControl,
isAccessControl: config.isAccessControl,
),
builder: (context, state, __) {
final accessControl = state.accessControl;
final isAccessControl = state.isAccessControl;
final accessControlMode = accessControl.mode;
final currentList = accessControl.currentList;
final packageNameList = this.packages.map((e) => e.packageName).toList();
final valueList = currentList.intersection(packageNameList);
return DisabledMask(
status: !isAccessControl,
child: ListView.builder(
itemCount: packages.length,
itemBuilder: (_, index) {
final package = packages[index];
return PackageListItem(
key: Key(package.packageName),
package: package,
value: valueList.contains(package.packageName),
isActive: isAccessControl,
onChanged: (value) {
if (value == true) {
valueList.add(package.packageName);
} else {
valueList.remove(package.packageName);
}
final config = globalState.appController.config;
if (accessControlMode == AccessControlMode.acceptSelected) {
config.accessControl = config.accessControl.copyWith(
acceptList: valueList,
);
} else {
config.accessControl = config.accessControl.copyWith(
rejectList: valueList,
);
}
},
);
},
),
);
},
);
}
@override
Widget buildResults(BuildContext context) {
return buildSuggestions(context);
}
@override
Widget buildSuggestions(BuildContext context) {
return _packageList(_results);
}
}

View File

@@ -1,5 +1,3 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
@@ -56,7 +54,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
onTab: () {
_modifyMixedPort(mixedPort);
},
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 16,vertical: 4),
leading: const Icon(Icons.adjust),
title: Text(appLocalizations.proxyPort),
trailing: FilledButton.tonal(
@@ -107,24 +105,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
// );
// },
// ),
if (Platform.isAndroid)
Selector<Config, bool>(
selector: (_, config) => config.allowBypass,
builder: (_, allowBypass, __) {
return ListItem.switchItem(
leading: const Icon(Icons.double_arrow),
title: Text(appLocalizations.allowBypass),
subtitle: Text(appLocalizations.allowBypassDesc),
delegate: SwitchDelegate(
value: allowBypass,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.config.allowBypass = value;
},
),
);
},
),
Selector<Config, bool>(
selector: (_, config) => config.isCompatible,
builder: (_, isCompatible, __) {

View File

@@ -38,7 +38,6 @@ class _NetworkDetectionState extends State<NetworkDetection> {
} else {
timeoutNotifier.value = false;
}
_preIsStart = isStart;
ipInfoNotifier.value = ipInfo;
}

View File

@@ -17,16 +17,9 @@ enum ProfileActions {
delete,
}
class ProfilesFragment extends StatefulWidget {
class ProfilesFragment extends StatelessWidget {
const ProfilesFragment({super.key});
@override
State<ProfilesFragment> createState() => _ProfilesFragmentState();
}
class _ProfilesFragmentState extends State<ProfilesFragment> {
final hasPadding = ValueNotifier<bool>(false);
_handleShowAddExtendPage() {
showExtendPage(
globalState.navigatorKey.currentState!.context,
@@ -48,7 +41,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
}
}
_initScaffoldState() {
_initScaffoldState(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
final commonScaffoldState =
@@ -62,7 +55,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
},
);
},
icon: const Icon(Icons.sync),
icon: const Icon(Icons.download),
),
const SizedBox(
width: 8,
@@ -85,7 +78,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
selector: (_, appState) => appState.currentLabel == 'profiles',
builder: (_, isCurrent, child) {
if (isCurrent) {
_initScaffoldState();
_initScaffoldState(context);
}
return child!;
},
@@ -105,46 +98,27 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
final isMobile = state.viewMode == ViewMode.mobile;
return Align(
alignment: Alignment.topCenter,
child: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
WidgetsBinding.instance.addPostFrameCallback((_) {
hasPadding.value =
scrollNotification.metrics.maxScrollExtent > 0;
});
return true;
},
child: ValueListenableBuilder(
valueListenable: hasPadding,
builder: (_, hasPadding, __) {
return SingleChildScrollView(
padding: !isMobile
? EdgeInsets.only(
left: 16,
right: 16,
top: 16,
bottom: 16 + (hasPadding ? 56 : 0),
)
: EdgeInsets.only(
bottom: 0 + (hasPadding ? 56 : 0),
),
child: Grid(
mainAxisSpacing: isMobile ? 8 : 16,
crossAxisSpacing: 16,
crossAxisCount: columns,
children: [
for (final profile in state.profiles)
GridItem(
child: ProfileItem(
profile: profile,
groupValue: state.currentProfileId,
onChanged:
globalState.appController.changeProfile,
),
),
],
child: SingleChildScrollView(
padding: !isMobile
? const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
)
: EdgeInsets.zero,
child: Grid(
mainAxisSpacing: isMobile ? 8 : 16,
crossAxisSpacing: 16,
crossAxisCount: columns,
children: [
for (final profile in state.profiles)
GridItem(
child: ProfileItem(
profile: profile,
groupValue: state.currentProfileId,
onChanged: globalState.appController.changeProfile,
),
),
);
},
],
),
),
);

View File

@@ -199,15 +199,10 @@ class ProxiesTabView extends StatelessWidget {
_delayTest(List<Proxy> proxies) async {
for (final proxy in proxies) {
final appController = globalState.appController;
final proxyName = appController.appState.getRealProxyName(proxy.name) ?? proxy.name;
globalState.appController.setDelay(
Delay(
name: proxyName,
value: 0,
),
Delay(name: proxy.name, value: 0),
);
clashCore.getDelay(proxyName).then((delay) {
clashCore.getDelay(proxy.name).then((delay) {
globalState.appController.setDelay(delay);
});
}
@@ -439,7 +434,7 @@ class _DelayTestButtonContainerState extends State<DelayTestButtonContainer>
_controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 200,
milliseconds: 600,
),
);
_scale = Tween<double>(

View File

@@ -160,8 +160,5 @@
"checking": "Checking...",
"country": "Country",
"checkError": "Check error",
"ipCheckTimeout": "Ip check timeout",
"search": "Search",
"allowBypass": "Allow applications to bypass VPN",
"allowBypassDesc": "After opening, some applications can bypass VPN"
"ipCheckTimeout": "Ip check timeout"
}

View File

@@ -160,8 +160,5 @@
"checking": "检测中...",
"country": "区域",
"checkError": "检测失败",
"ipCheckTimeout": "Ip检测超时",
"search": "搜索",
"allowBypass": "允许应用绕过vpn",
"allowBypassDesc": "开启后部分应用可绕过VPN"
"ipCheckTimeout": "Ip检测超时"
}

View File

@@ -40,10 +40,6 @@ class MessageLookup extends MessageLookupByLibrary {
"addressTip": MessageLookupByLibrary.simpleMessage(
"Please enter a valid WebDAV address"),
"ago": MessageLookupByLibrary.simpleMessage(" Ago"),
"allowBypass": MessageLookupByLibrary.simpleMessage(
"Allow applications to bypass VPN"),
"allowBypassDesc": MessageLookupByLibrary.simpleMessage(
"After opening, some applications can bypass VPN"),
"allowLan": MessageLookupByLibrary.simpleMessage("AllowLan"),
"allowLanDesc": MessageLookupByLibrary.simpleMessage(
"Allow access proxy through the LAN"),
@@ -216,7 +212,6 @@ class MessageLookup extends MessageLookupByLibrary {
"External resource related info"),
"rule": MessageLookupByLibrary.simpleMessage("Rule"),
"save": MessageLookupByLibrary.simpleMessage("Save"),
"search": MessageLookupByLibrary.simpleMessage("Search"),
"selectAll": MessageLookupByLibrary.simpleMessage("Select all"),
"selected": MessageLookupByLibrary.simpleMessage("Selected"),
"settings": MessageLookupByLibrary.simpleMessage("Settings"),

View File

@@ -36,9 +36,6 @@ class MessageLookup extends MessageLookupByLibrary {
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
"ago": MessageLookupByLibrary.simpleMessage(""),
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过vpn"),
"allowBypassDesc":
MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"),
"allowLan": MessageLookupByLibrary.simpleMessage("局域网代理"),
"allowLanDesc": MessageLookupByLibrary.simpleMessage("允许通过局域网访问代理"),
"appAccessControl": MessageLookupByLibrary.simpleMessage("应用访问控制"),
@@ -174,7 +171,6 @@ class MessageLookup extends MessageLookupByLibrary {
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"),
"rule": MessageLookupByLibrary.simpleMessage("规则"),
"save": MessageLookupByLibrary.simpleMessage("保存"),
"search": MessageLookupByLibrary.simpleMessage("搜索"),
"selectAll": MessageLookupByLibrary.simpleMessage("全选"),
"selected": MessageLookupByLibrary.simpleMessage("已选择"),
"settings": MessageLookupByLibrary.simpleMessage("设置"),

View File

@@ -1669,36 +1669,6 @@ class AppLocalizations {
args: [],
);
}
/// `Search`
String get search {
return Intl.message(
'Search',
name: 'search',
desc: '',
args: [],
);
}
/// `Allow applications to bypass VPN`
String get allowBypass {
return Intl.message(
'Allow applications to bypass VPN',
name: 'allowBypass',
desc: '',
args: [],
);
}
/// `After opening, some applications can bypass VPN`
String get allowBypassDesc {
return Intl.message(
'After opening, some applications can bypass VPN',
name: 'allowBypassDesc',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -107,7 +107,7 @@ class AppState with ChangeNotifier {
} else {
final index = groups.indexWhere((element) => element.name == proxyName);
if (index == -1) return type;
return "$type(${groups[index].now ?? '*'})";
return "$type(${groups[index].now})";
}
}

View File

@@ -16,7 +16,7 @@ class Tun with _$Tun {
const factory Tun({
@Default(false) bool enable,
@Default(appName) String device,
@Default(TunStack.gvisor) TunStack stack,
@Default(TunStack.mixed) TunStack stack,
@JsonKey(name: "dns-hijack") @Default(["any:53"]) List<String> dnsHijack,
}) = _Tun;

View File

@@ -8,7 +8,6 @@ import '../common/common.dart';
import 'models.dart';
part 'generated/config.g.dart';
part 'generated/config.freezed.dart';
@freezed
@@ -24,17 +23,6 @@ class AccessControl with _$AccessControl {
_$AccessControlFromJson(json);
}
@freezed
class Props with _$Props {
const factory Props({
AccessControl? accessControl,
bool? allowBypass,
}) = _Props;
factory Props.fromJson(Map<String, Object?> json) =>
_$PropsFromJson(json);
}
@JsonSerializable()
class Config extends ChangeNotifier {
List<Profile> _profiles;
@@ -53,7 +41,6 @@ class Config extends ChangeNotifier {
AccessControl _accessControl;
bool _isAnimateToPage;
bool _autoCheckUpdate;
bool _allowBypass;
DAV? _dav;
Config()
@@ -70,8 +57,7 @@ class Config extends ChangeNotifier {
_isAccessControl = false,
_autoCheckUpdate = true,
_accessControl = const AccessControl(),
_isAnimateToPage = true,
_allowBypass = true;
_isAnimateToPage = true;
deleteProfileById(String id) {
_profiles = profiles.where((element) => element.id != id).toList();
@@ -319,22 +305,8 @@ class Config extends ChangeNotifier {
}
}
@JsonKey(defaultValue: true)
bool get allowBypass {
return _allowBypass;
}
set allowBypass(bool value) {
if (_allowBypass != value) {
_allowBypass = value;
notifyListeners();
}
}
update([
Config? config,
RecoveryOption recoveryOptions = RecoveryOption.all,
]) {
update(
[Config? config, RecoveryOption recoveryOptions = RecoveryOption.all]) {
if (config != null) {
_profiles = config._profiles;
for (final profile in config._profiles) {
@@ -353,7 +325,6 @@ class Config extends ChangeNotifier {
_openLog = config._openLog;
_themeMode = config._themeMode;
_locale = config._locale;
_allowBypass = config._allowBypass;
_primaryColor = config._primaryColor;
_proxiesSortType = config._proxiesSortType;
_isMinimizeOnExit = config._isMinimizeOnExit;

View File

@@ -135,7 +135,7 @@ class _$TunImpl implements _Tun {
const _$TunImpl(
{this.enable = false,
this.device = appName,
this.stack = TunStack.gvisor,
this.stack = TunStack.mixed,
@JsonKey(name: "dns-hijack")
final List<String> dnsHijack = const ["any:53"]})
: _dnsHijack = dnsHijack;

View File

@@ -79,7 +79,7 @@ _$TunImpl _$$TunImplFromJson(Map<String, dynamic> json) => _$TunImpl(
enable: json['enable'] as bool? ?? false,
device: json['device'] as String? ?? appName,
stack: $enumDecodeNullable(_$TunStackEnumMap, json['stack']) ??
TunStack.gvisor,
TunStack.mixed,
dnsHijack: (json['dns-hijack'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??

View File

@@ -239,172 +239,3 @@ abstract class _AccessControl implements AccessControl {
_$$AccessControlImplCopyWith<_$AccessControlImpl> get copyWith =>
throw _privateConstructorUsedError;
}
Props _$PropsFromJson(Map<String, dynamic> json) {
return _Props.fromJson(json);
}
/// @nodoc
mixin _$Props {
AccessControl? get accessControl => throw _privateConstructorUsedError;
bool? get allowBypass => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$PropsCopyWith<Props> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PropsCopyWith<$Res> {
factory $PropsCopyWith(Props value, $Res Function(Props) then) =
_$PropsCopyWithImpl<$Res, Props>;
@useResult
$Res call({AccessControl? accessControl, bool? allowBypass});
$AccessControlCopyWith<$Res>? get accessControl;
}
/// @nodoc
class _$PropsCopyWithImpl<$Res, $Val extends Props>
implements $PropsCopyWith<$Res> {
_$PropsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accessControl = freezed,
Object? allowBypass = freezed,
}) {
return _then(_value.copyWith(
accessControl: freezed == accessControl
? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?,
allowBypass: freezed == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
as bool?,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$AccessControlCopyWith<$Res>? get accessControl {
if (_value.accessControl == null) {
return null;
}
return $AccessControlCopyWith<$Res>(_value.accessControl!, (value) {
return _then(_value.copyWith(accessControl: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$PropsImplCopyWith<$Res> implements $PropsCopyWith<$Res> {
factory _$$PropsImplCopyWith(
_$PropsImpl value, $Res Function(_$PropsImpl) then) =
__$$PropsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({AccessControl? accessControl, bool? allowBypass});
@override
$AccessControlCopyWith<$Res>? get accessControl;
}
/// @nodoc
class __$$PropsImplCopyWithImpl<$Res>
extends _$PropsCopyWithImpl<$Res, _$PropsImpl>
implements _$$PropsImplCopyWith<$Res> {
__$$PropsImplCopyWithImpl(
_$PropsImpl _value, $Res Function(_$PropsImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accessControl = freezed,
Object? allowBypass = freezed,
}) {
return _then(_$PropsImpl(
accessControl: freezed == accessControl
? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?,
allowBypass: freezed == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
as bool?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$PropsImpl implements _Props {
const _$PropsImpl({this.accessControl, this.allowBypass});
factory _$PropsImpl.fromJson(Map<String, dynamic> json) =>
_$$PropsImplFromJson(json);
@override
final AccessControl? accessControl;
@override
final bool? allowBypass;
@override
String toString() {
return 'Props(accessControl: $accessControl, allowBypass: $allowBypass)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PropsImpl &&
(identical(other.accessControl, accessControl) ||
other.accessControl == accessControl) &&
(identical(other.allowBypass, allowBypass) ||
other.allowBypass == allowBypass));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, accessControl, allowBypass);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$PropsImplCopyWith<_$PropsImpl> get copyWith =>
__$$PropsImplCopyWithImpl<_$PropsImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$PropsImplToJson(
this,
);
}
}
abstract class _Props implements Props {
const factory _Props(
{final AccessControl? accessControl,
final bool? allowBypass}) = _$PropsImpl;
factory _Props.fromJson(Map<String, dynamic> json) = _$PropsImpl.fromJson;
@override
AccessControl? get accessControl;
@override
bool? get allowBypass;
@override
@JsonKey(ignore: true)
_$$PropsImplCopyWith<_$PropsImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -32,8 +32,7 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
: DAV.fromJson(json['dav'] as Map<String, dynamic>)
..isAnimateToPage = json['isAnimateToPage'] as bool? ?? true
..isCompatible = json['isCompatible'] as bool? ?? true
..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true
..allowBypass = json['allowBypass'] as bool? ?? true;
..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true;
Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'profiles': instance.profiles,
@@ -53,7 +52,6 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'isAnimateToPage': instance.isAnimateToPage,
'isCompatible': instance.isCompatible,
'autoCheckUpdate': instance.autoCheckUpdate,
'allowBypass': instance.allowBypass,
};
const _$ThemeModeEnumMap = {
@@ -95,17 +93,3 @@ const _$AccessControlModeEnumMap = {
AccessControlMode.acceptSelected: 'acceptSelected',
AccessControlMode.rejectSelected: 'rejectSelected',
};
_$PropsImpl _$$PropsImplFromJson(Map<String, dynamic> json) => _$PropsImpl(
accessControl: json['accessControl'] == null
? null
: AccessControl.fromJson(
json['accessControl'] as Map<String, dynamic>),
allowBypass: json['allowBypass'] as bool?,
);
Map<String, dynamic> _$$PropsImplToJson(_$PropsImpl instance) =>
<String, dynamic>{
'accessControl': instance.accessControl,
'allowBypass': instance.allowBypass,
};

View File

@@ -28,8 +28,6 @@ const _$GroupTypeEnumMap = {
GroupType.Selector: 'Selector',
GroupType.URLTest: 'URLTest',
GroupType.Fallback: 'Fallback',
GroupType.LoadBalance: 'LoadBalance',
GroupType.Relay: 'Relay',
};
_$ProxyImpl _$$ProxyImplFromJson(Map<String, dynamic> json) => _$ProxyImpl(

View File

@@ -107,9 +107,6 @@ class _ScanPageState extends State<ScanPage> with WidgetsBindingObserver {
case TorchState.unavailable:
icon = const Icon(Icons.flash_off);
backgroundColor = Colors.transparent;
case TorchState.auto:
icon = const Icon(Icons.flash_auto);
backgroundColor = Colors.orange;
}
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8),

View File

@@ -61,14 +61,8 @@ class GlobalState {
required Config config,
required ClashConfig clashConfig,
}) async {
final args = config.isAccessControl
? json.encode(
Props(
accessControl: config.accessControl,
allowBypass: config.allowBypass,
),
)
: null;
final args =
config.isAccessControl ? json.encode(config.accessControl) : null;
await proxyManager.startProxy(
port: clashConfig.mixedPort,
args: args,

View File

@@ -122,6 +122,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
return floatingActionButton ?? Container();
},
),
floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Stack(

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none'
version: 0.8.19
version: 0.8.16
environment:
sdk: '>=3.1.0 <4.0.0'
@@ -24,7 +24,7 @@ dependencies:
windows_single_instance: ^1.0.1
json_annotation: ^4.9.0
file_picker: ^8.0.3
mobile_scanner: ^5.1.1
mobile_scanner: 5.0.1
app_links: ^3.5.0
win32_registry: ^1.1.2
tray_manager: ^0.2.1

View File

@@ -12,7 +12,10 @@ enum PlatformType {
macos,
}
enum Arch { amd64, arm64, arm }
enum Arch {
amd64,
arm64,
}
class BuildLibItem {
PlatformType platform;
@@ -61,11 +64,6 @@ class Build {
arch: Arch.amd64,
archName: 'amd64',
),
BuildLibItem(
platform: PlatformType.android,
arch: Arch.arm,
archName: 'armeabi-v7a',
),
BuildLibItem(
platform: PlatformType.android,
arch: Arch.arm64,
@@ -336,7 +334,7 @@ class BuildCommand extends Command {
final archName = argResults?['arch'];
final currentArches =
arches.where((element) => element.name == archName).toList();
final arch = currentArches.isEmpty ? null : currentArches.first;
final arch = currentArches.isEmpty ? null : arches.first;
await _buildLib(arch);
if (build != "all") {
return;
@@ -359,11 +357,10 @@ class BuildCommand extends Command {
break;
case PlatformType.android:
final targetMap = {
Arch.arm: "android-arm",
Arch.arm64: "android-arm64",
Arch.amd64: "android-x64",
Arch.arm64: "android-arm64"
};
final defaultArches = [Arch.arm, Arch.arm64, Arch.amd64];
final defaultArches = [Arch.amd64, Arch.arm64];
final defaultTargets = defaultArches
.where((element) => arch == null ? true : element == arch)
.map((e) => targetMap[e])

View File

@@ -1,4 +1,5 @@
// ignore_for_file: avoid_print
void main() async {
String input = """