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
This commit is contained in:
chen08209
2024-06-08 22:51:58 +08:00
parent c65746709d
commit f4c3b06cd5
31 changed files with 518 additions and 360 deletions

View File

@@ -82,7 +82,7 @@ jobs:
upload-release:
if: ${{ !endsWith(github.ref, '-debug') }}
if: startsWith(github.ref, 'refs/tags/v') && github.base_ref == 'main'
permissions: write-all
needs: [ build ]
runs-on: ubuntu-latest

View File

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

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>

View File

@@ -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)
}
}

View File

@@ -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

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 @@
// ignore_for_file: constant_identifier_names
enum GroupType { Selector, URLTest, Fallback }
enum GroupType { Selector, URLTest, Fallback, LoadBalance, Relay }
enum GroupName { GLOBAL, Proxy, Auto, Fallback }

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);
}
}

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,
),
),
],
),
],
);
},
),
),
);

View File

@@ -434,7 +434,7 @@ class _DelayTestButtonContainerState extends State<DelayTestButtonContainer>
_controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 600,
milliseconds: 200,
),
);
_scale = Tween<double>(

View File

@@ -160,5 +160,6 @@
"checking": "Checking...",
"country": "Country",
"checkError": "Check error",
"ipCheckTimeout": "Ip check timeout"
"ipCheckTimeout": "Ip check timeout",
"search": "Search"
}

View File

@@ -160,5 +160,6 @@
"checking": "检测中...",
"country": "区域",
"checkError": "检测失败",
"ipCheckTimeout": "Ip检测超时"
"ipCheckTimeout": "Ip检测超时",
"search": "搜索"
}

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"),

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("设置"),

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> {

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;

View File

@@ -8,6 +8,7 @@ import '../common/common.dart';
import 'models.dart';
part 'generated/config.g.dart';
part 'generated/config.freezed.dart';
@freezed

View File

@@ -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;

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() ??

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(

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.16
version: 0.8.17
environment:
sdk: '>=3.1.0 <4.0.0'

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])