Compare commits

..

3 Commits

Author SHA1 Message Date
chen08209
67d819e327 Add windows server mode start process verify 2025-04-27 01:08:29 +08:00
chen08209
0a98515457 Optimize startTun performance 2025-04-26 21:29:56 +08:00
chen08209
be8cf039ee Support custom text scaling
Optimize the display of different text scale

Optimize windows setup experience
2025-04-25 17:38:26 +08:00
61 changed files with 1019 additions and 416 deletions

View File

@@ -3,6 +3,7 @@ package com.follow.clash.models
data class Package(
val packageName: String,
val label: String,
val isSystem: Boolean,
val system: Boolean,
val internet: Boolean,
val lastUpdateTime: Long,
)

View File

@@ -293,19 +293,17 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
if (packages.isNotEmpty()) return packages
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA or PackageManager.GET_PERMISSIONS)
?.filter {
it.packageName != FlClashApplication.getAppContext().packageName && (
it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|| it.packageName == "android"
)
it.packageName != FlClashApplication.getAppContext().packageName || it.packageName == "android"
}?.map {
Package(
packageName = it.packageName,
label = it.applicationInfo?.loadLabel(packageManager).toString(),
isSystem = (it.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM)) == 1,
lastUpdateTime = it.lastUpdateTime
)
}?.let { packages.addAll(it) }
Package(
packageName = it.packageName,
label = it.applicationInfo?.loadLabel(packageManager).toString(),
system = (it.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM)) == 1,
lastUpdateTime = it.lastUpdateTime,
internet = it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
)
}?.let { packages.addAll(it) }
return packages
}

View File

@@ -84,7 +84,6 @@ fun Service.startForeground(notification: Notification) {
}
}
Log.d("[FlClash]","startForeground===>")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
try {
startForeground(

View File

@@ -34,6 +34,10 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
if (options.ipv4Address.isNotEmpty()) {
val cidr = options.ipv4Address.toCIDR()
addAddress(cidr.address, cidr.prefixLength)
Log.d(
"addAddress",
"address: ${cidr.address} prefixLength:${cidr.prefixLength}"
)
val routeAddress = options.getIpv4RouteAddress()
if (routeAddress.isNotEmpty()) {
try {
@@ -50,26 +54,39 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
} else {
addRoute("0.0.0.0", 0)
}
} else {
addRoute("0.0.0.0", 0)
}
if (options.ipv6Address.isNotEmpty()) {
val cidr = options.ipv6Address.toCIDR()
addAddress(cidr.address, cidr.prefixLength)
val routeAddress = options.getIpv6RouteAddress()
if (routeAddress.isNotEmpty()) {
try {
routeAddress.forEach { i ->
Log.d(
"addRoute6",
"address: ${i.address} prefixLength:${i.prefixLength}"
)
addRoute(i.address, i.prefixLength)
try {
if (options.ipv6Address.isNotEmpty()) {
val cidr = options.ipv6Address.toCIDR()
Log.d(
"addAddress6",
"address: ${cidr.address} prefixLength:${cidr.prefixLength}"
)
addAddress(cidr.address, cidr.prefixLength)
val routeAddress = options.getIpv6RouteAddress()
if (routeAddress.isNotEmpty()) {
try {
routeAddress.forEach { i ->
Log.d(
"addRoute6",
"address: ${i.address} prefixLength:${i.prefixLength}"
)
addRoute(i.address, i.prefixLength)
}
} catch (_: Exception) {
addRoute("::", 0)
}
} catch (_: Exception) {
} else {
addRoute("::", 0)
}
} else {
addRoute("::", 0)
}
}catch (_:Exception){
Log.d(
"addAddress6",
"IPv6 is not supported."
)
}
addDnsServer(options.dnsServerAddress)
setMtu(9000)

View File

@@ -392,5 +392,8 @@
"messageTestTip": "This is a message.",
"crashTest": "Crash test",
"clearData": "Clear Data",
"textScale": "Text Scaling"
"textScale": "Text Scaling",
"internet": "Internet",
"systemApp": "System APP",
"noNetworkApp": "No network App"
}

View File

@@ -393,5 +393,8 @@
"crashTest": "クラッシュテスト",
"clearData": "データを消去",
"zoom": "ズーム",
"textScale": "テキストスケーリング"
"textScale": "テキストスケーリング",
"internet": "インターネット",
"systemApp": "システムアプリ",
"noNetworkApp": "ネットワークなしアプリ"
}

View File

@@ -393,5 +393,8 @@
"crashTest": "Тест на сбои",
"clearData": "Очистить данные",
"zoom": "Масштаб",
"textScale": "Масштабирование текста"
"textScale": "Масштабирование текста",
"internet": "Интернет",
"systemApp": "Системное приложение",
"noNetworkApp": "Приложение без сети"
}

View File

@@ -393,5 +393,8 @@
"crashTest": "崩溃测试",
"clearData": "清除数据",
"zoom": "缩放",
"textScale": "文本缩放"
"textScale": "文本缩放",
"internet": "互联网",
"systemApp": "系统应用",
"noNetworkApp": "无网络应用"
}

View File

@@ -98,13 +98,13 @@ func handleStopTun() {
}
}
func handleStartTun(fd int, callback unsafe.Pointer) bool {
func handleStartTun(fd int, callback unsafe.Pointer) {
handleStopTun()
tunLock.Lock()
defer tunLock.Unlock()
now := time.Now()
runTime = &now
if fd != 0 {
tunLock.Lock()
defer tunLock.Unlock()
tunHandler = &TunHandler{
callback: callback,
limit: semaphore.NewWeighted(4),
@@ -113,13 +113,11 @@ func handleStartTun(fd int, callback unsafe.Pointer) bool {
tunListener, _ := t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack)
if tunListener != nil {
log.Infoln("TUN address: %v", tunListener.Address())
tunHandler.listener = tunListener
} else {
removeTunHook()
return false
}
tunHandler.listener = tunListener
}
return true
}
func handleGetRunTime() string {
@@ -228,7 +226,10 @@ func quickStart(initParamsChar *C.char, paramsChar *C.char, stateParamsChar *C.c
//export startTUN
func startTUN(fd C.int, callback unsafe.Pointer) bool {
return handleStartTun(int(fd), callback)
go func() {
handleStartTun(int(fd), callback)
}()
return true
}
//export getRunTime
@@ -238,7 +239,9 @@ func getRunTime() *C.char {
//export stopTun
func stopTun() {
handleStopTun()
go func() {
handleStopTun()
}()
}
//export getCurrentProfileName

View File

@@ -24,7 +24,6 @@ type AccessControl struct {
Mode string `json:"mode"`
AcceptList []string `json:"acceptList"`
RejectList []string `json:"rejectList"`
IsFilterSystemApp bool `json:"isFilterSystemApp"`
}
type AndroidVpnRawOptions struct {

View File

@@ -245,7 +245,6 @@ abstract class ClashHandlerInterface with ClashInterface {
);
}
@override
Future<bool> crash() {
return invoke<bool>(

View File

@@ -71,9 +71,9 @@ class ClashService extends ClashHandlerInterface {
}
}, (error, stack) {
commonPrint.log(error.toString());
if(error is SocketException){
if (error is SocketException) {
globalState.showNotifier(error.toString());
globalState.appController.restartCore();
// globalState.appController.restartCore();
}
});
}

View File

@@ -16,7 +16,8 @@ const browserUa =
const packageName = "com.follow.clash";
final unixSocketPath = "/tmp/FlClashSocket_${Random().nextInt(10000)}.sock";
const helperPort = 47890;
const helperTag = "2024125";
const maxTextScale = 1.4;
const minTextScale = 0.8;
final baseInfoEdgeInsets = EdgeInsets.symmetric(
vertical: 16.ap,
horizontal: 16.ap,
@@ -56,6 +57,7 @@ final commonFilter = ImageFilter.blur(
const navigationItemListEquality = ListEquality<NavigationItem>();
const connectionListEquality = ListEquality<Connection>();
const stringListEquality = ListEquality<String>();
const intListEquality = ListEquality<int>();
const logListEquality = ListEquality<Log>();
const groupListEquality = ListEquality<Group>();
const externalProviderListEquality = ListEquality<ExternalProvider>();

View File

@@ -130,7 +130,7 @@ class Request {
if (response.statusCode != HttpStatus.ok) {
return false;
}
return (response.data as String) == helperTag;
return (response.data as String) == globalState.helperToken;
} catch (_) {
return false;
}

View File

@@ -396,9 +396,9 @@ class AppController {
handleExit() async {
try {
await updateStatus(false);
await proxy?.stopProxy();
await clashCore.shutdown();
await clashService?.destroy();
await proxy?.stopProxy();
await savePreferences();
} finally {
system.exit();
@@ -487,10 +487,10 @@ class AppController {
Future<void> _initCore() async {
final isInit = await clashCore.isInit;
if (!isInit) {
await clashCore.init();
await clashCore.setState(
globalState.getCoreState(),
);
await clashCore.init();
}
await applyProfile();
}
@@ -941,29 +941,34 @@ class AppController {
_recovery(Config config, RecoveryOption recoveryOption) {
final profiles = config.profiles;
for (final profile in profiles) {
_ref.read(profilesProvider.notifier).setProfile(profile);
final oldProfiles = List.from(globalState.config.profiles);
_ref.read(profilesProvider.notifier).value = profiles;
for (final profile in oldProfiles) {
_ref.read(profilesProvider.notifier).setProfile(
profile,
force: false,
);
}
final onlyProfiles = recoveryOption == RecoveryOption.onlyProfiles;
if (onlyProfiles) {
final currentProfile = _ref.read(currentProfileProvider);
if (currentProfile != null) {
_ref.read(currentProfileIdProvider.notifier).value = profiles.first.id;
}
return;
if (!onlyProfiles) {
_ref.read(patchClashConfigProvider.notifier).value =
config.patchClashConfig;
_ref.read(appSettingProvider.notifier).value = config.appSetting;
_ref.read(currentProfileIdProvider.notifier).value =
config.currentProfileId;
_ref.read(appDAVSettingProvider.notifier).value = config.dav;
_ref.read(themeSettingProvider.notifier).value = config.themeProps;
_ref.read(windowSettingProvider.notifier).value = config.windowProps;
_ref.read(vpnSettingProvider.notifier).value = config.vpnProps;
_ref.read(proxiesStyleSettingProvider.notifier).value =
config.proxiesStyle;
_ref.read(overrideDnsProvider.notifier).value = config.overrideDns;
_ref.read(networkSettingProvider.notifier).value = config.networkProps;
_ref.read(hotKeyActionsProvider.notifier).value = config.hotKeyActions;
}
final currentProfile = _ref.read(currentProfileProvider);
if (currentProfile == null) {
_ref.read(currentProfileIdProvider.notifier).value = profiles.first.id;
}
_ref.read(patchClashConfigProvider.notifier).value =
config.patchClashConfig;
_ref.read(appSettingProvider.notifier).value = config.appSetting;
_ref.read(currentProfileIdProvider.notifier).value =
config.currentProfileId;
_ref.read(appDAVSettingProvider.notifier).value = config.dav;
_ref.read(themeSettingProvider.notifier).value = config.themeProps;
_ref.read(windowSettingProvider.notifier).value = config.windowProps;
_ref.read(vpnSettingProvider.notifier).value = config.vpnProps;
_ref.read(proxiesStyleSettingProvider.notifier).value = config.proxiesStyle;
_ref.read(overrideDnsProvider.notifier).value = config.overrideDns;
_ref.read(networkSettingProvider.notifier).value = config.networkProps;
_ref.read(hotKeyActionsProvider.notifier).value = config.hotKeyActions;
}
}

View File

@@ -293,6 +293,7 @@ enum WindowsHelperServiceStatus {
enum DebounceTag {
updateClashConfig,
updateStatus,
updateGroups,
addCheckIpNum,
applyProfile,
@@ -347,6 +348,15 @@ enum DashboardWidget {
),
platforms: desktopPlatforms,
),
vpnButton(
GridItem(
crossAxisCellCount: 4,
child: VpnButton(),
),
platforms: [
SupportPlatform.Android,
],
),
systemProxyButton(
GridItem(
crossAxisCellCount: 4,

View File

@@ -113,15 +113,11 @@ class _AccessFragmentState extends ConsumerState<AccessFragment> {
}
_intelligentSelected() async {
final appState = globalState.appState;
final config = globalState.config;
final accessControl = config.vpnProps.accessControl;
final packageNames = appState.packages
.where(
(item) =>
accessControl.isFilterSystemApp ? item.isSystem == false : true,
)
.map((item) => item.packageName);
final packageNames = ref.read(
packageListSelectorStateProvider.select(
(state) => state.list.map((item) => item.packageName),
),
);
final commonScaffoldState = context.commonScaffoldState;
if (commonScaffoldState?.mounted != true) return;
final selectedPackageNames =
@@ -194,7 +190,7 @@ class _AccessFragmentState extends ConsumerState<AccessFragment> {
final state = ref.watch(packageListSelectorStateProvider);
final accessControl = state.accessControl;
final accessControlMode = accessControl.mode;
final packages = state.getList(
final packages = state.getSortList(
accessControlMode == AccessControlMode.acceptSelected
? acceptList
: rejectList,
@@ -482,14 +478,20 @@ class AccessControlSearchDelegate extends SearchDelegate {
final lowQuery = query.toLowerCase();
return Consumer(
builder: (context, ref, __) {
final state = ref.watch(packageListSelectorStateProvider);
final accessControl = state.accessControl;
final accessControlMode = accessControl.mode;
final packages = state.getList(
accessControlMode == AccessControlMode.acceptSelected
? acceptList
: rejectList,
final vm3 = ref.watch(
packageListSelectorStateProvider.select(
(state) => VM3(
a: state.getSortList(
state.accessControl.mode == AccessControlMode.acceptSelected
? acceptList
: rejectList,
),
b: state.accessControl.enable,
c: state.accessControl.currentList,
),
),
);
final packages = vm3.a;
final queryPackages = packages
.where(
(package) =>
@@ -497,8 +499,8 @@ class AccessControlSearchDelegate extends SearchDelegate {
package.packageName.contains(lowQuery),
)
.toList();
final isAccessControl = state.accessControl.enable;
final currentList = accessControl.currentList;
final isAccessControl = vm3.b;
final currentList = vm3.c;
final packageNameList = packages.map((e) => e.packageName).toList();
final valueList = currentList.intersection(packageNameList);
return DisabledMask(
@@ -579,13 +581,6 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
};
}
String _getTextWithIsFilterSystemApp(bool isFilterSystemApp) {
return switch (isFilterSystemApp) {
true => appLocalizations.onlyOtherApps,
false => appLocalizations.allApps,
};
}
List<Widget> _buildModeSetting() {
return generateSection(
title: appLocalizations.mode,
@@ -673,25 +668,39 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
scrollDirection: Axis.horizontal,
child: Consumer(
builder: (_, ref, __) {
final isFilterSystemApp = ref.watch(
vpnSettingProvider
.select((state) => state.accessControl.isFilterSystemApp),
final vm2 = ref.watch(
vpnSettingProvider.select(
(state) => VM2(
a: state.accessControl.isFilterSystemApp,
b: state.accessControl.isFilterNonInternetApp,
),
),
);
return Wrap(
spacing: 16,
children: [
for (final item in [false, true])
SettingTextCard(
_getTextWithIsFilterSystemApp(item),
isSelected: isFilterSystemApp == item,
onPressed: () {
ref.read(vpnSettingProvider.notifier).updateState(
(state) => state.copyWith.accessControl(
isFilterSystemApp: item,
),
);
},
)
SettingTextCard(
appLocalizations.systemApp,
isSelected: vm2.a == false,
onPressed: () {
ref.read(vpnSettingProvider.notifier).updateState(
(state) => state.copyWith.accessControl(
isFilterSystemApp: !vm2.a,
),
);
},
),
SettingTextCard(
appLocalizations.noNetworkApp,
isSelected: vm2.b == false,
onPressed: () {
ref.read(vpnSettingProvider.notifier).updateState(
(state) => state.copyWith.accessControl(
isFilterNonInternetApp: !vm2.b,
),
);
},
)
],
);
},

View File

@@ -301,8 +301,11 @@ class RouteAddressItem extends ConsumerWidget {
title: appLocalizations.routeAddress,
widget: Consumer(
builder: (_, ref, __) {
final routeAddress = ref.watch(patchClashConfigProvider
.select((state) => state.tun.routeAddress));
final routeAddress = ref.watch(
patchClashConfigProvider.select(
(state) => state.tun.routeAddress,
),
);
return ListInputPage(
title: appLocalizations.routeAddress,
items: routeAddress,
@@ -371,7 +374,9 @@ class NetworkListView extends ConsumerWidget {
return;
}
ref.read(vpnSettingProvider.notifier).updateState(
(state) => defaultVpnProps,
(state) => defaultVpnProps.copyWith(
accessControl: state.accessControl,
),
);
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(

View File

@@ -24,10 +24,9 @@ class OutboundMode extends StatelessWidget {
);
return Theme(
data: Theme.of(context).copyWith(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent
),
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent),
child: CommonCard(
onPressed: () {},
info: Info(
@@ -132,10 +131,12 @@ class OutboundModeV2 extends StatelessWidget {
.titleSmall
?.adjustSize(1)
.copyWith(
color: _getTextColor(
context,
mode,
),
color: item == mode
? _getTextColor(
context,
item,
)
: null,
),
),
),

View File

@@ -165,3 +165,87 @@ class SystemProxyButton extends StatelessWidget {
);
}
}
class VpnButton extends StatelessWidget {
const VpnButton({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
height: getWidgetHeight(1),
child: CommonCard(
onPressed: () {
showSheet(
context: context,
builder: (_, type) {
return AdaptiveSheetScaffold(
type: type,
body: generateListView(
generateSection(
items: [
const VPNItem(),
const VpnSystemProxyItem(),
const TunStackItem(),
],
),
),
title: "VPN",
);
},
);
},
info: Info(
label: "VPN",
iconData: Icons.stacked_line_chart,
),
child: Container(
padding: baseInfoEdgeInsets.copyWith(
top: 4,
bottom: 8,
right: 8,
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 1,
child: TooltipText(
text: Text(
appLocalizations.options,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.titleSmall
?.adjustSize(-2)
.toLight,
),
),
),
Consumer(
builder: (_, ref, __) {
final enable = ref.watch(
vpnSettingProvider.select(
(state) => state.enable,
),
);
return Switch(
value: enable,
onChanged: (value) {
ref.read(vpnSettingProvider.notifier).updateState(
(state) => state.copyWith(
enable: value,
),
);
},
);
},
)
],
),
),
),
);
}
}

View File

@@ -1,4 +1,5 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
@@ -35,11 +36,15 @@ class _StartButtonState extends State<StartButton>
}
handleSwitchStart() {
if (isStart == globalState.appState.isStart) {
isStart = !isStart;
updateController();
globalState.appController.updateStatus(isStart);
}
isStart = !isStart;
updateController();
debouncer.call(
DebounceTag.updateStatus,
() {
globalState.appController.updateStatus(isStart);
},
duration: moreDuration,
);
}
updateController() {
@@ -126,9 +131,11 @@ class _StartButtonState extends State<StartButton>
final text = utils.getTimeText(runTime);
return Text(
text,
style: Theme.of(context).textTheme.titleMedium?.toSoftBold.copyWith(
color: context.colorScheme.onPrimaryContainer
),
style: Theme.of(context)
.textTheme
.titleMedium
?.toSoftBold
.copyWith(color: context.colorScheme.onPrimaryContainer),
);
},
),

View File

@@ -6,7 +6,7 @@ import 'package:fl_clash/state.dart';
double get listHeaderHeight {
final measure = globalState.measure;
return 24 + measure.titleMediumHeight + 4 + measure.bodyMediumHeight;
return 28 + measure.titleMediumHeight + 4 + measure.bodyMediumHeight;
}
double getItemHeight(ProxyCardType proxyCardType) {

View File

@@ -414,7 +414,10 @@ class _ListHeaderState extends State<ListHeader>
return Consumer(
builder: (_, ref, child) {
final iconStyle = ref.watch(
proxiesStyleSettingProvider.select((state) => state.iconStyle));
proxiesStyleSettingProvider.select(
(state) => state.iconStyle,
),
);
final icon = ref.watch(proxiesStyleSettingProvider.select((state) {
final iconMapEntryList = state.iconMap.entries.toList();
final index = iconMapEntryList.indexWhere((item) {
@@ -430,30 +433,44 @@ class _ListHeaderState extends State<ListHeader>
return this.icon;
}));
return switch (iconStyle) {
ProxiesIconStyle.standard => Container(
height: 48,
width: 48,
margin: const EdgeInsets.only(
right: 16,
),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: context.colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(12),
),
clipBehavior: Clip.antiAlias,
child: CommonTargetIcon(
src: icon,
size: 32,
),
ProxiesIconStyle.standard => LayoutBuilder(
builder: (_, constraints) {
return Container(
margin: const EdgeInsets.only(
right: 16,
),
child: AspectRatio(
aspectRatio: 1,
child: Container(
height: constraints.maxHeight,
width: constraints.maxWidth,
alignment: Alignment.center,
padding: EdgeInsets.all(6.ap),
decoration: BoxDecoration(
color: context.colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(12),
),
clipBehavior: Clip.antiAlias,
child: CommonTargetIcon(
src: icon,
size: constraints.maxHeight - 12.ap,
),
),
),
);
},
),
ProxiesIconStyle.icon => Container(
margin: const EdgeInsets.only(
right: 16,
),
child: CommonTargetIcon(
src: icon,
size: 42,
child: LayoutBuilder(
builder: (_, constraints) {
return CommonTargetIcon(
src: icon,
size: constraints.maxHeight - 8,
);
},
),
),
ProxiesIconStyle.none => Container(),

View File

@@ -78,7 +78,7 @@ class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
return AdaptiveSheetScaffold(
type: type,
body: const ProxiesSetting(),
title: appLocalizations.proxiesSetting,
title: appLocalizations.settings,
);
},
);
@@ -123,8 +123,13 @@ class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
@override
Widget build(BuildContext context) {
final proxiesType =
ref.watch(proxiesStyleSettingProvider.select((state) => state.type));
final proxiesType = ref.watch(
proxiesStyleSettingProvider.select(
(state) => state.type,
),
);
ref.watch(themeSettingProvider.select((state) => state.textScale));
return switch (proxiesType) {
ProxiesType.tab => ProxiesTabFragment(
key: _proxiesTabKey,

View File

@@ -147,22 +147,21 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
FutureBuilder<FileInfo>(
future: _getGeoFileLastModified(geoItem.fileName),
builder: (_, snapshot) {
final height = globalState.measure.bodyMediumHeight;
return SizedBox(
height: 24,
child: FadeThroughBox(
key: Key("fade_box_${geoItem.label}"),
child: snapshot.data == null
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: Text(
snapshot.data!.desc,
height: height,
child: snapshot.data == null
? SizedBox(
width: height,
height: height,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
)
: Text(
snapshot.data!.desc,
style: context.textTheme.bodyMedium,
),
);
},
),

View File

@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use
import 'dart:math';
import 'dart:ui' as ui;
@@ -41,6 +43,7 @@ class ThemeFragment extends StatelessWidget {
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
spacing: 24,
children: [
_ThemeModeItem(),
_PrimaryColorItem(),
@@ -69,20 +72,15 @@ class ItemCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(
top: 16,
),
child: Wrap(
runSpacing: 16,
children: [
InfoHeader(
info: info,
actions: actions,
),
child,
],
),
return Wrap(
runSpacing: 16,
children: [
InfoHeader(
info: info,
actions: actions,
),
child,
],
);
}
}
@@ -285,18 +283,21 @@ class _PrimaryColorItemState extends ConsumerState<_PrimaryColorItem> {
@override
Widget build(BuildContext context) {
final vm3 = ref.watch(
final vm4 = ref.watch(
themeSettingProvider.select(
(state) => VM3(
(state) => VM4(
a: state.primaryColor,
b: state.primaryColors,
c: state.schemeVariant,
d: state.primaryColor == defaultPrimaryColor &&
intListEquality.equals(state.primaryColors, defaultPrimaryColors),
),
),
);
final primaryColor = vm3.a;
final primaryColors = [null, ...vm3.b];
final schemeVariant = vm3.c;
final primaryColor = vm4.a;
final primaryColors = [null, ...vm4.b];
final schemeVariant = vm4.c;
final isEquals = vm4.d;
return CommonPopScope(
onPop: () {
@@ -323,33 +324,32 @@ class _PrimaryColorItemState extends ConsumerState<_PrimaryColorItem> {
onPressed: _handleChangeSchemeVariant,
child: Text(Intl.message("${schemeVariant.name}Scheme")),
),
_removablePrimaryColor != null
? FilledButton(
style: ButtonStyle(
visualDensity: VisualDensity.compact,
),
onPressed: () {
setState(() {
_removablePrimaryColor = null;
});
},
child: Text(appLocalizations.cancel),
)
: IconButton.filledTonal(
iconSize: 20,
padding: EdgeInsets.all(4),
visualDensity: VisualDensity.compact,
onPressed: _handleReset,
icon: Icon(Icons.replay),
)
if (_removablePrimaryColor != null)
FilledButton(
style: ButtonStyle(
visualDensity: VisualDensity.compact,
),
onPressed: () {
setState(() {
_removablePrimaryColor = null;
});
},
child: Text(appLocalizations.cancel),
),
if (_removablePrimaryColor == null && !isEquals)
IconButton.filledTonal(
iconSize: 20,
padding: EdgeInsets.all(4),
visualDensity: VisualDensity.compact,
onPressed: _handleReset,
icon: Icon(Icons.replay),
)
],
space: 8,
),
child: Container(
margin: const EdgeInsets.only(
left: 16,
right: 16,
bottom: 16,
margin: const EdgeInsets.symmetric(
horizontal: 16,
),
child: LayoutBuilder(
builder: (_, constraints) {
@@ -441,31 +441,31 @@ class _PrueBlackItem extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final prueBlack =
ref.watch(themeSettingProvider.select((state) => state.pureBlack));
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: ListItem.switchItem(
leading: Icon(
Icons.contrast,
),
horizontalTitleGap: 12,
title: Text(
appLocalizations.pureBlackMode,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurfaceVariant,
),
),
delegate: SwitchDelegate(
value: prueBlack,
onChanged: (value) {
ref.read(themeSettingProvider.notifier).updateState(
(state) => state.copyWith(
pureBlack: value,
),
);
},
),
final prueBlack = ref.watch(
themeSettingProvider.select(
(state) => state.pureBlack,
),
);
return ListItem.switchItem(
leading: Icon(
Icons.contrast,
),
horizontalTitleGap: 12,
title: Text(
appLocalizations.pureBlackMode,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurfaceVariant,
),
),
delegate: SwitchDelegate(
value: prueBlack,
onChanged: (value) {
ref.read(themeSettingProvider.notifier).updateState(
(state) => state.copyWith(
pureBlack: value,
),
);
},
),
);
}
@@ -481,13 +481,12 @@ class _TextScaleFactorItem extends ConsumerWidget {
(state) => state.textScale,
),
);
final String process =
"${((textScale.scale * 100) as double).round()}%";
final String process = "${((textScale.scale * 100) as double).round()}%";
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
padding: EdgeInsets.only(bottom: 8),
child: ListItem.switchItem(
leading: Icon(
Icons.text_fields,
@@ -511,30 +510,45 @@ class _TextScaleFactorItem extends ConsumerWidget {
),
),
),
DisabledMask(
status: !textScale.enable,
child: ActivateBox(
active: textScale.enable,
child: SliderTheme(
data: _SliderDefaultsM3(context),
child: Slider(
min: 0.8,
divisions: 8,
padding: EdgeInsets.symmetric(
horizontal: 16,
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
spacing: 32,
children: [
Expanded(
child: DisabledMask(
status: !textScale.enable,
child: ActivateBox(
active: textScale.enable,
child: SliderTheme(
data: _SliderDefaultsM3(context),
child: Slider(
padding: EdgeInsets.zero,
min: minTextScale,
max: maxTextScale,
value: textScale.scale,
onChanged: (value) {
ref.read(themeSettingProvider.notifier).updateState(
(state) => state.copyWith.textScale(
scale: value,
),
);
},
),
),
),
),
max: 1.2,
label: process,
value: textScale.scale,
onChanged: (value) {
ref.read(themeSettingProvider.notifier).updateState(
(state) => state.copyWith.textScale(
scale: value,
),
);
},
),
),
Padding(
padding: EdgeInsets.only(right: 4),
child: Text(
process,
style: context.textTheme.titleMedium,
),
),
],
),
),
],

View File

@@ -310,7 +310,7 @@ class _DeveloperItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.developer_mode),
leading: const Icon(Icons.developer_board),
title: Text(appLocalizations.developerMode),
delegate: OpenDelegate(
title: appLocalizations.developerMode,

View File

@@ -326,6 +326,7 @@ class MessageLookup extends MessageLookupByLibrary {
"intelligentSelected": MessageLookupByLibrary.simpleMessage(
"Intelligent selection",
),
"internet": MessageLookupByLibrary.simpleMessage("Internet"),
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
@@ -409,6 +410,7 @@ class MessageLookup extends MessageLookupByLibrary {
"noInfo": MessageLookupByLibrary.simpleMessage("No info"),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("No more info"),
"noNetwork": MessageLookupByLibrary.simpleMessage("No network"),
"noNetworkApp": MessageLookupByLibrary.simpleMessage("No network App"),
"noProxy": MessageLookupByLibrary.simpleMessage("No proxy"),
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
"Please create a profile or add a valid profile",
@@ -622,6 +624,7 @@ class MessageLookup extends MessageLookupByLibrary {
"submit": MessageLookupByLibrary.simpleMessage("Submit"),
"sync": MessageLookupByLibrary.simpleMessage("Sync"),
"system": MessageLookupByLibrary.simpleMessage("System"),
"systemApp": MessageLookupByLibrary.simpleMessage("System APP"),
"systemFont": MessageLookupByLibrary.simpleMessage("System font"),
"systemProxy": MessageLookupByLibrary.simpleMessage("System proxy"),
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(

View File

@@ -236,6 +236,7 @@ class MessageLookup extends MessageLookupByLibrary {
"init": MessageLookupByLibrary.simpleMessage("初期化"),
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("正しいホットキーを入力"),
"intelligentSelected": MessageLookupByLibrary.simpleMessage("インテリジェント選択"),
"internet": MessageLookupByLibrary.simpleMessage("インターネット"),
"intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"),
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"),
@@ -295,6 +296,7 @@ class MessageLookup extends MessageLookupByLibrary {
"noInfo": MessageLookupByLibrary.simpleMessage("情報なし"),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("追加情報なし"),
"noNetwork": MessageLookupByLibrary.simpleMessage("ネットワークなし"),
"noNetworkApp": MessageLookupByLibrary.simpleMessage("ネットワークなしアプリ"),
"noProxy": MessageLookupByLibrary.simpleMessage("プロキシなし"),
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
"プロファイルを作成するか、有効なプロファイルを追加してください",
@@ -460,6 +462,7 @@ class MessageLookup extends MessageLookupByLibrary {
"submit": MessageLookupByLibrary.simpleMessage("送信"),
"sync": MessageLookupByLibrary.simpleMessage("同期"),
"system": MessageLookupByLibrary.simpleMessage("システム"),
"systemApp": MessageLookupByLibrary.simpleMessage("システムアプリ"),
"systemFont": MessageLookupByLibrary.simpleMessage("システムフォント"),
"systemProxy": MessageLookupByLibrary.simpleMessage("システムプロキシ"),
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(

View File

@@ -348,6 +348,7 @@ class MessageLookup extends MessageLookupByLibrary {
"intelligentSelected": MessageLookupByLibrary.simpleMessage(
"Интеллектуальный выбор",
),
"internet": MessageLookupByLibrary.simpleMessage("Интернет"),
"intranetIP": MessageLookupByLibrary.simpleMessage("Внутренний IP"),
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
@@ -437,6 +438,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Нет дополнительной информации",
),
"noNetwork": MessageLookupByLibrary.simpleMessage("Нет сети"),
"noNetworkApp": MessageLookupByLibrary.simpleMessage("Приложение без сети"),
"noProxy": MessageLookupByLibrary.simpleMessage("Нет прокси"),
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
"Пожалуйста, создайте профиль или добавьте действительный профиль",
@@ -660,6 +662,7 @@ class MessageLookup extends MessageLookupByLibrary {
"submit": MessageLookupByLibrary.simpleMessage("Отправить"),
"sync": MessageLookupByLibrary.simpleMessage("Синхронизация"),
"system": MessageLookupByLibrary.simpleMessage("Система"),
"systemApp": MessageLookupByLibrary.simpleMessage("Системное приложение"),
"systemFont": MessageLookupByLibrary.simpleMessage("Системный шрифт"),
"systemProxy": MessageLookupByLibrary.simpleMessage("Системный прокси"),
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(

View File

@@ -210,6 +210,7 @@ class MessageLookup extends MessageLookupByLibrary {
"init": MessageLookupByLibrary.simpleMessage("初始化"),
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("请输入正确的快捷键"),
"intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"),
"internet": MessageLookupByLibrary.simpleMessage("互联网"),
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
@@ -263,6 +264,7 @@ class MessageLookup extends MessageLookupByLibrary {
"noInfo": MessageLookupByLibrary.simpleMessage("暂无信息"),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("暂无更多信息"),
"noNetwork": MessageLookupByLibrary.simpleMessage("无网络"),
"noNetworkApp": MessageLookupByLibrary.simpleMessage("无网络应用"),
"noProxy": MessageLookupByLibrary.simpleMessage("暂无代理"),
"noProxyDesc": MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"),
"noResolve": MessageLookupByLibrary.simpleMessage("不解析IP"),
@@ -404,6 +406,7 @@ class MessageLookup extends MessageLookupByLibrary {
"submit": MessageLookupByLibrary.simpleMessage("提交"),
"sync": MessageLookupByLibrary.simpleMessage("同步"),
"system": MessageLookupByLibrary.simpleMessage("系统"),
"systemApp": MessageLookupByLibrary.simpleMessage("系统应用"),
"systemFont": MessageLookupByLibrary.simpleMessage("系统字体"),
"systemProxy": MessageLookupByLibrary.simpleMessage("系统代理"),
"systemProxyDesc": MessageLookupByLibrary.simpleMessage("设置系统代理"),

View File

@@ -3059,6 +3059,26 @@ class AppLocalizations {
String get textScale {
return Intl.message('Text Scaling', name: 'textScale', desc: '', args: []);
}
/// `Internet`
String get internet {
return Intl.message('Internet', name: 'internet', desc: '', args: []);
}
/// `System APP`
String get systemApp {
return Intl.message('System APP', name: 'systemApp', desc: '', args: []);
}
/// `No network App`
String get noNetworkApp {
return Intl.message(
'No network App',
name: 'noNetworkApp',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -27,6 +27,7 @@ Future<void> main() async {
await android?.init();
await window?.init(version);
globalState.isPre = const String.fromEnvironment("APP_ENV") != 'stable';
globalState.helperToken = const String.fromEnvironment("HELPER_TOKEN");
HttpOverrides.global = FlClashHttpOverrides();
runApp(ProviderScope(
child: const Application(),

View File

@@ -23,9 +23,9 @@ class ThemeManager extends ConsumerWidget {
final double textScaleFactor = max(
min(
textScale.enable ? textScale.scale : defaultTextScaleFactor,
1.2,
maxTextScale,
),
0.8,
minTextScale,
);
globalState.measure = Measure.of(context, textScaleFactor);

View File

@@ -25,7 +25,6 @@ class WindowManager extends ConsumerStatefulWidget {
class _WindowContainerState extends ConsumerState<WindowManager>
with WindowListener, WindowExtListener {
@override
Widget build(BuildContext context) {
return widget.child;
@@ -183,19 +182,23 @@ class _WindowHeaderState extends State<WindowHeader> {
super.dispose();
}
_updateMaximized() {
isMaximizedNotifier.value = !isMaximizedNotifier.value;
switch (isMaximizedNotifier.value) {
_updateMaximized() async {
final isMaximized = await windowManager.isMaximized();
switch (isMaximized) {
case true:
windowManager.maximize();
await windowManager.unmaximize();
break;
case false:
windowManager.unmaximize();
await windowManager.maximize();
break;
}
isMaximizedNotifier.value = await windowManager.isMaximized();
}
_updatePin() {
isPinNotifier.value = !isPinNotifier.value;
windowManager.setAlwaysOnTop(isPinNotifier.value);
_updatePin() async {
final isAlwaysOnTop = await windowManager.isAlwaysOnTop();
await windowManager.setAlwaysOnTop(!isAlwaysOnTop);
isPinNotifier.value = await windowManager.isAlwaysOnTop();
}
_buildActions() {

View File

@@ -147,6 +147,7 @@ class Tun with _$Tun {
const factory Tun({
@Default(false) bool enable,
@Default(appName) String device,
@JsonKey(name: "auto-route") @Default(false) bool autoRoute,
@Default(TunStack.gvisor) TunStack stack,
@JsonKey(name: "dns-hijack") @Default(["any:53"]) List<String> dnsHijack,
@JsonKey(name: "route-address") @Default([]) List<String> routeAddress,

View File

@@ -30,7 +30,8 @@ class Package with _$Package {
const factory Package({
required String packageName,
required String label,
required bool isSystem,
required bool system,
required bool internet,
required int lastUpdateTime,
}) = _Package;

View File

@@ -106,6 +106,7 @@ class AccessControl with _$AccessControl {
@Default([]) List<String> rejectList,
@Default(AccessSortType.none) AccessSortType sort,
@Default(true) bool isFilterSystemApp,
@Default(true) bool isFilterNonInternetApp,
}) = _AccessControl;
factory AccessControl.fromJson(Map<String, Object?> json) =>

View File

@@ -660,6 +660,8 @@ Tun _$TunFromJson(Map<String, dynamic> json) {
mixin _$Tun {
bool get enable => throw _privateConstructorUsedError;
String get device => throw _privateConstructorUsedError;
@JsonKey(name: "auto-route")
bool get autoRoute => throw _privateConstructorUsedError;
TunStack get stack => throw _privateConstructorUsedError;
@JsonKey(name: "dns-hijack")
List<String> get dnsHijack => throw _privateConstructorUsedError;
@@ -683,6 +685,7 @@ abstract class $TunCopyWith<$Res> {
$Res call(
{bool enable,
String device,
@JsonKey(name: "auto-route") bool autoRoute,
TunStack stack,
@JsonKey(name: "dns-hijack") List<String> dnsHijack,
@JsonKey(name: "route-address") List<String> routeAddress});
@@ -704,6 +707,7 @@ class _$TunCopyWithImpl<$Res, $Val extends Tun> implements $TunCopyWith<$Res> {
$Res call({
Object? enable = null,
Object? device = null,
Object? autoRoute = null,
Object? stack = null,
Object? dnsHijack = null,
Object? routeAddress = null,
@@ -717,6 +721,10 @@ class _$TunCopyWithImpl<$Res, $Val extends Tun> implements $TunCopyWith<$Res> {
? _value.device
: device // ignore: cast_nullable_to_non_nullable
as String,
autoRoute: null == autoRoute
? _value.autoRoute
: autoRoute // ignore: cast_nullable_to_non_nullable
as bool,
stack: null == stack
? _value.stack
: stack // ignore: cast_nullable_to_non_nullable
@@ -742,6 +750,7 @@ abstract class _$$TunImplCopyWith<$Res> implements $TunCopyWith<$Res> {
$Res call(
{bool enable,
String device,
@JsonKey(name: "auto-route") bool autoRoute,
TunStack stack,
@JsonKey(name: "dns-hijack") List<String> dnsHijack,
@JsonKey(name: "route-address") List<String> routeAddress});
@@ -760,6 +769,7 @@ class __$$TunImplCopyWithImpl<$Res> extends _$TunCopyWithImpl<$Res, _$TunImpl>
$Res call({
Object? enable = null,
Object? device = null,
Object? autoRoute = null,
Object? stack = null,
Object? dnsHijack = null,
Object? routeAddress = null,
@@ -773,6 +783,10 @@ class __$$TunImplCopyWithImpl<$Res> extends _$TunCopyWithImpl<$Res, _$TunImpl>
? _value.device
: device // ignore: cast_nullable_to_non_nullable
as String,
autoRoute: null == autoRoute
? _value.autoRoute
: autoRoute // ignore: cast_nullable_to_non_nullable
as bool,
stack: null == stack
? _value.stack
: stack // ignore: cast_nullable_to_non_nullable
@@ -795,6 +809,7 @@ class _$TunImpl implements _Tun {
const _$TunImpl(
{this.enable = false,
this.device = appName,
@JsonKey(name: "auto-route") this.autoRoute = false,
this.stack = TunStack.gvisor,
@JsonKey(name: "dns-hijack")
final List<String> dnsHijack = const ["any:53"],
@@ -813,6 +828,9 @@ class _$TunImpl implements _Tun {
@JsonKey()
final String device;
@override
@JsonKey(name: "auto-route")
final bool autoRoute;
@override
@JsonKey()
final TunStack stack;
final List<String> _dnsHijack;
@@ -835,7 +853,7 @@ class _$TunImpl implements _Tun {
@override
String toString() {
return 'Tun(enable: $enable, device: $device, stack: $stack, dnsHijack: $dnsHijack, routeAddress: $routeAddress)';
return 'Tun(enable: $enable, device: $device, autoRoute: $autoRoute, stack: $stack, dnsHijack: $dnsHijack, routeAddress: $routeAddress)';
}
@override
@@ -845,6 +863,8 @@ class _$TunImpl implements _Tun {
other is _$TunImpl &&
(identical(other.enable, enable) || other.enable == enable) &&
(identical(other.device, device) || other.device == device) &&
(identical(other.autoRoute, autoRoute) ||
other.autoRoute == autoRoute) &&
(identical(other.stack, stack) || other.stack == stack) &&
const DeepCollectionEquality()
.equals(other._dnsHijack, _dnsHijack) &&
@@ -858,6 +878,7 @@ class _$TunImpl implements _Tun {
runtimeType,
enable,
device,
autoRoute,
stack,
const DeepCollectionEquality().hash(_dnsHijack),
const DeepCollectionEquality().hash(_routeAddress));
@@ -882,6 +903,7 @@ abstract class _Tun implements Tun {
const factory _Tun(
{final bool enable,
final String device,
@JsonKey(name: "auto-route") final bool autoRoute,
final TunStack stack,
@JsonKey(name: "dns-hijack") final List<String> dnsHijack,
@JsonKey(name: "route-address") final List<String> routeAddress}) =
@@ -894,6 +916,9 @@ abstract class _Tun implements Tun {
@override
String get device;
@override
@JsonKey(name: "auto-route")
bool get autoRoute;
@override
TunStack get stack;
@override
@JsonKey(name: "dns-hijack")

View File

@@ -66,6 +66,7 @@ Map<String, dynamic> _$$RuleProviderImplToJson(_$RuleProviderImpl instance) =>
_$TunImpl _$$TunImplFromJson(Map<String, dynamic> json) => _$TunImpl(
enable: json['enable'] as bool? ?? false,
device: json['device'] as String? ?? appName,
autoRoute: json['auto-route'] as bool? ?? false,
stack: $enumDecodeNullable(_$TunStackEnumMap, json['stack']) ??
TunStack.gvisor,
dnsHijack: (json['dns-hijack'] as List<dynamic>?)
@@ -81,6 +82,7 @@ _$TunImpl _$$TunImplFromJson(Map<String, dynamic> json) => _$TunImpl(
Map<String, dynamic> _$$TunImplToJson(_$TunImpl instance) => <String, dynamic>{
'enable': instance.enable,
'device': instance.device,
'auto-route': instance.autoRoute,
'stack': _$TunStackEnumMap[instance.stack]!,
'dns-hijack': instance.dnsHijack,
'route-address': instance.routeAddress,

View File

@@ -289,7 +289,8 @@ Package _$PackageFromJson(Map<String, dynamic> json) {
mixin _$Package {
String get packageName => throw _privateConstructorUsedError;
String get label => throw _privateConstructorUsedError;
bool get isSystem => throw _privateConstructorUsedError;
bool get system => throw _privateConstructorUsedError;
bool get internet => throw _privateConstructorUsedError;
int get lastUpdateTime => throw _privateConstructorUsedError;
/// Serializes this Package to a JSON map.
@@ -307,7 +308,11 @@ abstract class $PackageCopyWith<$Res> {
_$PackageCopyWithImpl<$Res, Package>;
@useResult
$Res call(
{String packageName, String label, bool isSystem, int lastUpdateTime});
{String packageName,
String label,
bool system,
bool internet,
int lastUpdateTime});
}
/// @nodoc
@@ -327,7 +332,8 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
$Res call({
Object? packageName = null,
Object? label = null,
Object? isSystem = null,
Object? system = null,
Object? internet = null,
Object? lastUpdateTime = null,
}) {
return _then(_value.copyWith(
@@ -339,9 +345,13 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
? _value.label
: label // ignore: cast_nullable_to_non_nullable
as String,
isSystem: null == isSystem
? _value.isSystem
: isSystem // ignore: cast_nullable_to_non_nullable
system: null == system
? _value.system
: system // ignore: cast_nullable_to_non_nullable
as bool,
internet: null == internet
? _value.internet
: internet // ignore: cast_nullable_to_non_nullable
as bool,
lastUpdateTime: null == lastUpdateTime
? _value.lastUpdateTime
@@ -359,7 +369,11 @@ abstract class _$$PackageImplCopyWith<$Res> implements $PackageCopyWith<$Res> {
@override
@useResult
$Res call(
{String packageName, String label, bool isSystem, int lastUpdateTime});
{String packageName,
String label,
bool system,
bool internet,
int lastUpdateTime});
}
/// @nodoc
@@ -377,7 +391,8 @@ class __$$PackageImplCopyWithImpl<$Res>
$Res call({
Object? packageName = null,
Object? label = null,
Object? isSystem = null,
Object? system = null,
Object? internet = null,
Object? lastUpdateTime = null,
}) {
return _then(_$PackageImpl(
@@ -389,9 +404,13 @@ class __$$PackageImplCopyWithImpl<$Res>
? _value.label
: label // ignore: cast_nullable_to_non_nullable
as String,
isSystem: null == isSystem
? _value.isSystem
: isSystem // ignore: cast_nullable_to_non_nullable
system: null == system
? _value.system
: system // ignore: cast_nullable_to_non_nullable
as bool,
internet: null == internet
? _value.internet
: internet // ignore: cast_nullable_to_non_nullable
as bool,
lastUpdateTime: null == lastUpdateTime
? _value.lastUpdateTime
@@ -407,7 +426,8 @@ class _$PackageImpl implements _Package {
const _$PackageImpl(
{required this.packageName,
required this.label,
required this.isSystem,
required this.system,
required this.internet,
required this.lastUpdateTime});
factory _$PackageImpl.fromJson(Map<String, dynamic> json) =>
@@ -418,13 +438,15 @@ class _$PackageImpl implements _Package {
@override
final String label;
@override
final bool isSystem;
final bool system;
@override
final bool internet;
@override
final int lastUpdateTime;
@override
String toString() {
return 'Package(packageName: $packageName, label: $label, isSystem: $isSystem, lastUpdateTime: $lastUpdateTime)';
return 'Package(packageName: $packageName, label: $label, system: $system, internet: $internet, lastUpdateTime: $lastUpdateTime)';
}
@override
@@ -435,16 +457,17 @@ class _$PackageImpl implements _Package {
(identical(other.packageName, packageName) ||
other.packageName == packageName) &&
(identical(other.label, label) || other.label == label) &&
(identical(other.isSystem, isSystem) ||
other.isSystem == isSystem) &&
(identical(other.system, system) || other.system == system) &&
(identical(other.internet, internet) ||
other.internet == internet) &&
(identical(other.lastUpdateTime, lastUpdateTime) ||
other.lastUpdateTime == lastUpdateTime));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, packageName, label, isSystem, lastUpdateTime);
int get hashCode => Object.hash(
runtimeType, packageName, label, system, internet, lastUpdateTime);
/// Create a copy of Package
/// with the given fields replaced by the non-null parameter values.
@@ -466,7 +489,8 @@ abstract class _Package implements Package {
const factory _Package(
{required final String packageName,
required final String label,
required final bool isSystem,
required final bool system,
required final bool internet,
required final int lastUpdateTime}) = _$PackageImpl;
factory _Package.fromJson(Map<String, dynamic> json) = _$PackageImpl.fromJson;
@@ -476,7 +500,9 @@ abstract class _Package implements Package {
@override
String get label;
@override
bool get isSystem;
bool get system;
@override
bool get internet;
@override
int get lastUpdateTime;

View File

@@ -10,7 +10,8 @@ _$PackageImpl _$$PackageImplFromJson(Map<String, dynamic> json) =>
_$PackageImpl(
packageName: json['packageName'] as String,
label: json['label'] as String,
isSystem: json['isSystem'] as bool,
system: json['system'] as bool,
internet: json['internet'] as bool,
lastUpdateTime: (json['lastUpdateTime'] as num).toInt(),
);
@@ -18,7 +19,8 @@ Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
<String, dynamic>{
'packageName': instance.packageName,
'label': instance.label,
'isSystem': instance.isSystem,
'system': instance.system,
'internet': instance.internet,
'lastUpdateTime': instance.lastUpdateTime,
};

View File

@@ -539,6 +539,7 @@ mixin _$AccessControl {
List<String> get rejectList => throw _privateConstructorUsedError;
AccessSortType get sort => throw _privateConstructorUsedError;
bool get isFilterSystemApp => throw _privateConstructorUsedError;
bool get isFilterNonInternetApp => throw _privateConstructorUsedError;
/// Serializes this AccessControl to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -562,7 +563,8 @@ abstract class $AccessControlCopyWith<$Res> {
List<String> acceptList,
List<String> rejectList,
AccessSortType sort,
bool isFilterSystemApp});
bool isFilterSystemApp,
bool isFilterNonInternetApp});
}
/// @nodoc
@@ -586,6 +588,7 @@ class _$AccessControlCopyWithImpl<$Res, $Val extends AccessControl>
Object? rejectList = null,
Object? sort = null,
Object? isFilterSystemApp = null,
Object? isFilterNonInternetApp = null,
}) {
return _then(_value.copyWith(
enable: null == enable
@@ -612,6 +615,10 @@ class _$AccessControlCopyWithImpl<$Res, $Val extends AccessControl>
? _value.isFilterSystemApp
: isFilterSystemApp // ignore: cast_nullable_to_non_nullable
as bool,
isFilterNonInternetApp: null == isFilterNonInternetApp
? _value.isFilterNonInternetApp
: isFilterNonInternetApp // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
@@ -630,7 +637,8 @@ abstract class _$$AccessControlImplCopyWith<$Res>
List<String> acceptList,
List<String> rejectList,
AccessSortType sort,
bool isFilterSystemApp});
bool isFilterSystemApp,
bool isFilterNonInternetApp});
}
/// @nodoc
@@ -652,6 +660,7 @@ class __$$AccessControlImplCopyWithImpl<$Res>
Object? rejectList = null,
Object? sort = null,
Object? isFilterSystemApp = null,
Object? isFilterNonInternetApp = null,
}) {
return _then(_$AccessControlImpl(
enable: null == enable
@@ -678,6 +687,10 @@ class __$$AccessControlImplCopyWithImpl<$Res>
? _value.isFilterSystemApp
: isFilterSystemApp // ignore: cast_nullable_to_non_nullable
as bool,
isFilterNonInternetApp: null == isFilterNonInternetApp
? _value.isFilterNonInternetApp
: isFilterNonInternetApp // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -691,7 +704,8 @@ class _$AccessControlImpl implements _AccessControl {
final List<String> acceptList = const [],
final List<String> rejectList = const [],
this.sort = AccessSortType.none,
this.isFilterSystemApp = true})
this.isFilterSystemApp = true,
this.isFilterNonInternetApp = true})
: _acceptList = acceptList,
_rejectList = rejectList;
@@ -728,10 +742,13 @@ class _$AccessControlImpl implements _AccessControl {
@override
@JsonKey()
final bool isFilterSystemApp;
@override
@JsonKey()
final bool isFilterNonInternetApp;
@override
String toString() {
return 'AccessControl(enable: $enable, mode: $mode, acceptList: $acceptList, rejectList: $rejectList, sort: $sort, isFilterSystemApp: $isFilterSystemApp)';
return 'AccessControl(enable: $enable, mode: $mode, acceptList: $acceptList, rejectList: $rejectList, sort: $sort, isFilterSystemApp: $isFilterSystemApp, isFilterNonInternetApp: $isFilterNonInternetApp)';
}
@override
@@ -747,7 +764,9 @@ class _$AccessControlImpl implements _AccessControl {
.equals(other._rejectList, _rejectList) &&
(identical(other.sort, sort) || other.sort == sort) &&
(identical(other.isFilterSystemApp, isFilterSystemApp) ||
other.isFilterSystemApp == isFilterSystemApp));
other.isFilterSystemApp == isFilterSystemApp) &&
(identical(other.isFilterNonInternetApp, isFilterNonInternetApp) ||
other.isFilterNonInternetApp == isFilterNonInternetApp));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -759,7 +778,8 @@ class _$AccessControlImpl implements _AccessControl {
const DeepCollectionEquality().hash(_acceptList),
const DeepCollectionEquality().hash(_rejectList),
sort,
isFilterSystemApp);
isFilterSystemApp,
isFilterNonInternetApp);
/// Create a copy of AccessControl
/// with the given fields replaced by the non-null parameter values.
@@ -784,7 +804,8 @@ abstract class _AccessControl implements AccessControl {
final List<String> acceptList,
final List<String> rejectList,
final AccessSortType sort,
final bool isFilterSystemApp}) = _$AccessControlImpl;
final bool isFilterSystemApp,
final bool isFilterNonInternetApp}) = _$AccessControlImpl;
factory _AccessControl.fromJson(Map<String, dynamic> json) =
_$AccessControlImpl.fromJson;
@@ -801,6 +822,8 @@ abstract class _AccessControl implements AccessControl {
AccessSortType get sort;
@override
bool get isFilterSystemApp;
@override
bool get isFilterNonInternetApp;
/// Create a copy of AccessControl
/// with the given fields replaced by the non-null parameter values.

View File

@@ -59,6 +59,7 @@ const _$DashboardWidgetEnumMap = {
DashboardWidget.trafficUsage: 'trafficUsage',
DashboardWidget.networkDetection: 'networkDetection',
DashboardWidget.tunButton: 'tunButton',
DashboardWidget.vpnButton: 'vpnButton',
DashboardWidget.systemProxyButton: 'systemProxyButton',
DashboardWidget.intranetIp: 'intranetIp',
DashboardWidget.memoryInfo: 'memoryInfo',
@@ -80,6 +81,7 @@ _$AccessControlImpl _$$AccessControlImplFromJson(Map<String, dynamic> json) =>
sort: $enumDecodeNullable(_$AccessSortTypeEnumMap, json['sort']) ??
AccessSortType.none,
isFilterSystemApp: json['isFilterSystemApp'] as bool? ?? true,
isFilterNonInternetApp: json['isFilterNonInternetApp'] as bool? ?? true,
);
Map<String, dynamic> _$$AccessControlImplToJson(_$AccessControlImpl instance) =>
@@ -90,6 +92,7 @@ Map<String, dynamic> _$$AccessControlImplToJson(_$AccessControlImpl instance) =>
'rejectList': instance.rejectList,
'sort': _$AccessSortTypeEnumMap[instance.sort]!,
'isFilterSystemApp': instance.isFilterSystemApp,
'isFilterNonInternetApp': instance.isFilterNonInternetApp,
};
const _$AccessControlModeEnumMap = {

View File

@@ -327,6 +327,193 @@ abstract class _VM3<A, B, C> implements VM3<A, B, C> {
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$VM4<A, B, C, D> {
A get a => throw _privateConstructorUsedError;
B get b => throw _privateConstructorUsedError;
C get c => throw _privateConstructorUsedError;
D get d => throw _privateConstructorUsedError;
/// Create a copy of VM4
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$VM4CopyWith<A, B, C, D, VM4<A, B, C, D>> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $VM4CopyWith<A, B, C, D, $Res> {
factory $VM4CopyWith(
VM4<A, B, C, D> value, $Res Function(VM4<A, B, C, D>) then) =
_$VM4CopyWithImpl<A, B, C, D, $Res, VM4<A, B, C, D>>;
@useResult
$Res call({A a, B b, C c, D d});
}
/// @nodoc
class _$VM4CopyWithImpl<A, B, C, D, $Res, $Val extends VM4<A, B, C, D>>
implements $VM4CopyWith<A, B, C, D, $Res> {
_$VM4CopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of VM4
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? a = freezed,
Object? b = freezed,
Object? c = freezed,
Object? d = freezed,
}) {
return _then(_value.copyWith(
a: freezed == a
? _value.a
: a // ignore: cast_nullable_to_non_nullable
as A,
b: freezed == b
? _value.b
: b // ignore: cast_nullable_to_non_nullable
as B,
c: freezed == c
? _value.c
: c // ignore: cast_nullable_to_non_nullable
as C,
d: freezed == d
? _value.d
: d // ignore: cast_nullable_to_non_nullable
as D,
) as $Val);
}
}
/// @nodoc
abstract class _$$VM4ImplCopyWith<A, B, C, D, $Res>
implements $VM4CopyWith<A, B, C, D, $Res> {
factory _$$VM4ImplCopyWith(_$VM4Impl<A, B, C, D> value,
$Res Function(_$VM4Impl<A, B, C, D>) then) =
__$$VM4ImplCopyWithImpl<A, B, C, D, $Res>;
@override
@useResult
$Res call({A a, B b, C c, D d});
}
/// @nodoc
class __$$VM4ImplCopyWithImpl<A, B, C, D, $Res>
extends _$VM4CopyWithImpl<A, B, C, D, $Res, _$VM4Impl<A, B, C, D>>
implements _$$VM4ImplCopyWith<A, B, C, D, $Res> {
__$$VM4ImplCopyWithImpl(
_$VM4Impl<A, B, C, D> _value, $Res Function(_$VM4Impl<A, B, C, D>) _then)
: super(_value, _then);
/// Create a copy of VM4
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? a = freezed,
Object? b = freezed,
Object? c = freezed,
Object? d = freezed,
}) {
return _then(_$VM4Impl<A, B, C, D>(
a: freezed == a
? _value.a
: a // ignore: cast_nullable_to_non_nullable
as A,
b: freezed == b
? _value.b
: b // ignore: cast_nullable_to_non_nullable
as B,
c: freezed == c
? _value.c
: c // ignore: cast_nullable_to_non_nullable
as C,
d: freezed == d
? _value.d
: d // ignore: cast_nullable_to_non_nullable
as D,
));
}
}
/// @nodoc
class _$VM4Impl<A, B, C, D> implements _VM4<A, B, C, D> {
const _$VM4Impl(
{required this.a, required this.b, required this.c, required this.d});
@override
final A a;
@override
final B b;
@override
final C c;
@override
final D d;
@override
String toString() {
return 'VM4<$A, $B, $C, $D>(a: $a, b: $b, c: $c, d: $d)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$VM4Impl<A, B, C, D> &&
const DeepCollectionEquality().equals(other.a, a) &&
const DeepCollectionEquality().equals(other.b, b) &&
const DeepCollectionEquality().equals(other.c, c) &&
const DeepCollectionEquality().equals(other.d, d));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(a),
const DeepCollectionEquality().hash(b),
const DeepCollectionEquality().hash(c),
const DeepCollectionEquality().hash(d));
/// Create a copy of VM4
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$VM4ImplCopyWith<A, B, C, D, _$VM4Impl<A, B, C, D>> get copyWith =>
__$$VM4ImplCopyWithImpl<A, B, C, D, _$VM4Impl<A, B, C, D>>(
this, _$identity);
}
abstract class _VM4<A, B, C, D> implements VM4<A, B, C, D> {
const factory _VM4(
{required final A a,
required final B b,
required final C c,
required final D d}) = _$VM4Impl<A, B, C, D>;
@override
A get a;
@override
B get b;
@override
C get c;
@override
D get d;
/// Create a copy of VM4
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$VM4ImplCopyWith<A, B, C, D, _$VM4Impl<A, B, C, D>> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$StartButtonSelectorState {
bool get isInit => throw _privateConstructorUsedError;
@@ -3335,6 +3522,7 @@ mixin _$ClashConfigState {
bool get overrideDns => throw _privateConstructorUsedError;
ClashConfig get clashConfig => throw _privateConstructorUsedError;
OverrideData get overrideData => throw _privateConstructorUsedError;
RouteMode get routeMode => throw _privateConstructorUsedError;
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
@@ -3350,7 +3538,10 @@ abstract class $ClashConfigStateCopyWith<$Res> {
_$ClashConfigStateCopyWithImpl<$Res, ClashConfigState>;
@useResult
$Res call(
{bool overrideDns, ClashConfig clashConfig, OverrideData overrideData});
{bool overrideDns,
ClashConfig clashConfig,
OverrideData overrideData,
RouteMode routeMode});
$ClashConfigCopyWith<$Res> get clashConfig;
$OverrideDataCopyWith<$Res> get overrideData;
@@ -3374,6 +3565,7 @@ class _$ClashConfigStateCopyWithImpl<$Res, $Val extends ClashConfigState>
Object? overrideDns = null,
Object? clashConfig = null,
Object? overrideData = null,
Object? routeMode = null,
}) {
return _then(_value.copyWith(
overrideDns: null == overrideDns
@@ -3388,6 +3580,10 @@ class _$ClashConfigStateCopyWithImpl<$Res, $Val extends ClashConfigState>
? _value.overrideData
: overrideData // ignore: cast_nullable_to_non_nullable
as OverrideData,
routeMode: null == routeMode
? _value.routeMode
: routeMode // ignore: cast_nullable_to_non_nullable
as RouteMode,
) as $Val);
}
@@ -3421,7 +3617,10 @@ abstract class _$$ClashConfigStateImplCopyWith<$Res>
@override
@useResult
$Res call(
{bool overrideDns, ClashConfig clashConfig, OverrideData overrideData});
{bool overrideDns,
ClashConfig clashConfig,
OverrideData overrideData,
RouteMode routeMode});
@override
$ClashConfigCopyWith<$Res> get clashConfig;
@@ -3445,6 +3644,7 @@ class __$$ClashConfigStateImplCopyWithImpl<$Res>
Object? overrideDns = null,
Object? clashConfig = null,
Object? overrideData = null,
Object? routeMode = null,
}) {
return _then(_$ClashConfigStateImpl(
overrideDns: null == overrideDns
@@ -3459,6 +3659,10 @@ class __$$ClashConfigStateImplCopyWithImpl<$Res>
? _value.overrideData
: overrideData // ignore: cast_nullable_to_non_nullable
as OverrideData,
routeMode: null == routeMode
? _value.routeMode
: routeMode // ignore: cast_nullable_to_non_nullable
as RouteMode,
));
}
}
@@ -3469,7 +3673,8 @@ class _$ClashConfigStateImpl implements _ClashConfigState {
const _$ClashConfigStateImpl(
{required this.overrideDns,
required this.clashConfig,
required this.overrideData});
required this.overrideData,
required this.routeMode});
@override
final bool overrideDns;
@@ -3477,10 +3682,12 @@ class _$ClashConfigStateImpl implements _ClashConfigState {
final ClashConfig clashConfig;
@override
final OverrideData overrideData;
@override
final RouteMode routeMode;
@override
String toString() {
return 'ClashConfigState(overrideDns: $overrideDns, clashConfig: $clashConfig, overrideData: $overrideData)';
return 'ClashConfigState(overrideDns: $overrideDns, clashConfig: $clashConfig, overrideData: $overrideData, routeMode: $routeMode)';
}
@override
@@ -3493,12 +3700,14 @@ class _$ClashConfigStateImpl implements _ClashConfigState {
(identical(other.clashConfig, clashConfig) ||
other.clashConfig == clashConfig) &&
(identical(other.overrideData, overrideData) ||
other.overrideData == overrideData));
other.overrideData == overrideData) &&
(identical(other.routeMode, routeMode) ||
other.routeMode == routeMode));
}
@override
int get hashCode =>
Object.hash(runtimeType, overrideDns, clashConfig, overrideData);
int get hashCode => Object.hash(
runtimeType, overrideDns, clashConfig, overrideData, routeMode);
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
@@ -3514,7 +3723,8 @@ abstract class _ClashConfigState implements ClashConfigState {
const factory _ClashConfigState(
{required final bool overrideDns,
required final ClashConfig clashConfig,
required final OverrideData overrideData}) = _$ClashConfigStateImpl;
required final OverrideData overrideData,
required final RouteMode routeMode}) = _$ClashConfigStateImpl;
@override
bool get overrideDns;
@@ -3522,6 +3732,8 @@ abstract class _ClashConfigState implements ClashConfigState {
ClashConfig get clashConfig;
@override
OverrideData get overrideData;
@override
RouteMode get routeMode;
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.

View File

@@ -24,6 +24,17 @@ class VM3<A, B, C> with _$VM3<A, B, C> {
}) = _VM3;
}
@freezed
class VM4<A, B, C,D> with _$VM4<A, B, C,D> {
const factory VM4({
required A a,
required B b,
required C c,
required D d,
}) = _VM4;
}
@freezed
class StartButtonSelectorState with _$StartButtonSelectorState {
const factory StartButtonSelectorState({
@@ -146,12 +157,21 @@ class PackageListSelectorState with _$PackageListSelectorState {
}
extension PackageListSelectorStateExt on PackageListSelectorState {
List<Package> getList(List<String> selectedList) {
List<Package> get list {
final isFilterSystemApp = accessControl.isFilterSystemApp;
final sort = accessControl.sort;
final isFilterNonInternetApp = accessControl.isFilterNonInternetApp;
return packages
.where((item) => isFilterSystemApp ? item.isSystem == false : true)
.sorted(
.where(
(item) =>
(isFilterSystemApp ? item.system == false : true) &&
(isFilterNonInternetApp ? item.internet == true : true),
)
.toList();
}
List<Package> getSortList(List<String> selectedList) {
final sort = accessControl.sort;
return list.sorted(
(a, b) {
return switch (sort) {
AccessSortType.none => 0,
@@ -208,6 +228,7 @@ class ClashConfigState with _$ClashConfigState {
required bool overrideDns,
required ClashConfig clashConfig,
required OverrideData overrideData,
required RouteMode routeMode,
}) = _ClashConfigState;
}

View File

@@ -127,6 +127,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
);
},
popup: CommonPopupMenu(
minWidth: 180,
items: [
PopupMenuItemData(
icon: Icons.search,
@@ -189,7 +190,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
shortcutsActivatorsBuilder: DefaultCodeShortcutsActivatorsBuilder(),
controller: _controller,
style: CodeEditorStyle(
fontSize: 14,
fontSize: 14.ap,
fontFamily: FontFamily.jetBrainsMono.value,
codeTheme: CodeHighlightTheme(
languages: {

View File

@@ -190,43 +190,46 @@ class CommonNavigationBar extends ConsumerWidget {
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: IntrinsicHeight(
child: NavigationRail(
backgroundColor: context.colorScheme.surfaceContainer,
selectedIconTheme: IconThemeData(
color: context.colorScheme.onSurfaceVariant,
),
unselectedIconTheme: IconThemeData(
color: context.colorScheme.onSurfaceVariant,
),
selectedLabelTextStyle:
context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
unselectedLabelTextStyle:
context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
destinations: navigationItems
.map(
(e) => NavigationRailDestination(
icon: e.icon,
label: Text(
Intl.message(e.label.name),
child: ScrollConfiguration(
behavior: HiddenBarScrollBehavior(),
child: SingleChildScrollView(
child: IntrinsicHeight(
child: NavigationRail(
backgroundColor: context.colorScheme.surfaceContainer,
selectedIconTheme: IconThemeData(
color: context.colorScheme.onSurfaceVariant,
),
unselectedIconTheme: IconThemeData(
color: context.colorScheme.onSurfaceVariant,
),
selectedLabelTextStyle:
context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
unselectedLabelTextStyle:
context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
destinations: navigationItems
.map(
(e) => NavigationRailDestination(
icon: e.icon,
label: Text(
Intl.message(e.label.name),
),
),
),
)
.toList(),
onDestinationSelected: (index) {
globalState.appController
.toPage(navigationItems[index].label);
},
extended: false,
selectedIndex: currentIndex,
labelType: showLabel
? NavigationRailLabelType.all
: NavigationRailLabelType.none,
)
.toList(),
onDestinationSelected: (index) {
globalState.appController
.toPage(navigationItems[index].label);
},
extended: false,
selectedIndex: currentIndex,
labelType: showLabel
? NavigationRailLabelType.all
: NavigationRailLabelType.none,
),
),
),
),

View File

@@ -126,7 +126,7 @@ class Profiles extends _$Profiles with AutoDisposeNotifierMixin {
}
}
setProfile(Profile profile) {
setProfile(Profile profile, {bool force = true}) {
final List<Profile> profilesTemp = List.from(state);
final index =
profilesTemp.indexWhere((element) => element.id == profile.id);
@@ -135,7 +135,7 @@ class Profiles extends _$Profiles with AutoDisposeNotifierMixin {
);
if (index == -1) {
profilesTemp.add(updateProfile);
} else {
} else if (force == true) {
profilesTemp[index] = updateProfile;
}
state = profilesTemp;

View File

@@ -83,7 +83,7 @@ final themeSettingProvider =
);
typedef _$ThemeSetting = AutoDisposeNotifier<ThemeProps>;
String _$profilesHash() => r'a6514c89064e4f42fc31fe7d525088fd26c51016';
String _$profilesHash() => r'c416fda0f8deded24a715a8234efa0bcd0445449';
/// See also [Profiles].
@ProviderFor(Profiles)

View File

@@ -78,7 +78,7 @@ final coreStateProvider = AutoDisposeProvider<CoreState>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef CoreStateRef = AutoDisposeProviderRef<CoreState>;
String _$clashConfigStateHash() => r'848f6b2f734d99fb11ec05f73d614be415e9658f';
String _$clashConfigStateHash() => r'fbbcd7221b0b9b18db523e59c9021e8e56e119ca';
/// See also [clashConfigState].
@ProviderFor(clashConfigState)

View File

@@ -75,13 +75,15 @@ CoreState coreState(Ref ref) {
ClashConfigState clashConfigState(Ref ref) {
final clashConfig = ref.watch(patchClashConfigProvider);
final overrideDns = ref.watch(overrideDnsProvider);
final overrideData = ref.watch(currentProfileProvider.select(
(state) => state?.overrideData,
));
final overrideData =
ref.watch(currentProfileProvider.select((state) => state?.overrideData));
final routeMode =
ref.watch(networkSettingProvider.select((state) => state.routeMode));
return ClashConfigState(
overrideDns: overrideDns,
clashConfig: clashConfig,
overrideData: overrideData ?? OverrideData(),
routeMode: routeMode,
);
}

View File

@@ -30,6 +30,7 @@ class GlobalState {
late Config config;
late AppState appState;
bool isPre = true;
String? helperToken;
late PackageInfo packageInfo;
Function? updateCurrentDelayDebounce;
late Measure measure;
@@ -258,14 +259,17 @@ class GlobalState {
getUpdateConfigParams([bool? isPatch]) {
final currentProfile = config.currentProfile;
final clashConfig = config.patchClashConfig;
final routeAddress =
config.networkProps.routeMode == RouteMode.bypassPrivate
? defaultBypassPrivateRouteAddress
: clashConfig.tun.routeAddress;
return UpdateConfigParams(
profileId: config.currentProfileId ?? "",
config: clashConfig.copyWith(
globalUa: ua,
tun: clashConfig.tun.copyWith(
routeAddress: config.networkProps.routeMode == RouteMode.bypassPrivate
? defaultBypassPrivateRouteAddress
: clashConfig.tun.routeAddress,
autoRoute: routeAddress.isEmpty ? true : false,
routeAddress: routeAddress,
),
rule: currentProfile?.overrideData.runningRule ?? [],
),

View File

@@ -10,7 +10,7 @@ import 'package:flutter/services.dart';
import 'chip.dart';
class CommonScaffold extends StatefulWidget {
final PreferredSizeWidget? appBar;
final AppBar? appBar;
final Widget body;
final Widget? bottomNavigationBar;
final Widget? sideNavigationBar;
@@ -344,10 +344,10 @@ class CommonScaffoldState extends State<CommonScaffold> {
),
),
),
child: widget.appBar ??
Stack(
alignment: Alignment.bottomCenter,
children: [
child: Stack(
alignment: Alignment.bottomCenter,
children: [
widget.appBar ??
ValueListenableBuilder<AppBarState>(
valueListenable: _appBarState,
builder: (_, state, __) {
@@ -368,16 +368,16 @@ class CommonScaffoldState extends State<CommonScaffold> {
);
},
),
ValueListenableBuilder(
valueListenable: _loading,
builder: (_, value, __) {
return value == true
? const LinearProgressIndicator()
: Container();
},
),
],
ValueListenableBuilder(
valueListenable: _loading,
builder: (_, value, __) {
return value == true
? const LinearProgressIndicator()
: Container();
},
),
],
),
),
);
}

View File

@@ -279,7 +279,7 @@ packages:
source: hosted
version: "0.3.4+2"
crypto:
dependency: transitive
dependency: "direct dev"
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"

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.83+202504231
version: 0.8.83+202504254
environment:
sdk: '>=3.1.0 <4.0.0'
@@ -66,6 +66,7 @@ dev_dependencies:
riverpod_generator: ^2.6.3
custom_lint: ^0.7.0
riverpod_lint: ^2.6.3
crypto: ^3.0.3
flutter:
uses-material-design: true

View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@@ -284,6 +284,7 @@ dependencies = [
"anyhow",
"once_cell",
"serde",
"sha2",
"tokio",
"warp",
"windows-service",
@@ -822,6 +823,17 @@ dependencies = [
"digest",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"

View File

@@ -14,10 +14,11 @@ anyhow = "1.0.93"
warp = "0.3.7"
serde = { version = "1.0.215", features = ["derive"] }
once_cell = "1.20.2"
sha2 = "0.10.8"
[profile.release]
panic = "abort"
codegen-units = 1
lto = true
opt-level = "s"
opt-level = "s"

4
services/helper/build.rs Normal file
View File

@@ -0,0 +1,4 @@
fn main() {
let version = std::env::var("TOKEN").unwrap_or_default();
println!("cargo:rustc-env=TOKEN={}", version);
}

View File

@@ -1,11 +1,13 @@
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::VecDeque;
use std::{io, thread};
use std::io::BufRead;
use std::fs::File;
use std::io::{BufRead, Error, Read};
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::{io, thread};
use warp::{Filter, Reply};
use serde::{Deserialize, Serialize};
use once_cell::sync::Lazy;
const LISTEN_PORT: u16 = 47890;
@@ -15,10 +17,31 @@ pub struct StartParams {
pub arg: String,
}
static LOGS: Lazy<Arc<Mutex<VecDeque<String>>>> = Lazy::new(|| Arc::new(Mutex::new(VecDeque::with_capacity(100))));
static PROCESS: Lazy<Arc<Mutex<Option<std::process::Child>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
fn sha256_file(path: &str) -> Result<String, Error> {
let mut file = File::open(path)?;
let mut hasher = Sha256::new();
let mut buffer = [0; 4096];
loop {
let bytes_read = file.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
Ok(format!("{:x}", hasher.finalize()))
}
static LOGS: Lazy<Arc<Mutex<VecDeque<String>>>> =
Lazy::new(|| Arc::new(Mutex::new(VecDeque::with_capacity(100))));
static PROCESS: Lazy<Arc<Mutex<Option<std::process::Child>>>> =
Lazy::new(|| Arc::new(Mutex::new(None)));
fn start(start_params: StartParams) -> impl Reply {
if sha256_file(start_params.path.as_str()).unwrap_or("".to_string()) != env!("TOKEN") {
return "Only FlClashCore is allowed to run.".to_string();
}
stop();
let mut process = PROCESS.lock().unwrap();
match Command::new(&start_params.path)
@@ -73,38 +96,29 @@ fn log_message(message: String) {
fn get_logs() -> impl Reply {
let log_buffer = LOGS.lock().unwrap();
let value = log_buffer.iter().cloned().collect::<Vec<String>>().join("\n");
let value = log_buffer
.iter()
.cloned()
.collect::<Vec<String>>()
.join("\n");
warp::reply::with_header(value, "Content-Type", "text/plain")
}
pub async fn run_service() -> anyhow::Result<()> {
let api_ping = warp::get()
.and(warp::path("ping"))
.map(|| "2024125");
let api_ping = warp::get().and(warp::path("ping")).map(|| env!("TOKEN"));
let api_start = warp::post()
.and(warp::path("start"))
.and(warp::body::json())
.map(|start_params: StartParams| {
start(start_params)
});
.map(|start_params: StartParams| start(start_params));
let api_stop = warp::post()
.and(warp::path("stop"))
.map(|| stop());
let api_stop = warp::post().and(warp::path("stop")).map(|| stop());
let api_logs = warp::get()
.and(warp::path("logs"))
.map(|| get_logs());
let api_logs = warp::get().and(warp::path("logs")).map(|| get_logs());
warp::serve(
api_ping
.or(api_start)
.or(api_stop)
.or(api_logs)
)
warp::serve(api_ping.or(api_start).or(api_stop).or(api_logs))
.run(([127, 0, 0, 1], LISTEN_PORT))
.await;
Ok(())
}
}

View File

@@ -5,6 +5,7 @@ import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:path/path.dart';
import 'package:crypto/crypto.dart';
enum Target {
windows,
@@ -195,7 +196,16 @@ class Build {
if (exitCode != 0 && name != null) throw "$name error";
}
static buildCore({
static Future<String?> calcSha256(String filePath) async {
final file = File(filePath);
if (!await file.exists()) {
return null;
}
final stream = file.openRead();
return sha256.convert(await stream.reduce((a, b) => a + b)).toString();
}
static Future<List<String>> buildCore({
required Mode mode,
required Target target,
Arch? arch,
@@ -209,6 +219,8 @@ class Build {
},
).toList();
final List<String> corePaths = [];
for (final item in items) {
final outFileDir = join(
outDir,
@@ -228,6 +240,7 @@ class Build {
outFileDir,
fileName,
);
corePaths.add(outPath);
final Map<String, String> env = {};
env["GOOS"] = item.target.os;
@@ -258,9 +271,11 @@ class Build {
workingDirectory: _coreDir,
);
}
return corePaths;
}
static buildHelper(Target target) async {
static buildHelper(Target target, String token) async {
await exec(
[
"cargo",
@@ -269,6 +284,9 @@ class Build {
"--features",
"windows-service",
],
environment: {
"TOKEN": token,
},
name: "build helper",
workingDirectory: _servicesDir,
);
@@ -278,13 +296,15 @@ class Build {
"release",
"helper${target.executableExtensionName}",
);
final targetPath = join(outDir, target.name,
"FlClashHelperService${target.executableExtensionName}");
final targetPath = join(
outDir,
target.name,
"FlClashHelperService${target.executableExtensionName}",
);
await File(outPath).copy(targetPath);
}
static List<String> getExecutable(String command) {
print(command);
return command.split(" ");
}
@@ -466,26 +486,28 @@ class BuildCommand extends Command {
throw "Invalid arch parameter";
}
await Build.buildCore(
final corePaths = await Build.buildCore(
target: target,
arch: arch,
mode: mode,
);
if (target == Target.windows) {
await Build.buildHelper(target);
}
if (out != "app") {
return;
}
switch (target) {
case Target.windows:
final token = await Build.calcSha256(corePaths.first);
if (token == null) {
throw "Core not exists";
}
Build.buildHelper(target, token);
_buildDistributor(
target: target,
targets: "exe,zip",
args: " --description $archName",
args:
" --description $archName --build-dart-define=HELPER_TOKEN=$token",
env: env,
);
return;