Compare commits

...

2 Commits

Author SHA1 Message Date
chen08209
0389b6eb29 Optimize proxy
Optimize delayed sorting performance

Add expansion panel proxies page

Support to adjust the proxy card size

Support to adjust proxies columns number
2024-06-26 16:04:30 +08:00
chen08209
8f22cbf746 Fix autoRun show issues
Fix Android 10 issues

Optimize ip show
2024-06-23 03:07:52 +08:00
35 changed files with 1064 additions and 536 deletions

View File

@@ -64,7 +64,6 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
toast!!.show()
}
@RequiresApi(Build.VERSION_CODES.Q)
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"moveTaskToBack" -> {
@@ -151,7 +150,6 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
val message = call.argument<String>("message")
tip(message)
result.success(true)
}
else -> {

42
core/platform/limit.go Normal file
View File

@@ -0,0 +1,42 @@
//go:build android
package platform
import "syscall"
var nullFd int
var maxFdCount int
func init() {
fd, err := syscall.Open("/dev/null", syscall.O_WRONLY, 0644)
if err != nil {
panic(err.Error())
}
nullFd = fd
var limit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
maxFdCount = 1024
} else {
maxFdCount = int(limit.Cur)
}
maxFdCount = maxFdCount / 4 * 3
}
func ShouldBlockConnection() bool {
fd, err := syscall.Dup(nullFd)
if err != nil {
return true
}
_ = syscall.Close(fd)
if fd > maxFdCount {
return true
}
return false
}

View File

@@ -4,7 +4,9 @@ package main
import "C"
import (
"core/platform"
t "core/tun"
"errors"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/log"
"golang.org/x/sync/semaphore"
@@ -59,8 +61,13 @@ func stopTun() {
}()
}
var errBlocked = errors.New("blocked")
func init() {
dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error {
if platform.ShouldBlockConnection() {
return errBlocked
}
return conn.Control(func(fd uintptr) {
if tun != nil {
tun.MarkSocket(int(fd))

View File

@@ -130,7 +130,6 @@ class ApplicationState extends State<Application> {
httpTimeoutDuration,
(timer) async {
await globalState.appController.updateGroups();
globalState.appController.appState.sortNum++;
},
);
}

View File

@@ -7,6 +7,7 @@ const coreName = "clash.meta";
const packageName = "FlClash";
const httpTimeoutDuration = Duration(milliseconds: 5000);
const moreDuration = Duration(milliseconds: 100);
const animateDuration = Duration(milliseconds: 100);
const defaultUpdateDuration = Duration(days: 1);
const mmdbFileName = "geoip.metadb";
const geoSiteFileName = "GeoSite.dat";

View File

@@ -2,20 +2,13 @@ import 'package:flutter/material.dart';
import 'color.dart';
extension TextStyleExtension on TextStyle {
toLight() {
return copyWith(color: color?.toLight());
}
TextStyle get toLight => copyWith(color: color?.toLight());
toLighter() {
return copyWith(color: color?.toLighter());
}
TextStyle get toLighter => copyWith(color: color?.toLighter());
TextStyle get toSoftBold => copyWith(fontWeight: FontWeight.w500);
toSoftBold() {
return copyWith(fontWeight: FontWeight.w500);
}
TextStyle get toBold => copyWith(fontWeight: FontWeight.bold);
toBold() {
return copyWith(fontWeight: FontWeight.bold);
}
}
TextStyle get toMinus => copyWith(fontSize: fontSize! - 1);
}

View File

@@ -1,7 +1,6 @@
import 'dart:async';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@@ -267,6 +266,7 @@ class AppController {
}
init() async {
updateLogStatus();
if (!config.silentLaunch) {
window?.show();
}
@@ -290,14 +290,13 @@ class AppController {
}
afterInit() async {
if (config.autoRun) {
await proxyManager.updateStartTime();
if (proxyManager.isStart) {
await updateSystemProxy(true);
} else {
await proxyManager.updateStartTime();
await updateSystemProxy(proxyManager.isStart);
await updateSystemProxy(config.autoRun);
}
autoUpdateProfiles();
updateLogStatus();
autoCheckUpdate();
}
@@ -406,9 +405,55 @@ class AppController {
addProfileFormURL(url);
}
int get columns =>
globalState.getColumns(appState.viewMode, config.proxiesColumns);
changeColumns() {
config.proxiesColumns = globalState.getColumns(
appState.viewMode,
columns - 1,
);
}
updateViewWidth(double width) {
WidgetsBinding.instance.addPostFrameCallback((_) {
appState.viewWidth = width;
});
}
List<Proxy> _sortOfName(List<Proxy> proxies) {
return List.of(proxies)
..sort(
(a, b) => other.sortByChar(a.name, b.name),
);
}
List<Proxy> _sortOfDelay(List<Proxy> proxies) {
return proxies = List.of(proxies)
..sort(
(a, b) {
final aDelay = appState.getDelay(a.name);
final bDelay = appState.getDelay(b.name);
if (aDelay == null && bDelay == null) {
return 0;
}
if (aDelay == null || aDelay == -1) {
return 1;
}
if (bDelay == null || bDelay == -1) {
return -1;
}
return aDelay.compareTo(bDelay);
},
);
}
List<Proxy> getSortProxies(List<Proxy> proxies){
return switch(config.proxiesSortType){
ProxiesSortType.none => proxies,
ProxiesSortType.delay => _sortOfDelay(proxies),
ProxiesSortType.name =>_sortOfName(proxies),
};
}
}

View File

@@ -65,7 +65,10 @@ enum RecoveryOption {
onlyProfiles,
}
enum ChipType {
action,
delete,
}
enum ChipType { action, delete }
enum CommonCardType { plain, filled }
enum ProxiesType { tab, expansion }
enum ProxyCardType { expand, shrink }

View File

@@ -191,7 +191,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
builder: (_, ipv6, __) {
return ListItem.switchItem(
leading: const Icon(Icons.water_outlined),
title: const Text("Ipv6"),
title: const Text("IPv6"),
subtitle: Text(appLocalizations.ipv6Desc),
delegate: SwitchDelegate(
value: ipv6,

View File

@@ -13,6 +13,7 @@ class CoreInfo extends StatelessWidget {
selector: (_, appState) => appState.versionInfo,
builder: (_, versionInfo, __) {
return CommonCard(
onPressed: () {},
info: Info(
label: appLocalizations.coreInfo,
iconData: Icons.memory,
@@ -31,7 +32,7 @@ class CoreInfo extends StatelessWidget {
style: context
.textTheme
.titleMedium
?.toSoftBold(),
?.toSoftBold,
),
),
const SizedBox(
@@ -44,7 +45,7 @@ class CoreInfo extends StatelessWidget {
style: context
.textTheme
.titleLarge
?.toSoftBold(),
?.toSoftBold,
),
),
],

View File

@@ -19,7 +19,7 @@ class _IntranetIpState extends State<IntranetIp> {
List<NetworkInterface> interfaces = await NetworkInterface.list();
for (final interface in interfaces) {
for (final address in interface.addresses) {
if (address.type == InternetAddressType.IPv4 && !address.isLoopback) {
if (!address.isLoopback) {
return address.address;
}
}
@@ -48,9 +48,12 @@ class _IntranetIpState extends State<IntranetIp> {
label: appLocalizations.intranetIp,
iconData: Icons.devices,
),
onPressed: (){
},
child: Container(
padding: const EdgeInsets.all(16).copyWith(top: 0),
height: globalState.appController.measure.titleLargeHeight + 16,
height: globalState.appController.measure.titleLargeHeight + 24 - 1,
child: ValueListenableBuilder(
valueListenable: ipNotifier,
builder: (_, value, __) {
@@ -65,7 +68,7 @@ class _IntranetIpState extends State<IntranetIp> {
child: TooltipText(
text: Text(
value,
style: context.textTheme.titleLarge?.toSoftBold(),
style: context.textTheme.titleLarge?.toSoftBold.toMinus,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -74,8 +77,11 @@ class _IntranetIpState extends State<IntranetIp> {
],
)
: const Padding(
padding: EdgeInsets.all(4),
child: CircularProgressIndicator(),
padding: EdgeInsets.all(2),
child: AspectRatio(
aspectRatio: 1,
child: CircularProgressIndicator(),
),
),
);
},

View File

@@ -78,6 +78,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
valueListenable: ipInfoNotifier,
builder: (_, ipInfo, __) {
return CommonCard(
onPressed: () {},
child: Column(
children: [
Flexible(
@@ -134,8 +135,9 @@ class _NetworkDetectionState extends State<NetworkDetection> {
),
),
Container(
height:
globalState.appController.measure.titleLargeHeight + 24,
height: globalState.appController.measure.titleLargeHeight +
24 -
1,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.all(16).copyWith(top: 0),
child: FadeBox(
@@ -150,7 +152,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
text: Text(
ipInfo.ip,
style: context.textTheme.titleLarge
?.toSoftBold(),
?.toSoftBold.toMinus,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -164,9 +166,10 @@ class _NetworkDetectionState extends State<NetworkDetection> {
if (timeout) {
return Text(
"timeout",
style: context.textTheme.titleMedium
style: context.textTheme.titleLarge
?.copyWith(color: Colors.red)
.toSoftBold(),
.toSoftBold
.toMinus,
maxLines: 1,
overflow: TextOverflow.ellipsis,
);

View File

@@ -47,8 +47,8 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
final showValue = value.showValue;
final showUnit = "${value.showUnit}/s";
final titleLargeSoftBold =
Theme.of(context).textTheme.titleLarge?.toSoftBold();
final bodyMedium = Theme.of(context).textTheme.bodySmall?.toLight();
Theme.of(context).textTheme.titleLarge?.toSoftBold;
final bodyMedium = Theme.of(context).textTheme.bodySmall?.toLight;
final valueText = Text(
showValue,
style: titleLargeSoftBold,
@@ -75,7 +75,7 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
Flexible(
child: Text(
label,
style: Theme.of(context).textTheme.titleSmall?.toSoftBold(),
style: Theme.of(context).textTheme.titleSmall?.toSoftBold,
),
),
],
@@ -111,6 +111,7 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
@override
Widget build(BuildContext context) {
return CommonCard(
onPressed: () {},
info: Info(
label: appLocalizations.networkSpeed,
iconData: Icons.speed,

View File

@@ -33,6 +33,7 @@ class OutboundMode extends StatelessWidget {
selector: (_, clashConfig) => clashConfig.mode,
builder: (_, mode, __) {
return CommonCard(
onPressed: () {},
info: Info(
label: appLocalizations.outboundMode,
iconData: Icons.call_split,
@@ -67,7 +68,7 @@ class OutboundMode extends StatelessWidget {
.of(context)
.textTheme
.titleMedium
?.toSoftBold(),
?.toSoftBold,
),
),
],

View File

@@ -13,19 +13,17 @@ class StartButton extends StatefulWidget {
class _StartButtonState extends State<StartButton>
with SingleTickerProviderStateMixin {
bool isStart = false;
bool isInit = false;
late AnimationController _controller;
bool isStart = false;
@override
void initState() {
isStart = globalState.appController.appState.isStart;
super.initState();
_controller = AnimationController(
vsync: this,
value: isStart ? 1 : 0,
value: 0,
duration: const Duration(milliseconds: 200),
);
super.initState();
}
@override
@@ -35,9 +33,12 @@ class _StartButtonState extends State<StartButton>
}
handleSwitchStart() {
isStart = !isStart;
updateController();
updateSystemProxy();
final appController = globalState.appController;
if (isStart == appController.appState.isStart) {
isStart = !isStart;
updateController();
appController.updateSystemProxy(isStart);
}
}
updateController() {
@@ -48,11 +49,18 @@ class _StartButtonState extends State<StartButton>
}
}
updateSystemProxy() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final appController = globalState.appController;
await appController.updateSystemProxy(isStart);
});
Widget _updateControllerContainer(Widget child) {
return Selector<AppState, bool>(
selector: (_, appState) => appState.isStart,
builder: (_, isStart, child) {
if(isStart != this.isStart){
this.isStart = isStart;
updateController();
}
return child!;
},
child: child,
);
}
@override
@@ -72,8 +80,7 @@ class _StartButtonState extends State<StartButton>
other.getTimeDifference(
DateTime.now(),
),
style:
Theme.of(context).textTheme.titleMedium?.toSoftBold(),
style: Theme.of(context).textTheme.titleMedium?.toSoftBold,
),
)
.width +
@@ -119,24 +126,14 @@ class _StartButtonState extends State<StartButton>
child: child,
);
},
child: Selector<AppState, bool>(
selector: (_, appState) => appState.runTime != null,
builder: (_, isRun, child) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (isStart != isRun) {
isStart = isRun;
updateController();
}
});
return child!;
},
child: Selector<AppState, int?>(
child: _updateControllerContainer(
Selector<AppState, int?>(
selector: (_, appState) => appState.runTime,
builder: (_, int? value, __) {
final text = other.getTimeText(value);
return Text(
text,
style: Theme.of(context).textTheme.titleMedium?.toSoftBold(),
style: Theme.of(context).textTheme.titleMedium?.toSoftBold,
);
},
),

View File

@@ -42,7 +42,7 @@ class TrafficUsage extends StatelessWidget {
),
Text(
trafficValue.showUnit,
style: context.textTheme.labelMedium?.toLight(),
style: context.textTheme.labelMedium?.toLight,
),
],
);
@@ -51,6 +51,7 @@ class TrafficUsage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CommonCard(
onPressed: () {},
info: Info(
label: appLocalizations.trafficUsage,
iconData: Icons.data_saver_off,

View File

@@ -253,7 +253,7 @@ class _ProfileItemState extends State<ProfileItem> {
),
Text(
profile.lastUpdateDate?.lastUpdateTimeDesc ?? '',
style: textTheme.labelMedium?.toLight(),
style: textTheme.labelMedium?.toLight,
),
],
),
@@ -284,7 +284,7 @@ class _ProfileItemState extends State<ProfileItem> {
),
Text(
"$useShow / $totalShow",
style: textTheme.labelMedium?.toLight(),
style: textTheme.labelMedium?.toLight,
),
const SizedBox(
height: 2,
@@ -293,14 +293,14 @@ class _ProfileItemState extends State<ProfileItem> {
children: [
Text(
appLocalizations.expirationTime,
style: textTheme.labelMedium?.toLighter(),
style: textTheme.labelMedium?.toLighter,
),
const SizedBox(
width: 4,
),
Text(
expireShow,
style: textTheme.labelMedium?.toLighter(),
style: textTheme.labelMedium?.toLighter,
),
],
)
@@ -444,7 +444,7 @@ class _ProfileItemState extends State<ProfileItem> {
),
CommonPopupMenuItem(
action: ProfileActions.view,
label: "查看",
label: appLocalizations.view,
iconData: Icons.visibility,
),
],

File diff suppressed because it is too large Load Diff

View File

@@ -111,7 +111,7 @@
"noMoreInfoDesc": "No more info",
"profileParseErrorDesc": "profile parse error",
"proxyPort": "ProxyPort",
"proxyPortDesc": "Set the clash listening port",
"proxyPortDesc": "Set the Clash listening port",
"port": "Port",
"logLevel": "LogLevel",
"show": "Show",
@@ -166,8 +166,8 @@
"allowBypass": "Allow applications to bypass VPN",
"allowBypassDesc": "Some apps can bypass VPN when turned on",
"externalController": "ExternalController",
"externalControllerDesc": "Once enabled, the clash kernel can be controlled on port 9090",
"ipv6Desc": "When turned on it will be able to receive ipv6 traffic",
"externalControllerDesc": "Once enabled, the Clash kernel can be controlled on port 9090",
"ipv6Desc": "When turned on it will be able to receive IPv6 traffic",
"app": "App",
"general": "General",
"systemProxyDesc": "Attach HTTP proxy to VpnService",
@@ -187,7 +187,7 @@
"connections": "Connections",
"connectionsDesc": "View current connection",
"nullRequestsDesc": "No requests",
"nullConnectionsDesc": "No Connections",
"nullConnectionsDesc": "No connections",
"intranetIp": "Intranet IP",
"view": "View",
"cut": "Cut",

View File

@@ -111,7 +111,7 @@
"noMoreInfoDesc": "暂无更多信息",
"profileParseErrorDesc": "配置文件解析错误",
"proxyPort": "代理端口",
"proxyPortDesc": "设置clash监听端口",
"proxyPortDesc": "设置Clash监听端口",
"port": "端口",
"logLevel": "日志等级",
"show": "显示",
@@ -163,11 +163,11 @@
"checkError": "检测失败",
"ipCheckTimeout": "Ip检测超时",
"search": "搜索",
"allowBypass": "允许应用绕过vpn",
"allowBypass": "允许应用绕过VPN",
"allowBypassDesc": "开启后部分应用可绕过VPN",
"externalController": "外部控制器",
"externalControllerDesc": "开启后将可以通过9090端口控制clash内核",
"ipv6Desc": "开启后将可以接收ipv6流量",
"externalControllerDesc": "开启后将可以通过9090端口控制Clash内核",
"ipv6Desc": "开启后将可以接收IPv6流量",
"app": "应用",
"general": "基础",
"systemProxyDesc": "为VpnService附加HTTP代理",

View File

@@ -127,7 +127,7 @@ class MessageLookup extends MessageLookupByLibrary {
"externalController":
MessageLookupByLibrary.simpleMessage("ExternalController"),
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
"Once enabled, the clash kernel can be controlled on port 9090"),
"Once enabled, the Clash kernel can be controlled on port 9090"),
"externalResources":
MessageLookupByLibrary.simpleMessage("External resources"),
"file": MessageLookupByLibrary.simpleMessage("File"),
@@ -156,7 +156,7 @@ class MessageLookup extends MessageLookupByLibrary {
"ipCheckTimeout":
MessageLookupByLibrary.simpleMessage("Ip check timeout"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
"When turned on it will be able to receive ipv6 traffic"),
"When turned on it will be able to receive IPv6 traffic"),
"just": MessageLookupByLibrary.simpleMessage("Just"),
"language": MessageLookupByLibrary.simpleMessage("Language"),
"light": MessageLookupByLibrary.simpleMessage("Light"),
@@ -186,7 +186,7 @@ class MessageLookup extends MessageLookupByLibrary {
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
"The current proxy group cannot be selected."),
"nullConnectionsDesc":
MessageLookupByLibrary.simpleMessage("No Connections"),
MessageLookupByLibrary.simpleMessage("No connections"),
"nullCoreInfoDesc":
MessageLookupByLibrary.simpleMessage("Unable to obtain core info"),
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("No logs"),
@@ -230,7 +230,7 @@ class MessageLookup extends MessageLookupByLibrary {
"proxies": MessageLookupByLibrary.simpleMessage("Proxies"),
"proxyPort": MessageLookupByLibrary.simpleMessage("ProxyPort"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage(
"Set the clash listening port"),
"Set the Clash listening port"),
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage(
"Scan QR code to obtain profile"),

View File

@@ -36,7 +36,7 @@ class MessageLookup extends MessageLookupByLibrary {
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
"ago": MessageLookupByLibrary.simpleMessage(""),
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过vpn"),
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过VPN"),
"allowBypassDesc":
MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"),
"allowLan": MessageLookupByLibrary.simpleMessage("局域网代理"),
@@ -104,7 +104,7 @@ class MessageLookup extends MessageLookupByLibrary {
"expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"),
"externalController": MessageLookupByLibrary.simpleMessage("外部控制器"),
"externalControllerDesc":
MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制clash内核"),
MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制Clash内核"),
"externalResources": MessageLookupByLibrary.simpleMessage("外部资源"),
"file": MessageLookupByLibrary.simpleMessage("文件"),
"fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
@@ -125,7 +125,7 @@ class MessageLookup extends MessageLookupByLibrary {
"init": MessageLookupByLibrary.simpleMessage("初始化"),
"intranetIp": MessageLookupByLibrary.simpleMessage("内网 IP"),
"ipCheckTimeout": MessageLookupByLibrary.simpleMessage("Ip检测超时"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收ipv6流量"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
"language": MessageLookupByLibrary.simpleMessage("语言"),
"light": MessageLookupByLibrary.simpleMessage("浅色"),
@@ -186,7 +186,7 @@ class MessageLookup extends MessageLookupByLibrary {
"project": MessageLookupByLibrary.simpleMessage("项目"),
"proxies": MessageLookupByLibrary.simpleMessage("代理"),
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置clash监听端口"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),

View File

@@ -1170,10 +1170,10 @@ class AppLocalizations {
);
}
/// `Set the clash listening port`
/// `Set the Clash listening port`
String get proxyPortDesc {
return Intl.message(
'Set the clash listening port',
'Set the Clash listening port',
name: 'proxyPortDesc',
desc: '',
args: [],
@@ -1720,20 +1720,20 @@ class AppLocalizations {
);
}
/// `Once enabled, the clash kernel can be controlled on port 9090`
/// `Once enabled, the Clash kernel can be controlled on port 9090`
String get externalControllerDesc {
return Intl.message(
'Once enabled, the clash kernel can be controlled on port 9090',
'Once enabled, the Clash kernel can be controlled on port 9090',
name: 'externalControllerDesc',
desc: '',
args: [],
);
}
/// `When turned on it will be able to receive ipv6 traffic`
/// `When turned on it will be able to receive IPv6 traffic`
String get ipv6Desc {
return Intl.message(
'When turned on it will be able to receive ipv6 traffic',
'When turned on it will be able to receive IPv6 traffic',
name: 'ipv6Desc',
desc: '',
args: [],
@@ -1930,10 +1930,10 @@ class AppLocalizations {
);
}
/// `No Connections`
/// `No connections`
String get nullConnectionsDesc {
return Intl.message(
'No Connections',
'No connections',
name: 'nullConnectionsDesc',
desc: '',
args: [],

View File

@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -56,6 +57,9 @@ class Config extends ChangeNotifier {
bool _allowBypass;
bool _systemProxy;
DAV? _dav;
ProxiesType _proxiesType;
ProxyCardType _proxyCardType;
int _proxiesColumns;
Config()
: _profiles = [],
@@ -73,7 +77,10 @@ class Config extends ChangeNotifier {
_systemProxy = true,
_accessControl = const AccessControl(),
_isAnimateToPage = true,
_allowBypass = true;
_allowBypass = true,
_proxyCardType = ProxyCardType.expand,
_proxiesType = ProxiesType.tab,
_proxiesColumns = 2;
deleteProfileById(String id) {
_profiles = profiles.where((element) => element.id != id).toList();
@@ -150,6 +157,19 @@ class Config extends ChangeNotifier {
String? get currentGroupName => currentProfile?.currentGroupName;
Set<String> get currentUnfoldSet => currentProfile?.unfoldSet ?? {};
updateCurrentUnfoldSet(Set<String> value) {
if (!const SetEquality<String>().equals(currentUnfoldSet, value)) {
_setProfile(
currentProfile!.copyWith(
unfoldSet: value,
),
);
notifyListeners();
}
}
updateCurrentGroupName(String groupName) {
if (currentProfile != null &&
currentProfile!.currentGroupName != groupName) {
@@ -364,6 +384,36 @@ class Config extends ChangeNotifier {
}
}
@JsonKey(defaultValue: ProxiesType.tab)
ProxiesType get proxiesType => _proxiesType;
set proxiesType(ProxiesType value) {
if (_proxiesType != value) {
_proxiesType = value;
notifyListeners();
}
}
@JsonKey(defaultValue: ProxyCardType.expand)
ProxyCardType get proxyCardType => _proxyCardType;
set proxyCardType(ProxyCardType value) {
if (_proxyCardType != value) {
_proxyCardType = value;
notifyListeners();
}
}
@JsonKey(defaultValue: 2)
int get proxiesColumns => _proxiesColumns;
set proxiesColumns(int value) {
if (_proxiesColumns != value) {
_proxiesColumns = value;
notifyListeners();
}
}
update([
Config? config,
RecoveryOption recoveryOptions = RecoveryOption.all,
@@ -383,6 +433,7 @@ class Config extends ChangeNotifier {
_autoLaunch = config._autoLaunch;
_silentLaunch = config._silentLaunch;
_autoRun = config._autoRun;
_proxiesType = config._proxiesType;
_openLog = config._openLog;
_themeMode = config._themeMode;
_locale = config._locale;

View File

@@ -34,7 +34,14 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
..isCompatible = json['isCompatible'] as bool? ?? true
..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true
..allowBypass = json['allowBypass'] as bool? ?? true
..systemProxy = json['systemProxy'] as bool? ?? true;
..systemProxy = json['systemProxy'] as bool? ?? true
..proxiesType =
$enumDecodeNullable(_$ProxiesTypeEnumMap, json['proxiesType']) ??
ProxiesType.tab
..proxyCardType =
$enumDecodeNullable(_$ProxyCardTypeEnumMap, json['proxyCardType']) ??
ProxyCardType.expand
..proxiesColumns = (json['proxiesColumns'] as num?)?.toInt() ?? 2;
Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'profiles': instance.profiles,
@@ -56,6 +63,9 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'autoCheckUpdate': instance.autoCheckUpdate,
'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy,
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
'proxiesColumns': instance.proxiesColumns,
};
const _$ThemeModeEnumMap = {
@@ -70,6 +80,16 @@ const _$ProxiesSortTypeEnumMap = {
ProxiesSortType.name: 'name',
};
const _$ProxiesTypeEnumMap = {
ProxiesType.tab: 'tab',
ProxiesType.expansion: 'expansion',
};
const _$ProxyCardTypeEnumMap = {
ProxyCardType.expand: 'expand',
ProxyCardType.shrink: 'shrink',
};
_$AccessControlImpl _$$AccessControlImplFromJson(Map<String, dynamic> json) =>
_$AccessControlImpl(
mode: $enumDecodeNullable(_$AccessControlModeEnumMap, json['mode']) ??

View File

@@ -222,6 +222,7 @@ mixin _$Profile {
UserInfo? get userInfo => throw _privateConstructorUsedError;
bool get autoUpdate => throw _privateConstructorUsedError;
Map<String, String> get selectedMap => throw _privateConstructorUsedError;
Set<String> get unfoldSet => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -242,7 +243,8 @@ abstract class $ProfileCopyWith<$Res> {
Duration autoUpdateDuration,
UserInfo? userInfo,
bool autoUpdate,
Map<String, String> selectedMap});
Map<String, String> selectedMap,
Set<String> unfoldSet});
$UserInfoCopyWith<$Res>? get userInfo;
}
@@ -269,6 +271,7 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
Object? userInfo = freezed,
Object? autoUpdate = null,
Object? selectedMap = null,
Object? unfoldSet = null,
}) {
return _then(_value.copyWith(
id: null == id
@@ -307,6 +310,10 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
? _value.selectedMap
: selectedMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>,
unfoldSet: null == unfoldSet
? _value.unfoldSet
: unfoldSet // ignore: cast_nullable_to_non_nullable
as Set<String>,
) as $Val);
}
@@ -339,7 +346,8 @@ abstract class _$$ProfileImplCopyWith<$Res> implements $ProfileCopyWith<$Res> {
Duration autoUpdateDuration,
UserInfo? userInfo,
bool autoUpdate,
Map<String, String> selectedMap});
Map<String, String> selectedMap,
Set<String> unfoldSet});
@override
$UserInfoCopyWith<$Res>? get userInfo;
@@ -365,6 +373,7 @@ class __$$ProfileImplCopyWithImpl<$Res>
Object? userInfo = freezed,
Object? autoUpdate = null,
Object? selectedMap = null,
Object? unfoldSet = null,
}) {
return _then(_$ProfileImpl(
id: null == id
@@ -403,6 +412,10 @@ class __$$ProfileImplCopyWithImpl<$Res>
? _value._selectedMap
: selectedMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>,
unfoldSet: null == unfoldSet
? _value._unfoldSet
: unfoldSet // ignore: cast_nullable_to_non_nullable
as Set<String>,
));
}
}
@@ -419,8 +432,10 @@ class _$ProfileImpl implements _Profile {
required this.autoUpdateDuration,
this.userInfo,
this.autoUpdate = true,
final Map<String, String> selectedMap = const {}})
: _selectedMap = selectedMap;
final Map<String, String> selectedMap = const {},
final Set<String> unfoldSet = const {}})
: _selectedMap = selectedMap,
_unfoldSet = unfoldSet;
factory _$ProfileImpl.fromJson(Map<String, dynamic> json) =>
_$$ProfileImplFromJson(json);
@@ -452,9 +467,18 @@ class _$ProfileImpl implements _Profile {
return EqualUnmodifiableMapView(_selectedMap);
}
final Set<String> _unfoldSet;
@override
@JsonKey()
Set<String> get unfoldSet {
if (_unfoldSet is EqualUnmodifiableSetView) return _unfoldSet;
// ignore: implicit_dynamic_type
return EqualUnmodifiableSetView(_unfoldSet);
}
@override
String toString() {
return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap)';
return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap, unfoldSet: $unfoldSet)';
}
@override
@@ -476,7 +500,9 @@ class _$ProfileImpl implements _Profile {
(identical(other.autoUpdate, autoUpdate) ||
other.autoUpdate == autoUpdate) &&
const DeepCollectionEquality()
.equals(other._selectedMap, _selectedMap));
.equals(other._selectedMap, _selectedMap) &&
const DeepCollectionEquality()
.equals(other._unfoldSet, _unfoldSet));
}
@JsonKey(ignore: true)
@@ -491,7 +517,8 @@ class _$ProfileImpl implements _Profile {
autoUpdateDuration,
userInfo,
autoUpdate,
const DeepCollectionEquality().hash(_selectedMap));
const DeepCollectionEquality().hash(_selectedMap),
const DeepCollectionEquality().hash(_unfoldSet));
@JsonKey(ignore: true)
@override
@@ -517,7 +544,8 @@ abstract class _Profile implements Profile {
required final Duration autoUpdateDuration,
final UserInfo? userInfo,
final bool autoUpdate,
final Map<String, String> selectedMap}) = _$ProfileImpl;
final Map<String, String> selectedMap,
final Set<String> unfoldSet}) = _$ProfileImpl;
factory _Profile.fromJson(Map<String, dynamic> json) = _$ProfileImpl.fromJson;
@@ -540,6 +568,8 @@ abstract class _Profile implements Profile {
@override
Map<String, String> get selectedMap;
@override
Set<String> get unfoldSet;
@override
@JsonKey(ignore: true)
_$$ProfileImplCopyWith<_$ProfileImpl> get copyWith =>
throw _privateConstructorUsedError;

View File

@@ -41,6 +41,10 @@ _$ProfileImpl _$$ProfileImplFromJson(Map<String, dynamic> json) =>
(k, e) => MapEntry(k, e as String),
) ??
const {},
unfoldSet: (json['unfoldSet'] as List<dynamic>?)
?.map((e) => e as String)
.toSet() ??
const {},
);
Map<String, dynamic> _$$ProfileImplToJson(_$ProfileImpl instance) =>
@@ -54,4 +58,5 @@ Map<String, dynamic> _$$ProfileImplToJson(_$ProfileImpl instance) =>
'userInfo': instance.userInfo,
'autoUpdate': instance.autoUpdate,
'selectedMap': instance.selectedMap,
'unfoldSet': instance.unfoldSet.toList(),
};

View File

@@ -1730,39 +1730,37 @@ abstract class _ProxiesSelectorState implements ProxiesSelectorState {
}
/// @nodoc
mixin _$ProxiesTabViewSelectorState {
mixin _$ProxyGroupSelectorState {
ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError;
ProxyCardType get proxyCardType => throw _privateConstructorUsedError;
num get sortNum => throw _privateConstructorUsedError;
Group get group => throw _privateConstructorUsedError;
ViewMode get viewMode => throw _privateConstructorUsedError;
List<Proxy> get proxies => throw _privateConstructorUsedError;
int get columns => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ProxiesTabViewSelectorStateCopyWith<ProxiesTabViewSelectorState>
get copyWith => throw _privateConstructorUsedError;
$ProxyGroupSelectorStateCopyWith<ProxyGroupSelectorState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProxiesTabViewSelectorStateCopyWith<$Res> {
factory $ProxiesTabViewSelectorStateCopyWith(
ProxiesTabViewSelectorState value,
$Res Function(ProxiesTabViewSelectorState) then) =
_$ProxiesTabViewSelectorStateCopyWithImpl<$Res,
ProxiesTabViewSelectorState>;
abstract class $ProxyGroupSelectorStateCopyWith<$Res> {
factory $ProxyGroupSelectorStateCopyWith(ProxyGroupSelectorState value,
$Res Function(ProxyGroupSelectorState) then) =
_$ProxyGroupSelectorStateCopyWithImpl<$Res, ProxyGroupSelectorState>;
@useResult
$Res call(
{ProxiesSortType proxiesSortType,
ProxyCardType proxyCardType,
num sortNum,
Group group,
ViewMode viewMode});
$GroupCopyWith<$Res> get group;
List<Proxy> proxies,
int columns});
}
/// @nodoc
class _$ProxiesTabViewSelectorStateCopyWithImpl<$Res,
$Val extends ProxiesTabViewSelectorState>
implements $ProxiesTabViewSelectorStateCopyWith<$Res> {
_$ProxiesTabViewSelectorStateCopyWithImpl(this._value, this._then);
class _$ProxyGroupSelectorStateCopyWithImpl<$Res,
$Val extends ProxyGroupSelectorState>
implements $ProxyGroupSelectorStateCopyWith<$Res> {
_$ProxyGroupSelectorStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
@@ -1773,165 +1771,177 @@ class _$ProxiesTabViewSelectorStateCopyWithImpl<$Res,
@override
$Res call({
Object? proxiesSortType = null,
Object? proxyCardType = null,
Object? sortNum = null,
Object? group = null,
Object? viewMode = null,
Object? proxies = null,
Object? columns = null,
}) {
return _then(_value.copyWith(
proxiesSortType: null == proxiesSortType
? _value.proxiesSortType
: proxiesSortType // ignore: cast_nullable_to_non_nullable
as ProxiesSortType,
proxyCardType: null == proxyCardType
? _value.proxyCardType
: proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,
sortNum: null == sortNum
? _value.sortNum
: sortNum // ignore: cast_nullable_to_non_nullable
as num,
group: null == group
? _value.group
: group // ignore: cast_nullable_to_non_nullable
as Group,
viewMode: null == viewMode
? _value.viewMode
: viewMode // ignore: cast_nullable_to_non_nullable
as ViewMode,
proxies: null == proxies
? _value.proxies
: proxies // ignore: cast_nullable_to_non_nullable
as List<Proxy>,
columns: null == columns
? _value.columns
: columns // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$GroupCopyWith<$Res> get group {
return $GroupCopyWith<$Res>(_value.group, (value) {
return _then(_value.copyWith(group: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$ProxiesTabViewSelectorStateImplCopyWith<$Res>
implements $ProxiesTabViewSelectorStateCopyWith<$Res> {
factory _$$ProxiesTabViewSelectorStateImplCopyWith(
_$ProxiesTabViewSelectorStateImpl value,
$Res Function(_$ProxiesTabViewSelectorStateImpl) then) =
__$$ProxiesTabViewSelectorStateImplCopyWithImpl<$Res>;
abstract class _$$ProxyGroupSelectorStateImplCopyWith<$Res>
implements $ProxyGroupSelectorStateCopyWith<$Res> {
factory _$$ProxyGroupSelectorStateImplCopyWith(
_$ProxyGroupSelectorStateImpl value,
$Res Function(_$ProxyGroupSelectorStateImpl) then) =
__$$ProxyGroupSelectorStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{ProxiesSortType proxiesSortType,
ProxyCardType proxyCardType,
num sortNum,
Group group,
ViewMode viewMode});
@override
$GroupCopyWith<$Res> get group;
List<Proxy> proxies,
int columns});
}
/// @nodoc
class __$$ProxiesTabViewSelectorStateImplCopyWithImpl<$Res>
extends _$ProxiesTabViewSelectorStateCopyWithImpl<$Res,
_$ProxiesTabViewSelectorStateImpl>
implements _$$ProxiesTabViewSelectorStateImplCopyWith<$Res> {
__$$ProxiesTabViewSelectorStateImplCopyWithImpl(
_$ProxiesTabViewSelectorStateImpl _value,
$Res Function(_$ProxiesTabViewSelectorStateImpl) _then)
class __$$ProxyGroupSelectorStateImplCopyWithImpl<$Res>
extends _$ProxyGroupSelectorStateCopyWithImpl<$Res,
_$ProxyGroupSelectorStateImpl>
implements _$$ProxyGroupSelectorStateImplCopyWith<$Res> {
__$$ProxyGroupSelectorStateImplCopyWithImpl(
_$ProxyGroupSelectorStateImpl _value,
$Res Function(_$ProxyGroupSelectorStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? proxiesSortType = null,
Object? proxyCardType = null,
Object? sortNum = null,
Object? group = null,
Object? viewMode = null,
Object? proxies = null,
Object? columns = null,
}) {
return _then(_$ProxiesTabViewSelectorStateImpl(
return _then(_$ProxyGroupSelectorStateImpl(
proxiesSortType: null == proxiesSortType
? _value.proxiesSortType
: proxiesSortType // ignore: cast_nullable_to_non_nullable
as ProxiesSortType,
proxyCardType: null == proxyCardType
? _value.proxyCardType
: proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,
sortNum: null == sortNum
? _value.sortNum
: sortNum // ignore: cast_nullable_to_non_nullable
as num,
group: null == group
? _value.group
: group // ignore: cast_nullable_to_non_nullable
as Group,
viewMode: null == viewMode
? _value.viewMode
: viewMode // ignore: cast_nullable_to_non_nullable
as ViewMode,
proxies: null == proxies
? _value._proxies
: proxies // ignore: cast_nullable_to_non_nullable
as List<Proxy>,
columns: null == columns
? _value.columns
: columns // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
class _$ProxiesTabViewSelectorStateImpl
implements _ProxiesTabViewSelectorState {
const _$ProxiesTabViewSelectorStateImpl(
class _$ProxyGroupSelectorStateImpl implements _ProxyGroupSelectorState {
const _$ProxyGroupSelectorStateImpl(
{required this.proxiesSortType,
required this.proxyCardType,
required this.sortNum,
required this.group,
required this.viewMode});
required final List<Proxy> proxies,
required this.columns})
: _proxies = proxies;
@override
final ProxiesSortType proxiesSortType;
@override
final ProxyCardType proxyCardType;
@override
final num sortNum;
final List<Proxy> _proxies;
@override
final Group group;
List<Proxy> get proxies {
if (_proxies is EqualUnmodifiableListView) return _proxies;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_proxies);
}
@override
final ViewMode viewMode;
final int columns;
@override
String toString() {
return 'ProxiesTabViewSelectorState(proxiesSortType: $proxiesSortType, sortNum: $sortNum, group: $group, viewMode: $viewMode)';
return 'ProxyGroupSelectorState(proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, proxies: $proxies, columns: $columns)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProxiesTabViewSelectorStateImpl &&
other is _$ProxyGroupSelectorStateImpl &&
(identical(other.proxiesSortType, proxiesSortType) ||
other.proxiesSortType == proxiesSortType) &&
(identical(other.proxyCardType, proxyCardType) ||
other.proxyCardType == proxyCardType) &&
(identical(other.sortNum, sortNum) || other.sortNum == sortNum) &&
(identical(other.group, group) || other.group == group) &&
(identical(other.viewMode, viewMode) ||
other.viewMode == viewMode));
const DeepCollectionEquality().equals(other._proxies, _proxies) &&
(identical(other.columns, columns) || other.columns == columns));
}
@override
int get hashCode =>
Object.hash(runtimeType, proxiesSortType, sortNum, group, viewMode);
int get hashCode => Object.hash(runtimeType, proxiesSortType, proxyCardType,
sortNum, const DeepCollectionEquality().hash(_proxies), columns);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ProxiesTabViewSelectorStateImplCopyWith<_$ProxiesTabViewSelectorStateImpl>
get copyWith => __$$ProxiesTabViewSelectorStateImplCopyWithImpl<
_$ProxiesTabViewSelectorStateImpl>(this, _$identity);
_$$ProxyGroupSelectorStateImplCopyWith<_$ProxyGroupSelectorStateImpl>
get copyWith => __$$ProxyGroupSelectorStateImplCopyWithImpl<
_$ProxyGroupSelectorStateImpl>(this, _$identity);
}
abstract class _ProxiesTabViewSelectorState
implements ProxiesTabViewSelectorState {
const factory _ProxiesTabViewSelectorState(
abstract class _ProxyGroupSelectorState implements ProxyGroupSelectorState {
const factory _ProxyGroupSelectorState(
{required final ProxiesSortType proxiesSortType,
required final ProxyCardType proxyCardType,
required final num sortNum,
required final Group group,
required final ViewMode viewMode}) = _$ProxiesTabViewSelectorStateImpl;
required final List<Proxy> proxies,
required final int columns}) = _$ProxyGroupSelectorStateImpl;
@override
ProxiesSortType get proxiesSortType;
@override
ProxyCardType get proxyCardType;
@override
num get sortNum;
@override
Group get group;
List<Proxy> get proxies;
@override
ViewMode get viewMode;
int get columns;
@override
@JsonKey(ignore: true)
_$$ProxiesTabViewSelectorStateImplCopyWith<_$ProxiesTabViewSelectorStateImpl>
_$$ProxyGroupSelectorStateImplCopyWith<_$ProxyGroupSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@@ -54,6 +54,7 @@ class Profile with _$Profile {
UserInfo? userInfo,
@Default(true) bool autoUpdate,
@Default({}) SelectedMap selectedMap,
@Default({}) Set<String> unfoldSet,
}) = _Profile;
factory Profile.fromJson(Map<String, Object?> json) =>

View File

@@ -99,13 +99,14 @@ class ProxiesSelectorState with _$ProxiesSelectorState {
}
@freezed
class ProxiesTabViewSelectorState with _$ProxiesTabViewSelectorState {
const factory ProxiesTabViewSelectorState({
class ProxyGroupSelectorState with _$ProxyGroupSelectorState {
const factory ProxyGroupSelectorState({
required ProxiesSortType proxiesSortType,
required ProxyCardType proxyCardType,
required num sortNum,
required Group group,
required ViewMode viewMode,
}) = _ProxiesTabViewSelectorState;
required List<Proxy> proxies,
required int columns,
}) = _ProxyGroupSelectorState;
}
@freezed

View File

@@ -4,6 +4,7 @@ import 'dart:io';
import 'package:animations/animations.dart';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart';
@@ -261,6 +262,18 @@ class GlobalState {
return null;
}
}
int getColumns(ViewMode viewMode,int currentColumns){
final targetColumnsArray = switch (viewMode) {
ViewMode.mobile => [2, 1],
ViewMode.laptop => [3, 2],
ViewMode.desktop => [4, 3],
};
if (targetColumnsArray.contains(currentColumns)) {
return currentColumns;
}
return targetColumnsArray.first;
}
}
final globalState = GlobalState();

View File

@@ -1,4 +1,5 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
import 'text.dart';
@@ -54,6 +55,7 @@ class CommonCard extends StatelessWidget {
const CommonCard({
super.key,
bool? isSelected,
this.type = CommonCardType.plain,
this.onPressed,
this.info,
this.selectWidget,
@@ -65,10 +67,14 @@ class CommonCard extends StatelessWidget {
final Widget? selectWidget;
final Widget child;
final Info? info;
final CommonCardType type;
BorderSide getBorderSide(BuildContext context, Set<WidgetState> states) {
if(type == CommonCardType.filled){
return BorderSide.none;
}
final colorScheme = Theme.of(context).colorScheme;
var hoverColor = isSelected
final hoverColor = isSelected
? colorScheme.primary.toLight()
: colorScheme.primary.toLighter();
if (states.contains(WidgetState.hovered) ||
@@ -86,17 +92,28 @@ class CommonCard extends StatelessWidget {
Color? getBackgroundColor(BuildContext context, Set<WidgetState> states) {
final colorScheme = Theme.of(context).colorScheme;
if (isSelected) {
return colorScheme.secondaryContainer;
switch(type){
case CommonCardType.plain:
if (isSelected) {
return colorScheme.secondaryContainer;
}
if (states.isEmpty) {
return colorScheme.secondaryContainer.toLittle();
}
return Theme.of(context)
.outlinedButtonTheme
.style
?.backgroundColor
?.resolve(states);
case CommonCardType.filled:
if (isSelected) {
return colorScheme.secondaryContainer;
}
if (states.isEmpty) {
return colorScheme.surfaceContainerLow;
}
return colorScheme.surfaceContainer;
}
if (states.isEmpty) {
return colorScheme.secondaryContainer.toLittle();
}
return Theme.of(context)
.outlinedButtonTheme
.style
?.backgroundColor
?.resolve(states);
}
@override
@@ -136,11 +153,7 @@ class CommonCard extends StatelessWidget {
(states) => getBorderSide(context, states),
),
),
onPressed: () {
if (onPressed != null) {
onPressed!();
}
},
onPressed: onPressed,
child: Builder(
builder: (_) {
List<Widget> children = [];

View File

@@ -17,12 +17,17 @@ class CommonChip extends StatelessWidget {
@override
Widget build(BuildContext context) {
if(type == ChipType.delete){
if (type == ChipType.delete) {
return Chip(
avatar: avatar,
padding: const EdgeInsets.symmetric(
vertical: 0,
horizontal: 4,
),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onDeleted: onPressed ?? () {},
side: BorderSide(color: Theme.of(context).dividerColor.withOpacity(0.2)),
side:
BorderSide(color: Theme.of(context).dividerColor.withOpacity(0.2)),
labelStyle: Theme.of(context).textTheme.bodyMedium,
label: Text(label),
);
@@ -30,6 +35,10 @@ class CommonChip extends StatelessWidget {
return ActionChip(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
avatar: avatar,
padding: const EdgeInsets.symmetric(
vertical: 0,
horizontal: 4,
),
onPressed: onPressed ?? () {},
side: BorderSide(color: Theme.of(context).dividerColor.withOpacity(0.2)),
labelStyle: Theme.of(context).textTheme.bodyMedium,

View File

@@ -11,7 +11,7 @@ class NullStatus extends StatelessWidget {
return Center(
child: Text(
label,
style: Theme.of(context).textTheme.titleMedium?.toBold(),
style: Theme.of(context).textTheme.titleMedium?.toBold,
),
);
}

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