Compare commits

..

66 Commits

Author SHA1 Message Date
chen08209 7acf9c6db3 Fix LoadBalance, Relay load error 2024-06-09 20:53:36 +08:00
chen08209 8074547fb4 Fix build.yml4 2024-06-09 19:56:51 +08:00
chen08209 8a01e04871 Fix build.yml3 2024-06-09 19:49:51 +08:00
chen08209 7ddcdd9828 Fix build.yml2 2024-06-09 19:49:14 +08:00
chen08209 d89ed076fd Fix build.yml 2024-06-09 19:46:05 +08:00
chen08209 f4c3b06cd5 Add search function at access control
Fix the issues with the profile add button to cover the edit button

Adapt LoadBalance and Relay

Add arm

Fix android notification icon error
2024-06-09 19:25:14 +08:00
chen08209 c65746709d Add one-click update all profiles
Add expire show
2024-06-08 15:43:28 +08:00
chen08209 d2d9bdab02 Temp remove tun mode 2024-06-06 22:58:32 +08:00
chen08209 d0f8444b6d Remove macos in workflow 2024-06-06 22:29:26 +08:00
chen08209 fccabfbe27 Change go version 2024-06-06 22:04:29 +08:00
chen08209 e9bb97c6ce Update Version
Fix tun unable to open
2024-06-06 17:36:49 +08:00
chen08209 068fe14d89 Optimize delay test2 2024-06-06 17:13:32 +08:00
chen08209 43c397007c Optimize delay test
Add check ip
2024-06-06 16:35:09 +08:00
chen08209 5e801d5f5e add check ip request 2024-06-06 16:34:31 +08:00
chen08209 52d61b15fd 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 10:15:51 +08:00
chen08209 fea3c14608 Fix quickStart change proxy error 2024-06-05 17:59:53 +08:00
chen08209 84be01a38a Fix core version 2024-06-05 17:59:50 +08:00
chen08209 93da242148 Fix core version 2024-06-05 14:13:54 +08:00
chen08209 bb7e44da30 Update file_picker
Add resources page

Optimize more detail
2024-06-05 13:44:11 +08:00
chen08209 01f1b2d72f Add access selected sorted 2024-06-05 13:43:44 +08:00
chen08209 3074b1299e Fix notification duplicate creation issue
Fix AccessControl click issue
2024-06-03 11:48:50 +08:00
chen08209 bd5470b863 Fix Workflow 2024-05-31 22:34:46 +08:00
chen08209 83fafa4b68 Fix Linux unable to open 2024-05-31 22:12:41 +08:00
chen08209 b7fb969301 Update README.md 3 2024-05-31 15:41:08 +08:00
chen08209 3ef3190785 Create LICENSE 2024-05-31 15:11:15 +08:00
chen08209 b1c763fcfa Update README.md 2 2024-05-31 15:08:51 +08:00
chen08209 9452ffa514 Update README.md 2024-05-31 15:02:18 +08:00
chen08209 c9291e5027 Optimize workFlow 2024-05-31 14:25:15 +08:00
chen08209 3ba86dc9c2 optimize checkUpdate 2024-05-31 10:07:51 +08:00
chen08209 fd3040283c Fix submit error 2024-05-30 17:22:23 +08:00
chen08209 91d30c0f0e add WebDAV
add Auto check updates

Optimize more details
2024-05-30 17:11:15 +08:00
chen08209 c4b470ffaf optimize delayTest 2024-05-17 19:54:57 +08:00
chen08209 9a07c785f2 upgrade flutter version 2024-05-15 20:34:59 +08:00
chen08209 a134c32493 Update kernel
Add import profile via QR code image
2024-05-15 20:21:02 +08:00
chen08209 472cea9037 Add compatibility mode and adapt clash scheme. 2024-05-11 14:10:06 +08:00
chen08209 08d07498b9 update Version 2024-05-07 18:32:21 +08:00
chen08209 d5aa09949a Reconstruction application proxy logic 2024-05-07 18:31:14 +08:00
chen08209 fd1dfe5c60 Fix Tab destroy error 2024-05-06 19:05:27 +08:00
chen08209 9f89fe8b29 Optimize repeat healthcheck 2024-05-06 17:17:26 +08:00
chen08209 78081a12e8 Optimize Direct mode ui 2024-05-06 15:27:37 +08:00
chen08209 6896837f28 Optimize Healthcheck 2024-05-06 14:32:23 +08:00
chen08209 85eb903402 Remove proxies position animation, improve performance
Add Telegram Link
2024-05-06 14:32:22 +08:00
chen08209 9aa9180f1f Update healthcheck policy 2024-05-06 14:32:21 +08:00
chen08209 feb9688a29 New Check URLTest 2024-05-06 14:32:21 +08:00
chen08209 5c71992174 Fix the problem of invalid auto-selection 2024-05-05 16:14:34 +08:00
chen08209 74c3d0ae25 New Async UpdateConfig 2024-05-05 03:13:52 +08:00
chen08209 ecd1bcafd5 add changeProfileDebounce 2024-05-05 03:13:51 +08:00
chen08209 184d2d117a Update Workflow 2024-05-05 03:13:50 +08:00
chen08209 89e6f17794 Fix ChangeProfile block 2024-05-05 03:13:49 +08:00
chen08209 aef50fe0e3 Fix Release Message Error 2024-05-04 16:14:03 +08:00
chen08209 fc0767ed25 Update Selector 2 2024-05-04 01:14:56 +08:00
chen08209 dbf1724cca Update Version 2024-05-04 00:14:34 +08:00
chen08209 909aa4038e Fix Proxies Select Error 2024-05-04 00:14:07 +08:00
chen08209 2d0a7d8d46 Fix the problem that the proxy group is empty in global mode. 2024-05-03 23:08:06 +08:00
chen08209 ca96cd1d82 Fix the problem that the proxy group is empty in global mode. 2024-05-03 23:07:38 +08:00
chen08209 91ab1e5dac Add ProxyProvider2 2024-05-03 21:48:22 +08:00
chen08209 b3a5f74df8 Add ProxyProvider 2024-05-03 21:28:22 +08:00
chen08209 1f98be8ad8 Update Version 2024-05-03 15:33:46 +08:00
chen08209 453c7c98d0 Update ProxyGroup Sort 2024-05-03 15:33:45 +08:00
chen08209 91faed35c0 Fix Android quickStart VpnService some problems 2024-05-02 00:32:11 +08:00
chen08209 07bbaf6b6f Update version 2024-05-01 23:40:04 +08:00
chen08209 e8feb7c431 Set Android notification low importance 2024-05-01 23:40:03 +08:00
chen08209 4d16820526 Fix the issue that VpnService can't be closed correctly in special cases 2024-05-01 23:40:00 +08:00
chen08209 92294b49c6 Fix the problem that TileService is not destroyed correctly in some cases
Adjust tab animation defaults
2024-05-01 23:39:59 +08:00
chen08209 8a188a37c9 Add Telegram in README_zh_CN.md 2024-05-01 21:52:07 +08:00
chen08209 48af16c265 Add Telegram 2024-05-01 21:50:26 +08:00
37 changed files with 536 additions and 367 deletions
+4 -4
View File
@@ -3,7 +3,7 @@ name: build
on:
push:
tags:
- '*'
- 'v*'
jobs:
build:
@@ -17,8 +17,8 @@ jobs:
os: windows-latest
- platform: linux
os: ubuntu-latest
- platform: macos
os: macos-13
# - platform: macos
# os: macos-13
steps:
- name: Checkout
@@ -82,7 +82,7 @@ jobs:
upload-release:
if: ${{ !endsWith(github.ref, '-debug') }}
if: ${{ !contains(github.ref, '+') }}
permissions: write-all
needs: [ build ]
runs-on: ubuntu-latest
+2
View File
@@ -34,6 +34,8 @@ 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
+2
View File
@@ -34,6 +34,8 @@ on Mobile:
💡 基本 Material You 设计, 类[Surfboard](https://github.com/getsurfboard/surfboard)用户界面
☁️ 支持通过WebDAV同步数据
✨ 支持一键导入订阅, 深色模式
## Contact
+1 -1
View File
@@ -62,7 +62,7 @@ android {
defaultConfig {
applicationId "com.follow.clash"
minSdkVersion 21
minSdkVersion 24
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
+7 -6
View File
@@ -1,9 +1,6 @@
<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" />
@@ -17,12 +14,15 @@
<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" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission"/>
<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" />
<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:extractNativeLibs="true"
android:networkSecurityConfig="@xml/network_security_config"
android:label="FlClash">
<activity
@@ -73,7 +73,8 @@
<service
android:name=".services.FlClashTileService"
android:exported="true"
android:icon="@drawable/tile_icon"
android:icon="@drawable/icon"
android:foregroundServiceType="specialUse"
android:label="FlClash"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
@@ -19,7 +19,6 @@ 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 ->
@@ -43,19 +42,27 @@ 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)
if (Build.VERSION.SDK_INT >= 34) {
val pendingIntent = PendingIntent.getActivity(
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
startActivityAndCollapse(pendingIntent)
} else {
PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startActivityAndCollapse(pendingIntent)
}else{
startActivityAndCollapse(intent)
}
}
@@ -1,10 +1,9 @@
package com.follow.clash.services
import android.annotation.SuppressLint
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
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
@@ -13,15 +12,13 @@ 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
@SuppressLint("WrongConstant")
class FlClashVpnService : VpnService() {
@@ -100,10 +97,10 @@ class FlClashVpnService : VpnService() {
}
private val notificationBuilder by lazy {
private val notificationBuilder: NotificationCompat.Builder by lazy {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
PendingIntent.getActivity(
this,
0,
@@ -119,43 +116,43 @@ class FlClashVpnService : VpnService() {
)
}
val icon = IconCompat.createWithResource(this, this.applicationInfo.icon)
with(NotificationCompat.Builder(this, CHANNEL)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setSmallIcon(icon)
}
setSmallIcon(R.drawable.ic_stat_name)
setContentTitle("FlClash")
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
setContentIntent(pendingIntent)
setCategory(NotificationCompat.CATEGORY_SERVICE)
priority = NotificationCompat.PRIORITY_LOW
priority = NotificationCompat.PRIORITY_MIN
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
}
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.UPSIDE_DOWN_CAKE) {
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
}else{
startForeground(notificationId, notification)
}
}
private fun stopForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
stopForeground(Service.STOP_FOREGROUND_REMOVE)
stopForeground(STOP_FOREGROUND_REMOVE)
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

+2 -2
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 {
+2 -2
View File
@@ -1,6 +1,6 @@
// ignore_for_file: constant_identifier_names
enum GroupType { Selector, URLTest, Fallback }
enum GroupType { Selector, URLTest, Fallback, LoadBalance, Relay }
enum GroupName { GLOBAL, Proxy, Auto, Fallback }
@@ -61,4 +61,4 @@ enum MessageType { log, tun, delay, process, now }
enum RecoveryOption {
all,
onlyProfiles,
}
}
+248 -138
View File
@@ -8,6 +8,13 @@ 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});
@@ -83,137 +90,64 @@ class _AccessFragmentState extends State<AccessFragment> {
);
}
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 _buildSearchButton(List<Package> packages) {
return IconButton(
tooltip: appLocalizations.search,
onPressed: () {
showSearch(
context: context,
delegate: AccessControlSearchDelegate(
packages: packages,
),
).then((_) => {setState(() {})});
},
icon: const Icon(Icons.search),
);
}
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 _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 _buildPackageList() {
return ValueListenableBuilder(
@@ -252,14 +186,11 @@ class _AccessFragmentState extends State<AccessFragment> {
accessControlMode == AccessControlMode.acceptSelected
? acceptPackages
: rejectPackages;
final currentList =
accessControlMode == AccessControlMode.acceptSelected
? accessControl.acceptList
: accessControl.rejectList;
final currentList = accessControl.currentList;
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();
@@ -272,11 +203,82 @@ class _AccessFragmentState extends State<AccessFragment> {
status: !isAccessControl,
child: Column(
children: [
_actionHeader(
isAccessControl: isAccessControl,
valueList: valueList,
describe: describe,
packageNameList: packageNameList,
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()),
],
),
],
),
),
),
Expanded(
flex: 1,
@@ -429,3 +431,111 @@ 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 = 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) {
final packages = _results;
return _packageList(packages);
}
@override
Widget buildSuggestions(BuildContext context) {
return _packageList(packages);
}
}
@@ -38,6 +38,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
} else {
timeoutNotifier.value = false;
}
_preIsStart = isStart;
ipInfoNotifier.value = ipInfo;
}
+49 -23
View File
@@ -17,9 +17,16 @@ enum ProfileActions {
delete,
}
class ProfilesFragment extends StatelessWidget {
class ProfilesFragment extends StatefulWidget {
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,
@@ -41,7 +48,7 @@ class ProfilesFragment extends StatelessWidget {
}
}
_initScaffoldState(BuildContext context) {
_initScaffoldState() {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
final commonScaffoldState =
@@ -78,7 +85,7 @@ class ProfilesFragment extends StatelessWidget {
selector: (_, appState) => appState.currentLabel == 'profiles',
builder: (_, isCurrent, child) {
if (isCurrent) {
_initScaffoldState(context);
_initScaffoldState();
}
return child!;
},
@@ -98,27 +105,46 @@ class ProfilesFragment extends StatelessWidget {
final isMobile = state.viewMode == ViewMode.mobile;
return Align(
alignment: Alignment.topCenter,
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,
),
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,
),
),
],
),
],
);
},
),
),
);
+8 -3
View File
@@ -199,10 +199,15 @@ 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: proxy.name, value: 0),
Delay(
name: proxyName,
value: 0,
),
);
clashCore.getDelay(proxy.name).then((delay) {
clashCore.getDelay(proxyName).then((delay) {
globalState.appController.setDelay(delay);
});
}
@@ -434,7 +439,7 @@ class _DelayTestButtonContainerState extends State<DelayTestButtonContainer>
_controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 600,
milliseconds: 200,
),
);
_scale = Tween<double>(
+2 -1
View File
@@ -160,5 +160,6 @@
"checking": "Checking...",
"country": "Country",
"checkError": "Check error",
"ipCheckTimeout": "Ip check timeout"
"ipCheckTimeout": "Ip check timeout",
"search": "Search"
}
+2 -1
View File
@@ -160,5 +160,6 @@
"checking": "检测中...",
"country": "区域",
"checkError": "检测失败",
"ipCheckTimeout": "Ip检测超时"
"ipCheckTimeout": "Ip检测超时",
"search": "搜索"
}
+1
View File
@@ -212,6 +212,7 @@ 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"),
+1
View File
@@ -171,6 +171,7 @@ 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("设置"),
+10
View File
@@ -1669,6 +1669,16 @@ class AppLocalizations {
args: [],
);
}
/// `Search`
String get search {
return Intl.message(
'Search',
name: 'search',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
+1 -1
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 ?? '*'})";
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ class Tun with _$Tun {
const factory Tun({
@Default(false) bool enable,
@Default(appName) String device,
@Default(TunStack.mixed) TunStack stack,
@Default(TunStack.gvisor) TunStack stack,
@JsonKey(name: "dns-hijack") @Default(["any:53"]) List<String> dnsHijack,
}) = _Tun;
+1
View File
@@ -8,6 +8,7 @@ import '../common/common.dart';
import 'models.dart';
part 'generated/config.g.dart';
part 'generated/config.freezed.dart';
@freezed
@@ -135,7 +135,7 @@ class _$TunImpl implements _Tun {
const _$TunImpl(
{this.enable = false,
this.device = appName,
this.stack = TunStack.mixed,
this.stack = TunStack.gvisor,
@JsonKey(name: "dns-hijack")
final List<String> dnsHijack = const ["any:53"]})
: _dnsHijack = dnsHijack;
+1 -1
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.mixed,
TunStack.gvisor,
dnsHijack: (json['dns-hijack'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
+2
View File
@@ -28,6 +28,8 @@ const _$GroupTypeEnumMap = {
GroupType.Selector: 'Selector',
GroupType.URLTest: 'URLTest',
GroupType.Fallback: 'Fallback',
GroupType.LoadBalance: 'LoadBalance',
GroupType.Relay: 'Relay',
};
_$ProxyImpl _$$ProxyImplFromJson(Map<String, dynamic> json) => _$ProxyImpl(
-1
View File
@@ -122,7 +122,6 @@ class CommonScaffoldState extends State<CommonScaffold> {
return floatingActionButton ?? Container();
},
),
floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Stack(
+146 -146
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none'
version: 0.8.16
version: 0.8.17
environment:
sdk: '>=3.1.0 <4.0.0'
+10 -7
View File
@@ -12,10 +12,7 @@ enum PlatformType {
macos,
}
enum Arch {
amd64,
arm64,
}
enum Arch { amd64, arm64, arm }
class BuildLibItem {
PlatformType platform;
@@ -64,6 +61,11 @@ class Build {
arch: Arch.amd64,
archName: 'amd64',
),
BuildLibItem(
platform: PlatformType.android,
arch: Arch.arm,
archName: 'armeabi-v7a',
),
BuildLibItem(
platform: PlatformType.android,
arch: Arch.arm64,
@@ -334,7 +336,7 @@ class BuildCommand extends Command {
final archName = argResults?['arch'];
final currentArches =
arches.where((element) => element.name == archName).toList();
final arch = currentArches.isEmpty ? null : arches.first;
final arch = currentArches.isEmpty ? null : currentArches.first;
await _buildLib(arch);
if (build != "all") {
return;
@@ -357,10 +359,11 @@ 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.amd64, Arch.arm64];
final defaultArches = [Arch.arm, Arch.arm64, Arch.amd64];
final defaultTargets = defaultArches
.where((element) => arch == null ? true : element == arch)
.map((e) => targetMap[e])
-1
View File
@@ -1,5 +1,4 @@
// ignore_for_file: avoid_print
void main() async {
String input = """
您