Compare commits

..

1 Commits

Author SHA1 Message Date
chen08209
f679d1e6d8 Optimize request ua
Change android icon

Optimize dashboard
2024-07-20 18:04:59 +08:00
88 changed files with 850 additions and 1908 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,26 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="240"
android:viewportHeight="240"
android:tint="#FFFFFF">
<group android:scaleX="2.0264318"
android:scaleY="2.0264318"
android:translateX="-123.17181"
android:translateY="-123.17181">
<group android:scaleX="0.61"
android:scaleY="0.61"
android:translateX="46.8"
android:translateY="46.8">
<path
android:pathData="M67.67,115.76L144.04,39.39A18,18 0,0 1,169.5 39.39L169.5,39.39A18,18 0,0 1,169.5 64.85L93.13,141.21A18,18 0,0 1,67.67 141.21L67.67,141.21A18,18 0,0 1,67.67 115.76z"
android:fillColor="#6666FF"/>
<path
android:pathData="M98.79,146.87L136.97,108.69A18,18 72.93,0 1,162.43 108.69L162.43,108.69A18,18 72.93,0 1,162.43 134.14L124.24,172.33A18,18 76.87,0 1,98.79 172.33L98.79,172.33A18,18 76.87,0 1,98.79 146.87z"
android:fillColor="#326CBF"/>
<path
android:pathData="M129.9,177.98L129.9,177.98A18,18 107.07,0 1,155.36 177.98L155.36,177.98A18,18 107.07,0 1,155.36 203.44L155.36,203.44A18,18 107.07,0 1,129.9 203.44L129.9,203.44A18,18 107.07,0 1,129.9 177.98z"
android:fillColor="#56afee"/>
</group>
</group>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 618 B

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 423 B

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 B

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -3,23 +3,23 @@
android:height="108dp"
android:viewportWidth="240"
android:viewportHeight="240">
<group android:scaleX="0.924"
android:scaleY="0.924"
android:translateX="9.12"
android:translateY="9.12">
<group android:scaleX="0.63461536"
android:scaleY="0.63461536"
android:translateX="45.96154"
android:translateY="43.846153">
<group android:scaleX="0.90244895"
android:scaleY="0.90244895"
android:translateX="12.708843"
android:translateY="10.703402">
<group android:scaleX="0.61"
android:scaleY="0.61"
android:translateX="46.8"
android:translateY="46.8">
<path
android:pathData="M60.65,89.6L154.18,35.6A18,18 107.59,0 1,178.77 42.19L178.77,42.19A18,18 107.59,0 1,172.18 66.78L78.65,120.78A18,18 106.67,0 1,54.06 114.19L54.06,114.19A18,18 106.67,0 1,60.65 89.6z"
android:fillColor="#6666FB"/>
android:pathData="M67.67,115.76L144.04,39.39A18,18 0,0 1,169.5 39.39L169.5,39.39A18,18 0,0 1,169.5 64.85L93.13,141.21A18,18 0,0 1,67.67 141.21L67.67,141.21A18,18 0,0 1,67.67 115.76z"
android:fillColor="#6666FF"/>
<path
android:pathData="M84.65,131.17L131.42,104.17A18,18 107.83,0 1,156 110.76L156,110.76A18,18 107.83,0 1,149.42 135.35L102.65,162.35A18,18 106.67,0 1,78.06 155.76L78.06,155.76A18,18 106.67,0 1,84.65 131.17z"
android:fillColor="#336AB6"/>
android:pathData="M98.79,146.87L136.97,108.69A18,18 72.93,0 1,162.43 108.69L162.43,108.69A18,18 72.93,0 1,162.43 134.14L124.24,172.33A18,18 76.87,0 1,98.79 172.33L98.79,172.33A18,18 76.87,0 1,98.79 146.87z"
android:fillColor="#326CBF"/>
<path
android:pathData="M108.65,172.74L108.65,172.74A18,18 116.03,0 1,133.24 179.33L133.24,179.33A18,18 116.03,0 1,126.65 203.92L126.65,203.92A18,18 116.03,0 1,102.06 197.33L102.06,197.33A18,18 116.03,0 1,108.65 172.74z"
android:fillColor="#5CA8E9"/>
android:pathData="M129.9,177.98L129.9,177.98A18,18 107.07,0 1,155.36 177.98L155.36,177.98A18,18 107.07,0 1,155.36 203.44L155.36,203.44A18,18 107.07,0 1,129.9 203.44L129.9,203.44A18,18 107.07,0 1,129.9 177.98z"
android:fillColor="#56afee"/>
</group>
</group>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 886 B

After

Width:  |  Height:  |  Size: 812 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
assets/images/app_icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

View File

@@ -423,7 +423,6 @@ func applyConfig() {
if configParams.IsPatch {
patchConfig(cfg.General)
} else {
closeConnections()
runtime.GC()
hub.UltraApplyConfig(cfg, true)
patchSelectGroup()

View File

@@ -299,7 +299,7 @@ func getConnections() *C.char {
}
//export closeConnections
func closeConnections() {
func closeConnections() bool {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
err := c.Close()
if err != nil {
@@ -307,16 +307,17 @@ func closeConnections() {
}
return true
})
return true
}
//export closeConnection
func closeConnection(id *C.char) {
func closeConnection(id *C.char) bool {
connectionId := C.GoString(id)
c := statistic.DefaultManager.Get(connectionId)
if c == nil {
return
err := statistic.DefaultManager.Get(connectionId).Close()
if err != nil {
return false
}
_ = c.Close()
return true
}
//export getProviders

View File

@@ -157,7 +157,9 @@ class ApplicationState extends State<Application> {
GlobalWidgetsLocalizations.delegate
],
builder: (_, child) {
return _buildApp(child!);
return PopContainer(
child: _buildApp(child!),
);
},
scrollBehavior: BaseScrollBehavior(),
title: appName,

View File

@@ -319,15 +319,11 @@ class ClashCore {
return connectionsRaw.map((e) => Connection.fromJson(e)).toList();
}
closeConnection(String id) {
closeConnections(String id) {
final idChar = id.toNativeUtf8().cast<Char>();
clashFFI.closeConnection(idChar);
malloc.free(idChar);
}
closeConnections() {
clashFFI.closeConnections();
}
}
final clashCore = ClashCore();

View File

@@ -5351,16 +5351,16 @@ class ClashFFI {
late final _getConnections =
_getConnectionsPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
void closeConnections() {
int closeConnections() {
return _closeConnections();
}
late final _closeConnectionsPtr =
_lookup<ffi.NativeFunction<ffi.Void Function()>>('closeConnections');
_lookup<ffi.NativeFunction<GoUint8 Function()>>('closeConnections');
late final _closeConnections =
_closeConnectionsPtr.asFunction<void Function()>();
_closeConnectionsPtr.asFunction<int Function()>();
void closeConnection(
int closeConnection(
ffi.Pointer<ffi.Char> id,
) {
return _closeConnection(
@@ -5369,10 +5369,10 @@ class ClashFFI {
}
late final _closeConnectionPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
_lookup<ffi.NativeFunction<GoUint8 Function(ffi.Pointer<ffi.Char>)>>(
'closeConnection');
late final _closeConnection =
_closeConnectionPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
_closeConnectionPtr.asFunction<int Function(ffi.Pointer<ffi.Char>)>();
ffi.Pointer<ffi.Char> getProviders() {
return _getProviders();

View File

@@ -10,58 +10,4 @@ extension IterableExt<T> on Iterable<T> {
yield iterator.current;
}
}
Iterable<List<T>> chunks(int size) sync* {
if (length == 0) return;
var iterator = this.iterator;
while (iterator.moveNext()) {
var chunk = [iterator.current];
for (var i = 1; i < size && iterator.moveNext(); i++) {
chunk.add(iterator.current);
}
yield chunk;
}
}
Iterable<T> fill(
int length, {
required T Function(int count) filler,
}) sync* {
int count = 0;
for (var item in this) {
yield item;
count++;
if (count >= length) return;
}
while (count < length) {
yield filler(count);
count++;
}
}
}
extension DoubleListExt on List<double> {
int findInterval(num target) {
if (isEmpty) return -1;
if (target < first) return -1;
if (target >= last) return length - 1;
int left = 0;
int right = length - 1;
while (left <= right) {
int mid = left + (right - left) ~/ 2;
if (mid == length - 1 ||
(this[mid] <= target && target < this[mid + 1])) {
return mid;
} else if (target < this[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1; // 这行理论上不会执行到,但为了完整性保留
}
}

View File

@@ -101,9 +101,9 @@ class Other {
String getTrayIconPath() {
if (Platform.isWindows) {
return "assets/images/icon.ico";
return "assets/images/app_icon.ico";
} else {
return "assets/images/icon_monochrome.png";
return "assets/images/launch_icon.png";
}
}

View File

@@ -14,14 +14,3 @@ class BaseScrollBehavior extends MaterialScrollBehavior {
PointerDeviceKind.unknown,
};
}
class HiddenBarScrollBehavior extends BaseScrollBehavior {
@override
Widget buildScrollbar(
BuildContext context,
Widget child,
ScrollableDetails details,
) {
return child;
}
}

View File

@@ -19,7 +19,7 @@ class Window {
await windowManager.ensureInitialized();
WindowOptions windowOptions = WindowOptions(
size: Size(props.width, props.height),
minimumSize: const Size(380, 500),
minimumSize: const Size(380, 600),
titleBarStyle: TitleBarStyle.hidden,
);
if (props.left != null || props.top != null) {

View File

@@ -90,7 +90,7 @@ class AppController {
final updateId = config.profiles.first.id;
changeProfile(updateId);
} else {
updateSystemProxy(false);
changeProfile(null);
}
}
}
@@ -193,7 +193,6 @@ class AppController {
}
handleBackOrExit() async {
print(config.isMinimizeOnExit);
if (config.isMinimizeOnExit) {
if (system.isDesktop) {
await savePreferences();
@@ -411,7 +410,8 @@ class AppController {
addProfileFormURL(url);
}
int get columns => other.getColumns(appState.viewMode, config.proxiesColumns);
int get columns =>
other.getColumns(appState.viewMode, config.proxiesColumns);
updateViewWidth(double width) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -453,9 +453,4 @@ class AppController {
ProxiesSortType.name => _sortOfName(proxies),
};
}
String getCurrentSelectedName(String groupName) {
final group = appState.getGroupWithName(groupName);
return config.currentSelectedMap[groupName] ?? group?.now ?? '';
}
}

View File

@@ -1,20 +1,7 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/list.dart';
import 'package:flutter/material.dart';
@immutable
class Contributor {
final String avatar;
final String name;
final String link;
const Contributor({
required this.avatar,
required this.name,
required this.link,
});
}
import 'package:url_launcher/url_launcher.dart';
class AboutFragment extends StatelessWidget {
const AboutFragment({super.key});
@@ -22,7 +9,8 @@ class AboutFragment extends StatelessWidget {
_checkUpdate(BuildContext context) async {
final commonScaffoldState = context.commonScaffoldState;
if (commonScaffoldState?.mounted != true) return;
final data = await commonScaffoldState?.loadingRun<Map<String, dynamic>?>(
final data =
await commonScaffoldState?.loadingRun<Map<String, dynamic>?>(
request.checkForUpdate,
title: appLocalizations.checkUpdate,
);
@@ -32,40 +20,84 @@ class AboutFragment extends StatelessWidget {
);
}
List<Widget> _buildMoreSection(BuildContext context) {
return generateSection(
separated: false,
title: appLocalizations.more,
items: [
ListItem(
@override
Widget build(BuildContext context) {
return ListView(
padding: kMaterialListPadding.copyWith(
top: 16,
bottom: 16,
),
children: [
ListTile(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Image.asset(
'assets/images/launch_icon.png',
width: 100,
height: 100,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appName,
style: Theme.of(context).textTheme.headlineSmall,
),
Text(
globalState.packageInfo.version,
style: Theme.of(context).textTheme.labelLarge,
)
],
)
],
),
const SizedBox(
height: 24,
),
Text(
appLocalizations.desc,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
const SizedBox(
height: 12,
),
ListTile(
title: Text(appLocalizations.checkUpdate),
onTap: () {
_checkUpdate(context);
},
),
ListItem(
ListTile(
title: const Text("Telegram"),
onTap: () {
globalState.openUrl(
"https://t.me/+G-veVtwBOl4wODc1",
launchUrl(
Uri.parse("https://t.me/+G-veVtwBOl4wODc1"),
);
},
trailing: const Icon(Icons.launch),
),
ListItem(
ListTile(
title: Text(appLocalizations.project),
onTap: () {
globalState.openUrl(
"https://github.com/$repository",
launchUrl(
Uri.parse("https://github.com/$repository"),
);
},
trailing: const Icon(Icons.launch),
),
ListItem(
ListTile(
title: Text(appLocalizations.core),
onTap: () {
globalState.openUrl(
"https://github.com/chen08209/Clash.Meta/tree/FlClash",
launchUrl(
Uri.parse("https://github.com/chen08209/Clash.Meta/tree/FlClash"),
);
},
trailing: const Icon(Icons.launch),
@@ -73,139 +105,4 @@ class AboutFragment extends StatelessWidget {
],
);
}
List<Widget> _buildContributorsSection() {
const contributors = [
Contributor(
avatar: "assets/images/avatars/june2.jpg",
name: "June2",
link: "https://t.me/Jibadong",
),
Contributor(
avatar: "assets/images/avatars/arue.jpg",
name: "Arue",
link: "https://t.me/xrcm6868",
),
];
return generateSection(
separated: false,
title: appLocalizations.otherContributors,
items: [
ListItem(
title: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Wrap(
spacing: 24,
children: [
for (final contributor in contributors)
Avatar(
contributor: contributor,
),
],
),
),
)
],
);
}
@override
Widget build(BuildContext context) {
final items = [
ListTile(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(12),
child: Image.asset(
'assets/images/icon.png',
width: 64,
height: 64,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appName,
style: Theme.of(context).textTheme.headlineSmall,
),
Text(
globalState.packageInfo.version,
style: Theme.of(context).textTheme.labelLarge,
)
],
)
],
),
const SizedBox(
height: 24,
),
Text(
appLocalizations.desc,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
const SizedBox(
height: 12,
),
..._buildContributorsSection(),
..._buildMoreSection(context),
];
return Padding(
padding: kMaterialListPadding.copyWith(
top: 16,
bottom: 16,
),
child: generateListView(items),
);
}
}
class Avatar extends StatelessWidget {
final Contributor contributor;
const Avatar({
super.key,
required this.contributor,
});
@override
Widget build(BuildContext context) {
return InkWell(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
child: Column(
children: [
SizedBox(
width: 36,
height: 36,
child: CircleAvatar(
foregroundImage: AssetImage(
contributor.avatar,
),
),
),
const SizedBox(
height: 4,
),
Text(
contributor.name,
style: context.textTheme.bodySmall,
)
],
),
onTap: () {
globalState.openUrl(contributor.link);
},
);
}
}

View File

@@ -113,7 +113,8 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
return Center(
child: FadeBox(
key: const Key("fade_box_1"),
child: snapshot.connectionState == ConnectionState.waiting
child: snapshot.connectionState ==
ConnectionState.waiting
? const SizedBox(
width: 12,
height: 12,
@@ -158,12 +159,12 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
ListHeader(
title: appLocalizations.backupAndRecovery),
ListItem(
onTap: _backup,
onTab: _backup,
title: Text(appLocalizations.backup),
subtitle: Text(appLocalizations.backupDesc),
),
ListItem(
onTap: _handleRecovery,
onTab: _handleRecovery,
title: Text(appLocalizations.recovery),
subtitle: Text(appLocalizations.recoveryDesc),
),
@@ -340,13 +341,13 @@ class _RecoveryOptionsDialogState extends State<RecoveryOptionsDialog> {
child: Wrap(
children: [
ListItem(
onTap: () {
onTab: () {
_handleOnTab(RecoveryOption.onlyProfiles);
},
title: Text(appLocalizations.recoveryProfiles),
),
ListItem(
onTap: () {
onTab: () {
_handleOnTab(RecoveryOption.all);
},
title: Text(appLocalizations.recoveryAll),

View File

@@ -141,7 +141,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
return generateSection(
title: appLocalizations.app,
items: [
if (Platform.isAndroid)...[
if (Platform.isAndroid)
Selector<Config, bool>(
selector: (_, config) => config.allowBypass,
builder: (_, allowBypass, __) {
@@ -159,6 +159,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
);
},
),
if (Platform.isAndroid)
Selector<Config, bool>(
selector: (_, config) => config.systemProxy,
builder: (_, systemProxy, __) {
@@ -176,24 +177,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
);
},
),
],
Selector<Config, bool>(
selector: (_, config) => config.isCloseConnections,
builder: (_, isCloseConnections, __) {
return ListItem.switchItem(
leading: const Icon(Icons.auto_delete_outlined),
title: Text(appLocalizations.autoCloseConnections),
subtitle: Text(appLocalizations.autoCloseConnectionsDesc),
delegate: SwitchDelegate(
value: isCloseConnections,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.config.isCloseConnections = value;
},
),
);
},
),
Selector<Config, bool>(
selector: (_, config) => config.isCompatible,
builder: (_, isCompatible, __) {
@@ -227,7 +210,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
leading: const Icon(Icons.info_outline),
title: Text(appLocalizations.logLevel),
subtitle: Text(value.name),
onTap: () {
onTab: () {
_showLogLevelDialog(value);
},
);
@@ -240,7 +223,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
leading: const Icon(Icons.computer_outlined),
title: const Text("UA"),
subtitle: Text(value ?? appLocalizations.defaultText),
onTap: () {
onTab: () {
_showUaDialog(value);
},
);
@@ -253,7 +236,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
leading: const Icon(Icons.timeline),
title: Text(appLocalizations.testUrl),
subtitle: Text(value),
onTap: () {
onTab: () {
_modifyTestUrl(value);
},
);
@@ -263,7 +246,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
selector: (_, clashConfig) => clashConfig.mixedPort,
builder: (_, mixedPort, __) {
return ListItem(
onTap: () {
onTab: () {
_modifyMixedPort(mixedPort);
},
leading: const Icon(Icons.adjust_outlined),

View File

@@ -37,9 +37,8 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
timer = Timer.periodic(
const Duration(seconds: 1),
(timer) {
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: clashCore.getConnections(),
);
connectionsNotifier.value = connectionsNotifier.value
.copyWith(connections: clashCore.getConnections());
},
);
});
@@ -51,18 +50,6 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [
IconButton(
onPressed: () {
clashCore.closeConnections();
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: clashCore.getConnections(),
);
},
icon: const Icon(Icons.delete_sweep_outlined),
),
const SizedBox(
width: 8,
),
IconButton(
onPressed: () {
showSearch(
@@ -100,7 +87,7 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
}
_handleBlockConnection(String id) {
clashCore.closeConnection(id);
clashCore.closeConnections(id);
connectionsNotifier.value = connectionsNotifier.value
.copyWith(connections: clashCore.getConnections());
}
@@ -240,7 +227,7 @@ class ConnectionsSearchDelegate extends SearchDelegate {
}
_handleBlockConnection(String id) {
clashCore.closeConnection(id);
clashCore.closeConnections(id);
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: clashCore.getConnections(),
);

View File

@@ -35,7 +35,7 @@ class _DashboardFragmentState extends State<DashboardFragment> {
// final viewMode = other.getViewMode(viewWidth);
// final isDesktop = viewMode == ViewMode.desktop;
return Grid(
crossAxisCount: max(4 * ((viewWidth / 350).ceil()), 8),
crossAxisCount: max(4 * ((viewWidth / 320).ceil()), 8),
crossAxisSpacing: 16,
mainAxisSpacing: 16,
children: const [

View File

@@ -48,19 +48,19 @@ class AddProfile extends StatelessWidget {
leading: const Icon(Icons.qr_code),
title: Text(appLocalizations.qrcode),
subtitle: Text(appLocalizations.qrcodeDesc),
onTap: _toScan,
onTab: _toScan,
),
ListItem(
leading: const Icon(Icons.upload_file),
title: Text(appLocalizations.file),
subtitle: Text(appLocalizations.fileDesc),
onTap: _handleAddProfileFormFile,
onTab: _handleAddProfileFormFile,
),
ListItem(
leading: const Icon(Icons.cloud_download),
title: Text(appLocalizations.url),
subtitle: Text(appLocalizations.urlDesc),
onTap: _toAdd,
onTab: _toAdd,
)
],
);

View File

@@ -313,6 +313,13 @@ class _ProfileItemState extends State<ProfileItem> {
),
Row(
children: [
Text(
appLocalizations.expirationTime,
style: textTheme.labelMedium?.toLighter,
),
const SizedBox(
width: 4,
),
Text(
expireShow,
style: textTheme.labelMedium?.toLighter,

View File

View File

@@ -105,10 +105,11 @@ class ProxyCard extends StatelessWidget {
groupName,
proxy.name,
);
globalState.changeProxy(
config: appController.config,
groupName: groupName,
proxyName: proxy.name,
clashCore.changeProxy(
ChangeProxyParams(
groupName: groupName,
proxyName: proxy.name,
),
);
}

View File

@@ -1,75 +0,0 @@
import 'dart:math';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/constant.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
Widget currentProxyNameBuilder({
required String groupName,
required Widget Function(String) builder,
}) {
return Selector2<AppState, Config, String>(
selector: (_, appState, config) {
final group = appState.getGroupWithName(groupName);
return config.currentSelectedMap[groupName] ?? group?.now ?? '';
},
builder: (_, value, ___) {
return builder(value);
},
);
}
double get listHeaderHeight {
final measure = globalState.appController.measure;
return 24 + measure.titleMediumHeight + 4 + measure.bodyMediumHeight;
}
double getItemHeight(ProxyCardType proxyCardType) {
final measure = globalState.appController.measure;
final baseHeight =
12 * 2 + measure.bodyMediumHeight * 2 + measure.bodySmallHeight + 8;
return switch (proxyCardType) {
ProxyCardType.expand => baseHeight + measure.labelSmallHeight + 8,
ProxyCardType.shrink => baseHeight,
ProxyCardType.min => baseHeight - measure.bodyMediumHeight,
};
}
delayTest(List<Proxy> proxies) async {
final appController = globalState.appController;
for (final proxy in proxies) {
final proxyName =
appController.appState.getRealProxyName(proxy.name) ?? proxy.name;
globalState.appController.setDelay(
Delay(
name: proxyName,
value: 0,
),
);
clashCore.getDelay(proxyName).then((delay) {
globalState.appController.setDelay(delay);
});
}
await Future.delayed(httpTimeoutDuration + moreDuration);
appController.appState.sortNum++;
}
double getScrollToSelectedOffset({
required String groupName,
required List<Proxy> proxies,
}) {
final appController = globalState.appController;
final columns = appController.columns;
final proxyCardType = appController.config.proxyCardType;
final selectedName = appController.getCurrentSelectedName(groupName);
final findSelectedIndex = proxies.indexWhere(
(proxy) => proxy.name == selectedName,
);
final selectedIndex = findSelectedIndex != -1 ? findSelectedIndex : 0;
final rows = ((selectedIndex - 1) / columns).ceil();
return max(rows * (getItemHeight(proxyCardType) + 8) - 8, 0);
}

View File

@@ -0,0 +1,404 @@
import 'dart:math';
import 'package:fl_clash/clash/clash.dart';
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/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'card.dart';
class ProxyGroupView extends StatefulWidget {
final String groupName;
final ProxiesType type;
const ProxyGroupView({
super.key,
required this.groupName,
required this.type,
});
@override
State<ProxyGroupView> createState() => _ProxyGroupViewState();
}
class _ProxyGroupViewState extends State<ProxyGroupView> {
var isLock = false;
final scrollController = ScrollController();
var isEnd = false;
String get groupName => widget.groupName;
ProxiesType get type => widget.type;
double _getItemHeight(ProxyCardType proxyCardType) {
final measure = globalState.appController.measure;
final baseHeight =
12 * 2 + measure.bodyMediumHeight * 2 + measure.bodySmallHeight + 8;
return switch(proxyCardType){
ProxyCardType.expand => baseHeight + measure.labelSmallHeight + 8,
ProxyCardType.shrink => baseHeight,
ProxyCardType.min => baseHeight - measure.bodyMediumHeight,
};
}
_delayTest(List<Proxy> proxies) async {
if (isLock) return;
isLock = true;
final appController = globalState.appController;
for (final proxy in proxies) {
final proxyName =
appController.appState.getRealProxyName(proxy.name) ?? proxy.name;
globalState.appController.setDelay(
Delay(
name: proxyName,
value: 0,
),
);
clashCore.getDelay(proxyName).then((delay) {
globalState.appController.setDelay(delay);
});
}
await Future.delayed(httpTimeoutDuration + moreDuration);
appController.appState.sortNum++;
isLock = false;
}
Widget _currentProxyNameBuilder({
required Widget Function(String) builder,
}) {
return Selector2<AppState, Config, String>(
selector: (_, appState, config) {
final group = appState.getGroupWithName(groupName)!;
return config.currentSelectedMap[groupName] ?? group.now ?? '';
},
builder: (_, value, ___) {
return builder(value);
},
);
}
Widget _buildTabGroupView({
required List<Proxy> proxies,
required int columns,
required ProxyCardType proxyCardType,
}) {
final sortedProxies = globalState.appController.getSortProxies(
proxies,
);
return DelayTestButtonContainer(
onClick: () async {
await _delayTest(
proxies,
);
},
child: Align(
alignment: Alignment.topCenter,
child: GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
mainAxisExtent: _getItemHeight(proxyCardType),
),
itemCount: sortedProxies.length,
itemBuilder: (_, index) {
final proxy = sortedProxies[index];
return _currentProxyNameBuilder(builder: (value) {
return ProxyCard(
type: proxyCardType,
key: ValueKey('$groupName.${proxy.name}'),
isSelected: value == proxy.name,
proxy: proxy,
groupName: groupName,
);
});
},
),
),
);
}
Widget _buildExpansionGroupView({
required List<Proxy> proxies,
required int columns,
required ProxyCardType proxyCardType,
}) {
final sortedProxies = globalState.appController.getSortProxies(
proxies,
);
final group =
globalState.appController.appState.getGroupWithName(groupName)!;
final itemHeight = _getItemHeight(proxyCardType);
final innerHeight = context.appSize.height - 200;
final lines = (sortedProxies.length / columns).ceil();
final minLines =
innerHeight >= 200 ? (innerHeight / itemHeight).floor() : 3;
final height = (itemHeight + 8) * min(lines, minLines) - 8;
return Selector<Config, Set<String>>(
selector: (_, config) => config.currentUnfoldSet,
builder: (_, currentUnfoldSet, __) {
return CommonCard(
child: ExpansionTile(
childrenPadding: const EdgeInsets.all(8),
initiallyExpanded: currentUnfoldSet.contains(groupName),
iconColor: context.colorScheme.onSurfaceVariant,
onExpansionChanged: (value) {
final tempUnfoldSet = Set<String>.from(currentUnfoldSet);
if (value) {
tempUnfoldSet.add(groupName);
} else {
tempUnfoldSet.remove(groupName);
}
globalState.appController.config.updateCurrentUnfoldSet(
tempUnfoldSet,
);
},
controlAffinity: ListTileControlAffinity.trailing,
title: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 1,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(groupName),
const SizedBox(
height: 4,
),
Flexible(
flex: 1,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
group.type.name,
style: context.textTheme.labelMedium?.toLight,
),
Flexible(
flex: 1,
child: _currentProxyNameBuilder(
builder: (value) {
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
if (value.isNotEmpty) ...[
Icon(
Icons.arrow_right,
color: context
.colorScheme.onSurfaceVariant,
),
Flexible(
flex: 1,
child: Text(
overflow: TextOverflow.ellipsis,
value,
style: context
.textTheme.labelMedium?.toLight,
),
),
]
],
);
},
),
),
],
),
),
const SizedBox(
height: 4,
),
],
),
),
IconButton(
icon: Icon(
Icons.network_ping,
size: 20,
color: context.colorScheme.onSurfaceVariant,
),
onPressed: () {
_delayTest(sortedProxies);
},
),
],
),
shape: const RoundedRectangleBorder(
side: BorderSide.none,
),
collapsedShape: const RoundedRectangleBorder(
side: BorderSide.none,
),
children: [
SizedBox(
height: height,
child: GridView.builder(
key: widget.key,
controller: scrollController,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
mainAxisExtent: _getItemHeight(proxyCardType),
),
itemCount: sortedProxies.length,
itemBuilder: (_, index) {
final proxy = sortedProxies[index];
return _currentProxyNameBuilder(
builder: (value) {
return ProxyCard(
style: CommonCardType.filled,
type: proxyCardType,
isSelected: value == proxy.name,
key: ValueKey('$groupName.${proxy.name}'),
proxy: proxy,
groupName: groupName,
);
},
);
},
),
),
],
),
);
},
);
}
@override
void dispose() {
super.dispose();
scrollController.dispose();
}
@override
Widget build(BuildContext context) {
return Selector2<AppState, Config, ProxyGroupSelectorState>(
selector: (_, appState, config) {
final group = appState.getGroupWithName(groupName)!;
return ProxyGroupSelectorState(
proxyCardType: config.proxyCardType,
proxiesSortType: config.proxiesSortType,
columns: globalState.appController.columns,
sortNum: appState.sortNum,
proxies: group.all,
);
},
builder: (_, state, __) {
final proxies = state.proxies;
final columns = state.columns;
final proxyCardType = state.proxyCardType;
return switch (type) {
ProxiesType.tab => _buildTabGroupView(
proxies: proxies,
columns: columns,
proxyCardType: proxyCardType,
),
ProxiesType.list => _buildExpansionGroupView(
proxies: proxies,
columns: columns,
proxyCardType: proxyCardType,
),
};
},
);
}
}
class DelayTestButtonContainer extends StatefulWidget {
final Widget child;
final Future Function() onClick;
const DelayTestButtonContainer({
super.key,
required this.child,
required this.onClick,
});
@override
State<DelayTestButtonContainer> createState() =>
_DelayTestButtonContainerState();
}
class _DelayTestButtonContainerState extends State<DelayTestButtonContainer>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scale;
_healthcheck() async {
_controller.forward();
await widget.onClick();
_controller.reverse();
}
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 200,
),
);
_scale = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0,
1,
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_controller.reverse();
return FloatLayout(
floatingWidget: FloatWrapper(
child: AnimatedBuilder(
animation: _controller.view,
builder: (_, child) {
return SizedBox(
width: 56,
height: 56,
child: Transform.scale(
scale: _scale.value,
child: child,
),
);
},
child: FloatingActionButton(
heroTag: null,
onPressed: _healthcheck,
child: const Icon(Icons.network_ping),
),
),
),
child: widget.child,
);
}
}

View File

@@ -1,512 +1,50 @@
import 'package:collection/collection.dart';
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/state.dart';
import 'package:fl_clash/widgets/card.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'card.dart';
import 'common.dart';
typedef GroupNameProxiesMap = Map<String, List<Proxy>>;
import 'group.dart';
class ProxiesListFragment extends StatefulWidget {
const ProxiesListFragment({super.key});
@override
State<ProxiesListFragment> createState() => _ProxiesListFragmentState();
State<ProxiesListFragment> createState() =>
_ProxiesListFragmentState();
}
class _ProxiesListFragmentState extends State<ProxiesListFragment> {
final _controller = ScrollController();
final _headerStateNotifier = ValueNotifier<ProxiesListHeaderSelectorState>(
const ProxiesListHeaderSelectorState(
offset: 0,
currentIndex: 0,
),
);
List<double> _headerOffset = [];
GroupNameProxiesMap _lastGroupNameProxiesMap = {};
@override
void initState() {
super.initState();
_controller.addListener(_adjustHeader);
}
_adjustHeader() {
final offset = _controller.offset;
final index = _headerOffset.findInterval(offset);
final currentIndex = index;
double headerOffset = 0.0;
if (index + 1 <= _headerOffset.length - 1) {
final endOffset = _headerOffset[index + 1];
final startOffset = endOffset - listHeaderHeight - 8;
if (offset > startOffset && offset < endOffset) {
headerOffset = offset - startOffset;
}
}
_headerStateNotifier.value = _headerStateNotifier.value.copyWith(
currentIndex: currentIndex,
offset: headerOffset,
);
}
double _getListItemHeight(Type type, ProxyCardType proxyCardType) {
return switch (type) {
const (SizedBox) => 8,
const (ListHeader) => listHeaderHeight,
Type() => getItemHeight(proxyCardType),
};
}
@override
void dispose() {
super.dispose();
_headerStateNotifier.dispose();
_controller.removeListener(_adjustHeader);
_controller.dispose();
}
_handleChange(Set<String> currentUnfoldSet, String groupName) {
final tempUnfoldSet = Set<String>.from(currentUnfoldSet);
if (tempUnfoldSet.contains(groupName)) {
tempUnfoldSet.remove(groupName);
} else {
tempUnfoldSet.add(groupName);
}
globalState.appController.config.updateCurrentUnfoldSet(
tempUnfoldSet,
);
WidgetsBinding.instance.addPostFrameCallback((_) {
_adjustHeader();
});
}
List<double> _getItemHeightList(
List<Widget> items,
ProxyCardType proxyCardType,
) {
final itemHeightList = <double>[];
List<double> headerOffset = [];
double currentHeight = 0;
for (final item in items) {
if (item.runtimeType == ListHeader) {
headerOffset.add(currentHeight);
}
final itemHeight = _getListItemHeight(item.runtimeType, proxyCardType);
itemHeightList.add(itemHeight);
currentHeight = currentHeight + itemHeight;
}
_headerOffset = headerOffset;
return itemHeightList;
}
List<Widget> _buildItems({
required List<String> groupNames,
required int columns,
required Set<String> currentUnfoldSet,
required ProxyCardType type,
}) {
final items = <Widget>[];
final GroupNameProxiesMap groupNameProxiesMap = {};
for (final groupName in groupNames) {
final group =
globalState.appController.appState.getGroupWithName(groupName)!;
final isExpand = currentUnfoldSet.contains(groupName);
items.addAll([
ListHeader(
onScrollToSelected: _scrollToGroupSelected,
key: Key(groupName),
isExpand: isExpand,
group: group,
onChange: (String groupName) {
_handleChange(currentUnfoldSet, groupName);
},
),
const SizedBox(
height: 8,
),
]);
if (isExpand) {
final sortedProxies = globalState.appController.getSortProxies(
group.all,
);
groupNameProxiesMap[groupName] = sortedProxies;
final chunks = sortedProxies.chunks(columns);
final rows = chunks.map<Widget>((proxies) {
final children = proxies
.map<Widget>(
(proxy) => Flexible(
child: currentProxyNameBuilder(
groupName: group.name,
builder: (currentProxyName) {
return ProxyCard(
type: type,
isSelected: currentProxyName == proxy.name,
key: ValueKey('$groupName.${proxy.name}'),
proxy: proxy,
groupName: groupName,
);
}),
),
)
.fill(
columns,
filler: (_) => const Flexible(
child: SizedBox(),
),
)
.separated(
const SizedBox(
width: 8,
),
);
return Row(
children: children.toList(),
);
}).separated(
const SizedBox(
height: 8,
),
);
items.addAll(
[
...rows,
const SizedBox(
height: 8,
),
],
);
}
}
_lastGroupNameProxiesMap = groupNameProxiesMap;
return items;
}
_buildHeader({
required String groupName,
required Set<String> currentUnfoldSet,
}) {
final group =
globalState.appController.appState.getGroupWithName(groupName)!;
final isExpand = currentUnfoldSet.contains(groupName);
return SizedBox(
height: listHeaderHeight,
child: ListHeader(
onScrollToSelected: _scrollToGroupSelected,
key: Key(groupName),
isExpand: isExpand,
group: group,
onChange: (String groupName) {
_handleChange(currentUnfoldSet, groupName);
},
),
);
}
_scrollToGroupSelected(String groupName) {
final appController = globalState.appController;
final currentGroups = appController.appState.currentGroups;
final groupNames = currentGroups.map((e) => e.name).toList();
final findIndex = groupNames.indexWhere((item) => item == groupName);
final index = findIndex != -1 ? findIndex : 0;
final currentInitOffset = _headerOffset[index];
final proxies = _lastGroupNameProxiesMap[groupName];
_controller.animateTo(
currentInitOffset +
getScrollToSelectedOffset(
groupName: groupName,
proxies: proxies ?? [],
),
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
}
class _ProxiesListFragmentState
extends State<ProxiesListFragment> {
@override
Widget build(BuildContext context) {
return Selector2<AppState, Config, ProxiesListSelectorState>(
return Selector2<AppState, Config, ProxiesSelectorState>(
selector: (_, appState, config) {
final currentGroups = appState.currentGroups;
final groupNames = currentGroups.map((e) => e.name).toList();
return ProxiesListSelectorState(
return ProxiesSelectorState(
groupNames: groupNames,
currentUnfoldSet: config.currentUnfoldSet,
proxyCardType: config.proxyCardType,
proxiesSortType: config.proxiesSortType,
columns: globalState.appController.columns,
sortNum: appState.sortNum,
currentGroupName: config.currentGroupName,
);
},
shouldRebuild: (prev, next) {
if (!const ListEquality<String>()
.equals(prev.groupNames, next.groupNames)) {
_headerStateNotifier.value = const ProxiesListHeaderSelectorState(
offset: 0,
currentIndex: 0,
);
}
return prev != next;
},
builder: (_, state, __) {
final items = _buildItems(
groupNames: state.groupNames,
currentUnfoldSet: state.currentUnfoldSet,
columns: state.columns,
type: state.proxyCardType,
);
final itemsOffset = _getItemHeightList(items, state.proxyCardType);
return Scrollbar(
controller: _controller,
thumbVisibility: true,
trackVisibility: true,
thickness: 8,
radius: const Radius.circular(8),
interactive: true,
child: Stack(
children: [
Positioned.fill(
child: ScrollConfiguration(
behavior: HiddenBarScrollBehavior(),
child: ListView.builder(
padding: const EdgeInsets.all(16),
controller: _controller,
itemExtentBuilder: (index, __) {
return itemsOffset[index];
},
itemCount: items.length,
itemBuilder: (_, index) {
return items[index];
},
),
),
),
LayoutBuilder(builder: (_, container) {
return ValueListenableBuilder(
valueListenable: _headerStateNotifier,
builder: (_, headerState, ___) {
final index =
headerState.currentIndex > state.groupNames.length - 1
? 0
: headerState.currentIndex;
return Stack(
children: [
Positioned(
top: -headerState.offset,
child: Container(
width: container.maxWidth,
color: context.colorScheme.surface,
padding: const EdgeInsets.only(
top: 16,
left: 16,
right: 16,
bottom: 8,
),
child: _buildHeader(
groupName: state.groupNames[index],
currentUnfoldSet: state.currentUnfoldSet,
),
),
),
],
);
},
);
}),
],
),
return ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: state.groupNames.length,
itemBuilder: (_, index) {
final groupName = state.groupNames[index];
return ProxyGroupView(
key: PageStorageKey(groupName),
groupName: groupName,
type: ProxiesType.list,
);
},
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(
height: 16,
);
},
);
},
);
}
}
class ListHeader extends StatefulWidget {
final Group group;
final Function(String groupName) onChange;
final Function(String groupName) onScrollToSelected;
final bool isExpand;
const ListHeader({
super.key,
required this.group,
required this.onChange,
required this.onScrollToSelected,
required this.isExpand,
});
@override
State<ListHeader> createState() => _ListHeaderState();
}
class _ListHeaderState extends State<ListHeader>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _iconTurns;
var isLock = false;
String get groupName => widget.group.name;
String get groupType => widget.group.type.name;
bool get isExpand => widget.isExpand;
_delayTest(List<Proxy> proxies) async {
if (isLock) return;
isLock = true;
await delayTest(proxies);
isLock = false;
}
_handleChange(String groupName) {
if (isExpand) {
_animationController.reverse();
} else {
_animationController.forward();
}
widget.onChange(groupName);
}
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_iconTurns = _animationController.drive(
Tween<double>(begin: 0.0, end: 0.5),
);
if (isExpand) {
_animationController.value = 1.0;
}
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CommonCard(
key: widget.key,
type: CommonCardType.filled,
child: Container(
padding: const EdgeInsets.all(12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
groupName,
style: context.textTheme.titleMedium?.copyWith(
color: context.colorScheme.primary,
),
),
const SizedBox(
height: 4,
),
Flexible(
flex: 1,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
groupType,
style: context.textTheme.labelMedium?.toLight,
),
Flexible(
flex: 1,
child: currentProxyNameBuilder(
groupName: groupName,
builder: (value) {
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (value.isNotEmpty) ...[
Flexible(
flex: 1,
child: Text(
overflow: TextOverflow.ellipsis,
" · $value",
style: context
.textTheme.labelMedium?.toLight,
),
),
]
],
);
},
),
),
],
),
),
],
),
),
Row(
children: [
if (isExpand) ...[
IconButton(
onPressed: () {
widget.onScrollToSelected(groupName);
},
icon: const Icon(
Icons.adjust,
),
),
IconButton(
onPressed: () {
_delayTest(widget.group.all);
},
icon: const Icon(
Icons.network_ping,
),
),
const SizedBox(
width: 4,
),
],
AnimatedBuilder(
animation: _animationController.view,
builder: (_, __) {
return IconButton.filledTonal(
onPressed: () {
_handleChange(groupName);
},
icon: RotationTransition(
turns: _iconTurns,
child: const Icon(
Icons.expand_more,
),
),
);
},
)
],
)
],
),
),
onPressed: () {
_handleChange(groupName);
},
);
}
}
}

View File

@@ -17,26 +17,12 @@ class ProxiesFragment extends StatefulWidget {
}
class _ProxiesFragmentState extends State<ProxiesFragment> {
final GlobalKey<ProxiesTabFragmentState> _proxiesTabKey = GlobalKey();
_initActions(ProxiesType proxiesType) {
_initActions() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [
if (proxiesType == ProxiesType.tab) ...[
IconButton(
onPressed: () {
_proxiesTabKey.currentState?.scrollToGroupSelected();
},
icon: const Icon(
Icons.gps_fixed,
),
),
const SizedBox(
width: 8,
)
],
IconButton(
onPressed: () {
showSheet(
@@ -57,24 +43,23 @@ class _ProxiesFragmentState extends State<ProxiesFragment> {
@override
Widget build(BuildContext context) {
return Selector<Config, ProxiesType>(
selector: (_, config) => config.proxiesType,
builder: (_, proxiesType, __) {
return Selector<AppState, bool>(
selector: (_, appState) => appState.currentLabel == 'proxies',
builder: (_, isCurrent, child) {
if (isCurrent) {
_initActions(proxiesType);
}
return switch (proxiesType) {
ProxiesType.tab => ProxiesTabFragment(
key: _proxiesTabKey,
),
ProxiesType.list => const ProxiesListFragment(),
};
},
);
return Selector<AppState, bool>(
selector: (_, appState) => appState.currentLabel == 'proxies',
builder: (_, isCurrent, child) {
if (isCurrent) {
_initActions();
}
return child!;
},
child: Selector<Config, ProxiesType>(
selector: (_, config) => config.proxiesType,
builder: (_, proxiesType, __) {
return switch (proxiesType) {
ProxiesType.tab => const ProxiesTabFragment(),
ProxiesType.list => const ProxiesListFragment(),
};
},
),
);
}
}

View File

@@ -8,23 +8,19 @@ import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'card.dart';
import 'common.dart';
typedef GroupNameKeyMap = Map<String, GlobalObjectKey<ProxyGroupViewState>>;
import 'group.dart';
class ProxiesTabFragment extends StatefulWidget {
const ProxiesTabFragment({super.key});
@override
State<ProxiesTabFragment> createState() => ProxiesTabFragmentState();
State<ProxiesTabFragment> createState() => _ProxiesTabFragmentState();
}
class ProxiesTabFragmentState extends State<ProxiesTabFragment>
class _ProxiesTabFragmentState extends State<ProxiesTabFragment>
with TickerProviderStateMixin {
TabController? _tabController;
final _hasMoreButtonNotifier = ValueNotifier<bool>(false);
GroupNameKeyMap _keyMap = {};
final hasMoreButtonNotifier = ValueNotifier<bool>(false);
@override
void dispose() {
@@ -32,11 +28,6 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
_tabController?.dispose();
}
scrollToGroupSelected() {
final currentGroupName = globalState.appController.config.currentGroupName;
_keyMap[currentGroupName]?.currentState?.scrollToSelected();
}
_buildMoreButton() {
return Selector<AppState, bool>(
selector: (_, appState) => appState.viewMode == ViewMode.mobile,
@@ -92,7 +83,6 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
.updateCurrentGroupName(
groupName,
);
Navigator.of(context).pop();
},
isSelected: groupName == state.currentGroupName,
)
@@ -136,29 +126,18 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
initialIndex: index == -1 ? 0 : index,
vsync: this,
);
GroupNameKeyMap keyMap = {};
final children = state.groupNames.map((groupName) {
keyMap[groupName] = GlobalObjectKey(groupName);
return KeepContainer(
child: ProxyGroupView(
key: keyMap[groupName],
groupName: groupName,
),
);
}).toList();
_keyMap = keyMap;
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
NotificationListener<ScrollMetricsNotification>(
onNotification: (scrollNotification) {
_hasMoreButtonNotifier.value =
hasMoreButtonNotifier.value =
scrollNotification.metrics.maxScrollExtent > 0;
return true;
},
child: ValueListenableBuilder(
valueListenable: _hasMoreButtonNotifier,
valueListenable: hasMoreButtonNotifier,
builder: (_, value, child) {
return Stack(
alignment: AlignmentDirectional.centerStart,
@@ -220,7 +199,16 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
Expanded(
child: TabBarView(
controller: _tabController,
children: children,
children: [
for (final groupName in state.groupNames)
KeepContainer(
key: ObjectKey(groupName),
child: ProxyGroupView(
groupName: groupName,
type: ProxiesType.tab,
),
),
],
),
)
],
@@ -229,205 +217,3 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
);
}
}
class ProxyGroupView extends StatefulWidget {
final String groupName;
const ProxyGroupView({
super.key,
required this.groupName,
});
@override
State<ProxyGroupView> createState() => ProxyGroupViewState();
}
class ProxyGroupViewState extends State<ProxyGroupView> {
var isLock = false;
final _controller = ScrollController();
List<Proxy> _lastProxies = [];
String get groupName => widget.groupName;
_delayTest(List<Proxy> proxies) async {
if (isLock) return;
isLock = true;
await delayTest(proxies);
isLock = false;
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
Widget _buildTabGroupView({
required List<Proxy> proxies,
required int columns,
required ProxyCardType proxyCardType,
}) {
final sortedProxies = globalState.appController.getSortProxies(
proxies,
);
_lastProxies = sortedProxies;
return DelayTestButtonContainer(
onClick: () async {
await _delayTest(
proxies,
);
},
child: Align(
alignment: Alignment.topCenter,
child: GridView.builder(
controller: _controller,
padding: const EdgeInsets.all(16),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
mainAxisExtent: getItemHeight(proxyCardType),
),
itemCount: sortedProxies.length,
itemBuilder: (_, index) {
final proxy = sortedProxies[index];
return currentProxyNameBuilder(
builder: (value) {
return ProxyCard(
type: proxyCardType,
key: ValueKey('$groupName.${proxy.name}'),
isSelected: value == proxy.name,
proxy: proxy,
groupName: groupName,
);
},
groupName: groupName,
);
},
),
),
);
}
scrollToSelected() {
_controller.animateTo(
16 +
getScrollToSelectedOffset(
groupName: groupName,
proxies: _lastProxies,
),
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
}
@override
Widget build(BuildContext context) {
return Selector2<AppState, Config, ProxyGroupSelectorState>(
selector: (_, appState, config) {
final group = appState.getGroupWithName(groupName)!;
return ProxyGroupSelectorState(
proxyCardType: config.proxyCardType,
proxiesSortType: config.proxiesSortType,
columns: globalState.appController.columns,
sortNum: appState.sortNum,
proxies: group.all,
);
},
builder: (_, state, __) {
final proxies = state.proxies;
final columns = state.columns;
final proxyCardType = state.proxyCardType;
return _buildTabGroupView(
proxies: proxies,
columns: columns,
proxyCardType: proxyCardType,
);
},
);
}
}
class DelayTestButtonContainer extends StatefulWidget {
final Widget child;
final Future Function() onClick;
const DelayTestButtonContainer({
super.key,
required this.child,
required this.onClick,
});
@override
State<DelayTestButtonContainer> createState() =>
_DelayTestButtonContainerState();
}
class _DelayTestButtonContainerState extends State<DelayTestButtonContainer>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scale;
_healthcheck() async {
_controller.forward();
await widget.onClick();
_controller.reverse();
}
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 200,
),
);
_scale = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0,
1,
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_controller.reverse();
return FloatLayout(
floatingWidget: FloatWrapper(
child: AnimatedBuilder(
animation: _controller.view,
builder: (_, child) {
return SizedBox(
width: 56,
height: 56,
child: Transform.scale(
scale: _scale.value,
child: child,
),
);
},
child: FloatingActionButton(
heroTag: null,
onPressed: _healthcheck,
child: const Icon(Icons.network_ping),
),
),
),
child: widget.child,
);
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
@@ -21,6 +23,27 @@ class ThemeModeItem {
class ThemeFragment extends StatelessWidget {
const ThemeFragment({super.key});
Widget _itemCard({
required BuildContext context,
required Info info,
required Widget child,
}) {
return Padding(
padding: const EdgeInsets.only(
top: 16,
),
child: Wrap(
runSpacing: 16,
children: [
InfoHeader(
info: info,
),
child,
],
),
);
}
@override
Widget build(BuildContext context) {
final previewCard = Padding(

View File

@@ -211,10 +211,5 @@
"sort": "Sort",
"columns": "Columns",
"proxiesSetting": "Proxies setting",
"proxyGroup": "Proxy group",
"go": "Go",
"externalLink": "External link",
"otherContributors": "Other contributors",
"autoCloseConnections": "Auto lose connections",
"autoCloseConnectionsDesc": "Auto close connections after change node"
"proxyGroup": "Proxy group"
}

View File

@@ -211,10 +211,5 @@
"sort": "排序",
"columns": "列数",
"proxiesSetting": "代理设置",
"proxyGroup": "代理组",
"go": "前往",
"externalLink": "外部链接",
"otherContributors": "其他贡献者",
"autoCloseConnections": "自动关闭连接",
"autoCloseConnectionsDesc": "切换节点后自动关闭连接"
"proxyGroup": "代理组"
}

View File

@@ -58,10 +58,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Auto check updates"),
"autoCheckUpdateDesc": MessageLookupByLibrary.simpleMessage(
"Auto check for updates when the app starts"),
"autoCloseConnections":
MessageLookupByLibrary.simpleMessage("Auto lose connections"),
"autoCloseConnectionsDesc": MessageLookupByLibrary.simpleMessage(
"Auto close connections after change node"),
"autoLaunch": MessageLookupByLibrary.simpleMessage("AutoLaunch"),
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage(
"Follow the system self startup"),
@@ -139,7 +135,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("ExternalController"),
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
"Once enabled, the Clash kernel can be controlled on port 9090"),
"externalLink": MessageLookupByLibrary.simpleMessage("External link"),
"externalResources":
MessageLookupByLibrary.simpleMessage("External resources"),
"file": MessageLookupByLibrary.simpleMessage("File"),
@@ -158,7 +153,6 @@ class MessageLookup extends MessageLookupByLibrary {
"geodataLoaderDesc": MessageLookupByLibrary.simpleMessage(
"Enabling will use the Geo low memory loader"),
"global": MessageLookupByLibrary.simpleMessage("Global"),
"go": MessageLookupByLibrary.simpleMessage("Go"),
"goDownload": MessageLookupByLibrary.simpleMessage("Go to download"),
"hours": MessageLookupByLibrary.simpleMessage("Hours"),
"importFromURL":
@@ -209,8 +203,6 @@ class MessageLookup extends MessageLookupByLibrary {
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"),
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
"other": MessageLookupByLibrary.simpleMessage("Other"),
"otherContributors":
MessageLookupByLibrary.simpleMessage("Other contributors"),
"outboundMode": MessageLookupByLibrary.simpleMessage("Outbound mode"),
"override": MessageLookupByLibrary.simpleMessage("Override"),
"overrideDesc": MessageLookupByLibrary.simpleMessage(

View File

@@ -49,9 +49,6 @@ class MessageLookup extends MessageLookupByLibrary {
"autoCheckUpdate": MessageLookupByLibrary.simpleMessage("自动检查更新"),
"autoCheckUpdateDesc":
MessageLookupByLibrary.simpleMessage("应用启动时自动检查更新"),
"autoCloseConnections": MessageLookupByLibrary.simpleMessage("自动关闭连接"),
"autoCloseConnectionsDesc":
MessageLookupByLibrary.simpleMessage("切换节点后自动关闭连接"),
"autoLaunch": MessageLookupByLibrary.simpleMessage("自启动"),
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage("跟随系统自启动"),
"autoRun": MessageLookupByLibrary.simpleMessage("自动运行"),
@@ -114,7 +111,6 @@ class MessageLookup extends MessageLookupByLibrary {
"externalController": MessageLookupByLibrary.simpleMessage("外部控制器"),
"externalControllerDesc":
MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制Clash内核"),
"externalLink": MessageLookupByLibrary.simpleMessage("外部链接"),
"externalResources": MessageLookupByLibrary.simpleMessage("外部资源"),
"file": MessageLookupByLibrary.simpleMessage("文件"),
"fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
@@ -129,7 +125,6 @@ class MessageLookup extends MessageLookupByLibrary {
"geodataLoaderDesc":
MessageLookupByLibrary.simpleMessage("开启将使用Geo低内存加载器"),
"global": MessageLookupByLibrary.simpleMessage("全局"),
"go": MessageLookupByLibrary.simpleMessage("前往"),
"goDownload": MessageLookupByLibrary.simpleMessage("前往下载"),
"hours": MessageLookupByLibrary.simpleMessage("小时"),
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
@@ -171,7 +166,6 @@ class MessageLookup extends MessageLookupByLibrary {
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
"other": MessageLookupByLibrary.simpleMessage("其他"),
"otherContributors": MessageLookupByLibrary.simpleMessage("其他贡献者"),
"outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"),
"override": MessageLookupByLibrary.simpleMessage("覆写"),
"overrideDesc": MessageLookupByLibrary.simpleMessage("覆写代理相关配置"),

View File

@@ -2179,56 +2179,6 @@ class AppLocalizations {
args: [],
);
}
/// `Go`
String get go {
return Intl.message(
'Go',
name: 'go',
desc: '',
args: [],
);
}
/// `External link`
String get externalLink {
return Intl.message(
'External link',
name: 'externalLink',
desc: '',
args: [],
);
}
/// `Other contributors`
String get otherContributors {
return Intl.message(
'Other contributors',
name: 'otherContributors',
desc: '',
args: [],
);
}
/// `Auto lose connections`
String get autoCloseConnections {
return Intl.message(
'Auto lose connections',
name: 'autoCloseConnections',
desc: '',
args: [],
);
}
/// `Auto close connections after change node`
String get autoCloseConnectionsDesc {
return Intl.message(
'Auto close connections after change node',
name: 'autoCloseConnectionsDesc',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -47,7 +47,6 @@ Future<void> main() async {
Future<void> vpnService() async {
WidgetsFlutterBinding.ensureInitialized();
globalState.isVpnService = true;
globalState.packageInfo = await PackageInfo.fromPlatform();
final config = await preferences.getConfig() ?? Config();
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
final appState = AppState(
@@ -87,10 +86,11 @@ Future<void> vpnService() async {
final currentSelectedMap = config.currentSelectedMap;
final proxyName = currentSelectedMap[groupName];
if (proxyName == null) return;
globalState.changeProxy(
config: config,
groupName: groupName,
proxyName: proxyName,
clashCore.changeProxy(
ChangeProxyParams(
groupName: groupName,
proxyName: proxyName,
),
);
},
),
@@ -119,7 +119,7 @@ Future<void> vpnService() async {
globalState.updateTraffic();
globalState.updateFunctionLists = [
() {
() {
globalState.updateTraffic();
}
];
@@ -137,7 +137,8 @@ class ServiceMessageHandler with ServiceMessageListener {
required Function(Process process) onProcess,
required Function(String runTime) onStarted,
required Function(String groupName) onLoaded,
}) : _onProtect = onProtect,
})
: _onProtect = onProtect,
_onProcess = onProcess,
_onStarted = onStarted,
_onLoaded = onLoaded;

View File

@@ -275,7 +275,7 @@ class ClashConfig extends ChangeNotifier {
}
}
@JsonKey(name: "global-ua", includeFromJson: false, includeToJson: true)
@JsonKey(name: "global-ua", defaultValue: null)
String get globalUa {
if (_globalRealUa == null) {
return globalState.packageInfo.ua;
@@ -320,6 +320,7 @@ class ClashConfig extends ChangeNotifier {
_geodataLoader = clashConfig._geodataLoader;
_dns = clashConfig._dns;
_rules = clashConfig._rules;
_globalRealUa = clashConfig.globalRealUa;
}
notifyListeners();
}

View File

@@ -73,7 +73,6 @@ class Config extends ChangeNotifier {
bool _systemProxy;
bool _isExclude;
DAV? _dav;
bool _isCloseConnections;
ProxiesType _proxiesType;
ProxyCardType _proxyCardType;
int _proxiesColumns;
@@ -85,7 +84,6 @@ class Config extends ChangeNotifier {
_autoLaunch = false,
_silentLaunch = false,
_autoRun = false,
_isCloseConnections = false,
_themeMode = ThemeMode.system,
_openLog = false,
_isCompatible = true,
@@ -407,18 +405,6 @@ class Config extends ChangeNotifier {
}
}
@JsonKey(defaultValue: false)
bool get isCloseConnections {
return _isCloseConnections;
}
set isCloseConnections(bool value) {
if (_isCloseConnections != value) {
_isCloseConnections = value;
notifyListeners();
}
}
@JsonKey(
defaultValue: ProxiesType.tab,
unknownEnumValue: ProxiesType.tab,
@@ -496,7 +482,6 @@ class Config extends ChangeNotifier {
}
if (onlyProfiles) return;
_currentProfileId = config._currentProfileId;
_isCloseConnections = config._isCloseConnections;
_isCompatible = config._isCompatible;
_autoLaunch = config._autoLaunch;
_silentLaunch = config._silentLaunch;

View File

@@ -1,6 +1,8 @@
// ignore_for_file: invalid_annotation_target
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/models/connection.dart';
import 'package:fl_clash/models/models.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

View File

@@ -82,7 +82,6 @@ Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) =>
'tun': instance.tun,
'dns': instance.dns,
'rules': instance.rules,
'global-ua': instance.globalUa,
'global-real-ua': instance.globalRealUa,
'geox-url': instance.geoXUrl,
};

View File

@@ -34,8 +34,7 @@ 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? ?? false
..isCloseConnections = json['isCloseConnections'] as bool? ?? false
..systemProxy = json['systemProxy'] as bool? ?? true
..proxiesType = $enumDecodeNullable(_$ProxiesTypeEnumMap, json['proxiesType'],
unknownValue: ProxiesType.tab) ??
ProxiesType.tab
@@ -69,7 +68,6 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'autoCheckUpdate': instance.autoCheckUpdate,
'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy,
'isCloseConnections': instance.isCloseConnections,
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
'proxiesColumns': instance.proxiesColumns,

View File

@@ -1756,257 +1756,6 @@ abstract class _ProxiesSelectorState implements ProxiesSelectorState {
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ProxiesListSelectorState {
List<String> get groupNames => throw _privateConstructorUsedError;
Set<String> get currentUnfoldSet => throw _privateConstructorUsedError;
ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError;
ProxyCardType get proxyCardType => throw _privateConstructorUsedError;
num get sortNum => throw _privateConstructorUsedError;
int get columns => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ProxiesListSelectorStateCopyWith<ProxiesListSelectorState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProxiesListSelectorStateCopyWith<$Res> {
factory $ProxiesListSelectorStateCopyWith(ProxiesListSelectorState value,
$Res Function(ProxiesListSelectorState) then) =
_$ProxiesListSelectorStateCopyWithImpl<$Res, ProxiesListSelectorState>;
@useResult
$Res call(
{List<String> groupNames,
Set<String> currentUnfoldSet,
ProxiesSortType proxiesSortType,
ProxyCardType proxyCardType,
num sortNum,
int columns});
}
/// @nodoc
class _$ProxiesListSelectorStateCopyWithImpl<$Res,
$Val extends ProxiesListSelectorState>
implements $ProxiesListSelectorStateCopyWith<$Res> {
_$ProxiesListSelectorStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? groupNames = null,
Object? currentUnfoldSet = null,
Object? proxiesSortType = null,
Object? proxyCardType = null,
Object? sortNum = null,
Object? columns = null,
}) {
return _then(_value.copyWith(
groupNames: null == groupNames
? _value.groupNames
: groupNames // ignore: cast_nullable_to_non_nullable
as List<String>,
currentUnfoldSet: null == currentUnfoldSet
? _value.currentUnfoldSet
: currentUnfoldSet // ignore: cast_nullable_to_non_nullable
as Set<String>,
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,
columns: null == columns
? _value.columns
: columns // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$ProxiesListSelectorStateImplCopyWith<$Res>
implements $ProxiesListSelectorStateCopyWith<$Res> {
factory _$$ProxiesListSelectorStateImplCopyWith(
_$ProxiesListSelectorStateImpl value,
$Res Function(_$ProxiesListSelectorStateImpl) then) =
__$$ProxiesListSelectorStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{List<String> groupNames,
Set<String> currentUnfoldSet,
ProxiesSortType proxiesSortType,
ProxyCardType proxyCardType,
num sortNum,
int columns});
}
/// @nodoc
class __$$ProxiesListSelectorStateImplCopyWithImpl<$Res>
extends _$ProxiesListSelectorStateCopyWithImpl<$Res,
_$ProxiesListSelectorStateImpl>
implements _$$ProxiesListSelectorStateImplCopyWith<$Res> {
__$$ProxiesListSelectorStateImplCopyWithImpl(
_$ProxiesListSelectorStateImpl _value,
$Res Function(_$ProxiesListSelectorStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? groupNames = null,
Object? currentUnfoldSet = null,
Object? proxiesSortType = null,
Object? proxyCardType = null,
Object? sortNum = null,
Object? columns = null,
}) {
return _then(_$ProxiesListSelectorStateImpl(
groupNames: null == groupNames
? _value._groupNames
: groupNames // ignore: cast_nullable_to_non_nullable
as List<String>,
currentUnfoldSet: null == currentUnfoldSet
? _value._currentUnfoldSet
: currentUnfoldSet // ignore: cast_nullable_to_non_nullable
as Set<String>,
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,
columns: null == columns
? _value.columns
: columns // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
class _$ProxiesListSelectorStateImpl implements _ProxiesListSelectorState {
const _$ProxiesListSelectorStateImpl(
{required final List<String> groupNames,
required final Set<String> currentUnfoldSet,
required this.proxiesSortType,
required this.proxyCardType,
required this.sortNum,
required this.columns})
: _groupNames = groupNames,
_currentUnfoldSet = currentUnfoldSet;
final List<String> _groupNames;
@override
List<String> get groupNames {
if (_groupNames is EqualUnmodifiableListView) return _groupNames;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_groupNames);
}
final Set<String> _currentUnfoldSet;
@override
Set<String> get currentUnfoldSet {
if (_currentUnfoldSet is EqualUnmodifiableSetView) return _currentUnfoldSet;
// ignore: implicit_dynamic_type
return EqualUnmodifiableSetView(_currentUnfoldSet);
}
@override
final ProxiesSortType proxiesSortType;
@override
final ProxyCardType proxyCardType;
@override
final num sortNum;
@override
final int columns;
@override
String toString() {
return 'ProxiesListSelectorState(groupNames: $groupNames, currentUnfoldSet: $currentUnfoldSet, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, columns: $columns)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProxiesListSelectorStateImpl &&
const DeepCollectionEquality()
.equals(other._groupNames, _groupNames) &&
const DeepCollectionEquality()
.equals(other._currentUnfoldSet, _currentUnfoldSet) &&
(identical(other.proxiesSortType, proxiesSortType) ||
other.proxiesSortType == proxiesSortType) &&
(identical(other.proxyCardType, proxyCardType) ||
other.proxyCardType == proxyCardType) &&
(identical(other.sortNum, sortNum) || other.sortNum == sortNum) &&
(identical(other.columns, columns) || other.columns == columns));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_groupNames),
const DeepCollectionEquality().hash(_currentUnfoldSet),
proxiesSortType,
proxyCardType,
sortNum,
columns);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ProxiesListSelectorStateImplCopyWith<_$ProxiesListSelectorStateImpl>
get copyWith => __$$ProxiesListSelectorStateImplCopyWithImpl<
_$ProxiesListSelectorStateImpl>(this, _$identity);
}
abstract class _ProxiesListSelectorState implements ProxiesListSelectorState {
const factory _ProxiesListSelectorState(
{required final List<String> groupNames,
required final Set<String> currentUnfoldSet,
required final ProxiesSortType proxiesSortType,
required final ProxyCardType proxyCardType,
required final num sortNum,
required final int columns}) = _$ProxiesListSelectorStateImpl;
@override
List<String> get groupNames;
@override
Set<String> get currentUnfoldSet;
@override
ProxiesSortType get proxiesSortType;
@override
ProxyCardType get proxyCardType;
@override
num get sortNum;
@override
int get columns;
@override
@JsonKey(ignore: true)
_$$ProxiesListSelectorStateImplCopyWith<_$ProxiesListSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ProxyGroupSelectorState {
ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError;
@@ -2652,151 +2401,3 @@ abstract class _ColumnsSelectorState implements ColumnsSelectorState {
_$$ColumnsSelectorStateImplCopyWith<_$ColumnsSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ProxiesListHeaderSelectorState {
double get offset => throw _privateConstructorUsedError;
int get currentIndex => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ProxiesListHeaderSelectorStateCopyWith<ProxiesListHeaderSelectorState>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProxiesListHeaderSelectorStateCopyWith<$Res> {
factory $ProxiesListHeaderSelectorStateCopyWith(
ProxiesListHeaderSelectorState value,
$Res Function(ProxiesListHeaderSelectorState) then) =
_$ProxiesListHeaderSelectorStateCopyWithImpl<$Res,
ProxiesListHeaderSelectorState>;
@useResult
$Res call({double offset, int currentIndex});
}
/// @nodoc
class _$ProxiesListHeaderSelectorStateCopyWithImpl<$Res,
$Val extends ProxiesListHeaderSelectorState>
implements $ProxiesListHeaderSelectorStateCopyWith<$Res> {
_$ProxiesListHeaderSelectorStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? offset = null,
Object? currentIndex = null,
}) {
return _then(_value.copyWith(
offset: null == offset
? _value.offset
: offset // ignore: cast_nullable_to_non_nullable
as double,
currentIndex: null == currentIndex
? _value.currentIndex
: currentIndex // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$ProxiesListHeaderSelectorStateImplCopyWith<$Res>
implements $ProxiesListHeaderSelectorStateCopyWith<$Res> {
factory _$$ProxiesListHeaderSelectorStateImplCopyWith(
_$ProxiesListHeaderSelectorStateImpl value,
$Res Function(_$ProxiesListHeaderSelectorStateImpl) then) =
__$$ProxiesListHeaderSelectorStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({double offset, int currentIndex});
}
/// @nodoc
class __$$ProxiesListHeaderSelectorStateImplCopyWithImpl<$Res>
extends _$ProxiesListHeaderSelectorStateCopyWithImpl<$Res,
_$ProxiesListHeaderSelectorStateImpl>
implements _$$ProxiesListHeaderSelectorStateImplCopyWith<$Res> {
__$$ProxiesListHeaderSelectorStateImplCopyWithImpl(
_$ProxiesListHeaderSelectorStateImpl _value,
$Res Function(_$ProxiesListHeaderSelectorStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? offset = null,
Object? currentIndex = null,
}) {
return _then(_$ProxiesListHeaderSelectorStateImpl(
offset: null == offset
? _value.offset
: offset // ignore: cast_nullable_to_non_nullable
as double,
currentIndex: null == currentIndex
? _value.currentIndex
: currentIndex // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
class _$ProxiesListHeaderSelectorStateImpl
implements _ProxiesListHeaderSelectorState {
const _$ProxiesListHeaderSelectorStateImpl(
{required this.offset, required this.currentIndex});
@override
final double offset;
@override
final int currentIndex;
@override
String toString() {
return 'ProxiesListHeaderSelectorState(offset: $offset, currentIndex: $currentIndex)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProxiesListHeaderSelectorStateImpl &&
(identical(other.offset, offset) || other.offset == offset) &&
(identical(other.currentIndex, currentIndex) ||
other.currentIndex == currentIndex));
}
@override
int get hashCode => Object.hash(runtimeType, offset, currentIndex);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ProxiesListHeaderSelectorStateImplCopyWith<
_$ProxiesListHeaderSelectorStateImpl>
get copyWith => __$$ProxiesListHeaderSelectorStateImplCopyWithImpl<
_$ProxiesListHeaderSelectorStateImpl>(this, _$identity);
}
abstract class _ProxiesListHeaderSelectorState
implements ProxiesListHeaderSelectorState {
const factory _ProxiesListHeaderSelectorState(
{required final double offset,
required final int currentIndex}) = _$ProxiesListHeaderSelectorStateImpl;
@override
double get offset;
@override
int get currentIndex;
@override
@JsonKey(ignore: true)
_$$ProxiesListHeaderSelectorStateImplCopyWith<
_$ProxiesListHeaderSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@@ -99,18 +99,6 @@ class ProxiesSelectorState with _$ProxiesSelectorState {
}) = _ProxiesSelectorState;
}
@freezed
class ProxiesListSelectorState with _$ProxiesListSelectorState {
const factory ProxiesListSelectorState({
required List<String> groupNames,
required Set<String> currentUnfoldSet,
required ProxiesSortType proxiesSortType,
required ProxyCardType proxyCardType,
required num sortNum,
required int columns,
}) = _ProxiesListSelectorState;
}
@freezed
class ProxyGroupSelectorState with _$ProxyGroupSelectorState {
const factory ProxyGroupSelectorState({
@@ -145,11 +133,3 @@ class ColumnsSelectorState with _$ColumnsSelectorState {
required ViewMode viewMode,
}) = _ColumnsSelectorState;
}
@freezed
class ProxiesListHeaderSelectorState with _$ProxiesListHeaderSelectorState {
const factory ProxiesListHeaderSelectorState({
required double offset,
required int currentIndex,
}) = _ProxiesListHeaderSelectorState;
}

View File

@@ -13,20 +13,6 @@ typedef OnSelected = void Function(int index);
class HomePage extends StatelessWidget {
const HomePage({super.key});
_navigationBarContainer({
required BuildContext context,
required Widget child,
}) {
// if (!system.isDesktop) return child;
return Container(
padding: const EdgeInsets.all(16).copyWith(
right: 0,
),
color: context.colorScheme.surface,
child: child,
);
}
_getNavigationBar({
required BuildContext context,
required ViewMode viewMode,
@@ -48,104 +34,110 @@ class HomePage extends StatelessWidget {
);
}
final extended = viewMode == ViewMode.desktop;
return _navigationBarContainer(
context: context,
child: NavigationRail(
groupAlignment: -0.8,
destinations: navigationItems
.map(
(e) => NavigationRailDestination(
icon: e.icon,
label: Text(
Intl.message(e.label),
),
),
)
.toList(),
onDestinationSelected: globalState.appController.toPage,
extended: extended,
minExtendedWidth: 172,
selectedIndex: currentIndex,
labelType: extended
? NavigationRailLabelType.none
: NavigationRailLabelType.selected,
// return NavigationRail(
// backgroundColor: context.colorScheme.surfaceContainer,
// groupAlignment: -0.8,
// destinations: navigationItems
// .map(
// (e) => NavigationRailDestination(
// icon: e.icon,
// label: Text(
// Intl.message(e.label),
// ),
// ),
// )
// .toList(),
// onDestinationSelected: globalState.appController.toPage,
// extended: extended,
// minExtendedWidth: 172,
// selectedIndex: currentIndex,
// labelType: extended
// ? NavigationRailLabelType.none
// : NavigationRailLabelType.selected,
// );
return Container(
padding: const EdgeInsets.all(16).copyWith(
right: 0,
),
color: context.colorScheme.surface,
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: NavigationRail(
backgroundColor: context.colorScheme.surfaceContainer,
groupAlignment: -0.8,
destinations: navigationItems
.map(
(e) => NavigationRailDestination(
icon: e.icon,
label: Text(
Intl.message(e.label),
),
),
)
.toList(),
onDestinationSelected: globalState.appController.toPage,
extended: extended,
minExtendedWidth: 172,
selectedIndex: currentIndex,
labelType: extended
? NavigationRailLabelType.none
: NavigationRailLabelType.selected,
),
),
);
return NavigationRail(
groupAlignment: -0.95,
destinations: navigationItems
.map(
(e) => NavigationRailDestination(
icon: e.icon,
label: Text(
Intl.message(e.label),
),
),
)
.toList(),
onDestinationSelected: globalState.appController.toPage,
extended: extended,
minExtendedWidth: 172,
selectedIndex: currentIndex,
labelType: extended
? NavigationRailLabelType.none
: NavigationRailLabelType.selected,
);
}
@override
Widget build(BuildContext context) {
return PopContainer(
child: LayoutBuilder(
builder: (_, container) {
final appController = globalState.appController;
final maxWidth = container.maxWidth;
if (appController.appState.viewWidth != maxWidth) {
globalState.appController.updateViewWidth(maxWidth);
}
return Selector2<AppState, Config, HomeSelectorState>(
selector: (_, appState, config) {
return HomeSelectorState(
currentLabel: appState.currentLabel,
navigationItems: appState.currentNavigationItems,
viewMode: other.getViewMode(maxWidth),
locale: config.locale,
);
},
builder: (_, state, child) {
final viewMode = state.viewMode;
final navigationItems = state.navigationItems;
final currentLabel = state.currentLabel;
final index = navigationItems.lastIndexWhere(
(element) => element.label == currentLabel,
);
final currentIndex = index == -1 ? 0 : index;
final navigationBar = _getNavigationBar(
context: context,
viewMode: viewMode,
navigationItems: navigationItems,
currentIndex: currentIndex,
);
final bottomNavigationBar =
viewMode == ViewMode.mobile ? navigationBar : null;
final sideNavigationBar =
viewMode != ViewMode.mobile ? navigationBar : null;
return CommonScaffold(
key: globalState.homeScaffoldKey,
title: Intl.message(
currentLabel,
),
sideNavigationBar: sideNavigationBar,
body: child!,
bottomNavigationBar: bottomNavigationBar,
);
},
child: const HomeBody(
key: Key("home_boy"),
),
);
},
),
return LayoutBuilder(
builder: (_, container) {
final appController = globalState.appController;
final maxWidth = container.maxWidth;
if (appController.appState.viewWidth != maxWidth) {
globalState.appController.updateViewWidth(maxWidth);
}
return Selector2<AppState, Config, HomeSelectorState>(
selector: (_, appState, config) {
return HomeSelectorState(
currentLabel: appState.currentLabel,
navigationItems: appState.currentNavigationItems,
viewMode: other.getViewMode(maxWidth),
locale: config.locale,
);
},
builder: (_, state, child) {
final viewMode = state.viewMode;
final navigationItems = state.navigationItems;
final currentLabel = state.currentLabel;
final index = navigationItems.lastIndexWhere(
(element) => element.label == currentLabel,
);
final currentIndex = index == -1 ? 0 : index;
final navigationBar = _getNavigationBar(
context: context,
viewMode: viewMode,
navigationItems: navigationItems,
currentIndex: currentIndex,
);
final bottomNavigationBar =
viewMode == ViewMode.mobile ? navigationBar : null;
final sideNavigationBar =
viewMode != ViewMode.mobile ? navigationBar : null;
return CommonScaffold(
key: globalState.homeScaffoldKey,
title: Intl.message(
currentLabel,
),
sideNavigationBar: sideNavigationBar,
body: child!,
bottomNavigationBar: bottomNavigationBar,
);
},
child: const HomeBody(
key: Key("home_boy"),
),
);
},
);
}
}

View File

@@ -103,7 +103,6 @@ class Proxy extends ProxyPlatform {
_handleServiceMessage(String message) {
final m = ServiceMessage.fromJson(json.decode(message));
debugPrint(m.toString());
switch (m.type) {
case ServiceMessageType.protect:
_serviceMessageHandler?.onProtect(Fd.fromJson(m.data));

View File

@@ -7,7 +7,6 @@ import 'package:fl_clash/plugins/proxy.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'controller.dart';
@@ -53,7 +52,7 @@ class GlobalState {
config: clashConfig,
params: ConfigExtendedParams(
isPatch: isPatch,
isCompatible: true,
isCompatible: config.isCompatible,
selectedMap: config.currentSelectedMap,
testUrl: config.testUrl,
),
@@ -124,7 +123,7 @@ class GlobalState {
}) async {
appState.isInit = clashCore.isInit;
if (!appState.isInit) {
if (Platform.isAndroid) {
if(Platform.isAndroid){
clashCore.setProps(
Props(
accessControl: config.isAccessControl ? config.accessControl : null,
@@ -184,22 +183,6 @@ class GlobalState {
);
}
changeProxy({
required Config config,
required String groupName,
required String proxyName,
}) {
clashCore.changeProxy(
ChangeProxyParams(
groupName: groupName,
proxyName: proxyName,
),
);
if(config.isCloseConnections){
clashCore.closeConnections();
}
}
Future<T?> showCommonDialog<T>({
required Widget child,
}) async {
@@ -278,17 +261,6 @@ class GlobalState {
return null;
}
}
openUrl(String url) {
showMessage(
message: TextSpan(text: url),
title: appLocalizations.externalLink,
confirmText: appLocalizations.go,
onTab: () {
launchUrl(Uri.parse(url));
},
);
}
}
final globalState = GlobalState();

View File

@@ -19,7 +19,6 @@ class AndroidContainer extends StatefulWidget {
class _AndroidContainerState extends State<AndroidContainer>
with WidgetsBindingObserver {
Widget _excludeContainer(Widget child) {
return Selector<Config, bool>(
selector: (_, config) => config.isExclude,

View File

@@ -60,10 +60,11 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
final currentSelectedMap = appController.config.currentSelectedMap;
final proxyName = currentSelectedMap[groupName];
if (proxyName == null) return;
globalState.changeProxy(
config: appController.config,
groupName: groupName,
proxyName: proxyName,
clashCore.changeProxy(
ChangeProxyParams(
groupName: groupName,
proxyName: proxyName,
),
);
appController.addCheckIpNumDebounce();
super.onLoaded(proxyName);

View File

@@ -78,7 +78,7 @@ class ListItem<T> extends StatelessWidget {
final Widget? trailing;
final Delegate delegate;
final double? horizontalTitleGap;
final void Function()? onTap;
final void Function()? onTab;
const ListItem({
super.key,
@@ -89,7 +89,7 @@ class ListItem<T> extends StatelessWidget {
this.trailing,
this.horizontalTitleGap,
this.prue,
this.onTap,
this.onTab,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : delegate = const Delegate();
@@ -104,7 +104,7 @@ class ListItem<T> extends StatelessWidget {
this.horizontalTitleGap,
this.prue,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : onTap = null;
}) : onTab = null;
const ListItem.next({
super.key,
@@ -117,7 +117,7 @@ class ListItem<T> extends StatelessWidget {
this.horizontalTitleGap,
this.prue,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : onTap = null;
}) : onTab = null;
const ListItem.checkbox({
super.key,
@@ -130,7 +130,7 @@ class ListItem<T> extends StatelessWidget {
this.prue,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : trailing = null,
onTap = null;
onTab = null;
const ListItem.switchItem({
super.key,
@@ -143,7 +143,7 @@ class ListItem<T> extends StatelessWidget {
this.prue,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : trailing = null,
onTap = null;
onTab = null;
const ListItem.radio({
super.key,
@@ -156,10 +156,10 @@ class ListItem<T> extends StatelessWidget {
this.prue,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : leading = null,
onTap = null;
onTab = null;
_buildListTile({
void Function()? onTap,
void Function()? onTab,
Widget? trailing,
Widget? leading,
}) {
@@ -188,7 +188,7 @@ class ListItem<T> extends StatelessWidget {
}
return InkWell(
splashFactory: NoSplash.splashFactory,
onTap: onTap,
onTap: onTab,
child: Container(
padding: padding,
child: Row(
@@ -203,7 +203,7 @@ class ListItem<T> extends StatelessWidget {
title: title,
subtitle: subtitle,
titleAlignment: tileTitleAlignment,
onTap: onTap,
onTap: onTab,
trailing: trailing ?? this.trailing,
contentPadding: padding,
);
@@ -230,7 +230,7 @@ class ListItem<T> extends StatelessWidget {
action();
}
return _buildListTile(onTap: openAction);
return _buildListTile(onTab: openAction);
},
openBuilder: (_, action) {
return CommonScaffold.open(
@@ -245,7 +245,7 @@ class ListItem<T> extends StatelessWidget {
if (delegate is NextDelegate) {
final nextDelegate = delegate as NextDelegate;
return _buildListTile(
onTap: () {
onTab: () {
final isMobile =
globalState.appController.appState.viewMode == ViewMode.mobile;
if (!isMobile) {
@@ -272,7 +272,7 @@ class ListItem<T> extends StatelessWidget {
if (delegate is CheckboxDelegate) {
final checkboxDelegate = delegate as CheckboxDelegate;
return _buildListTile(
onTap: () {
onTab: () {
if (checkboxDelegate.onChanged != null) {
checkboxDelegate.onChanged!(!checkboxDelegate.value);
}
@@ -286,7 +286,7 @@ class ListItem<T> extends StatelessWidget {
if (delegate is SwitchDelegate) {
final switchDelegate = delegate as SwitchDelegate;
return _buildListTile(
onTap: () {
onTab: () {
if (switchDelegate.onChanged != null) {
switchDelegate.onChanged!(!switchDelegate.value);
}
@@ -300,7 +300,7 @@ class ListItem<T> extends StatelessWidget {
if (delegate is RadioDelegate) {
final radioDelegate = delegate as RadioDelegate<T>;
return _buildListTile(
onTap: () {
onTab: () {
if (radioDelegate.onChanged != null) {
radioDelegate.onChanged!(radioDelegate.value);
}
@@ -319,7 +319,7 @@ class ListItem<T> extends StatelessWidget {
}
return _buildListTile(
onTap: onTap,
onTab: onTab,
);
}
}

View File

@@ -19,7 +19,7 @@ class _PopContainerState extends State<PopContainer> {
if (Platform.isAndroid) {
return PopScope(
canPop: false,
onPopInvoked: (_) async {
onPopInvoked: (didPop) async {
final canPop = Navigator.canPop(context);
if (canPop) {
Navigator.pop(context);

View File

@@ -1,6 +1,7 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class CommonScaffold extends StatefulWidget {
final Widget body;

View File

@@ -28,20 +28,39 @@ showExtendPage(
builder: (_, viewWidth, __) {
final isMobile =
globalState.appController.appState.viewMode == ViewMode.mobile;
final commonScaffold = CommonScaffold(
automaticallyImplyLeading: isMobile ? true : false,
actions: isMobile
? null
: [
const SizedBox(
height: kToolbarHeight,
width: kToolbarHeight,
child: CloseButton(),
final commonScaffold = isMobile
? CommonScaffold(
actions: isMobile
? null
: [
const SizedBox(
height: kToolbarHeight,
width: kToolbarHeight,
child: CloseButton(),
),
],
title: title,
body: uniqueBody,
)
: Column(
children: [
AppBar(
automaticallyImplyLeading: false,
title: Text(title),
actions: const [
SizedBox(
height: kToolbarHeight,
width: kToolbarHeight,
child: CloseButton(),
)
],
),
Expanded(
flex: 1,
child: uniqueBody,
),
],
title: title,
body: uniqueBody,
);
);
return AnimatedContainer(
duration: kThemeAnimationDuration,
width: isMobile ? viewWidth : extendPageWidth ?? 300,

View File

@@ -594,7 +594,6 @@ Future<T?> showModalSideSheet<T>({
AppBar(
automaticallyImplyLeading: false,
title: Text(title),
centerTitle: false,
actions: const [
SizedBox(
height: kToolbarHeight,

View File

@@ -207,6 +207,7 @@ class _WindowHeaderState extends State<WindowHeader> {
children: [
Positioned(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (_) {
windowManager.startDragging();
},
@@ -214,7 +215,7 @@ class _WindowHeaderState extends State<WindowHeader> {
_updateMaximized();
},
child: Container(
color: context.colorScheme.surface,
color: context.colorScheme.surfaceContainerHigh,
alignment: Alignment.centerLeft,
height: kHeaderHeight,
),
@@ -246,10 +247,10 @@ class AppIcon extends StatelessWidget {
child: const Row(
children: [
SizedBox(
width: 24,
height: 24,
width: 28,
height: 28,
child: CircleAvatar(
foregroundImage: AssetImage("assets/images/icon.png"),
foregroundImage: AssetImage("assets/images/launch_icon.png"),
backgroundColor: Colors.transparent,
),
),

View File

@@ -1,6 +1,6 @@
display_name: FlClash
icon: ./assets/images/icon.png
icon: ./assets/images/launch_icon.png
keywords:
- FlClash

View File

@@ -8,7 +8,7 @@ priority: optional
section: x11
installed_size: 6604
essential: false
icon: ./assets/images/icon.png
icon: ./assets/images/launch_icon.png
keywords:

View File

@@ -8,7 +8,7 @@ priority: optional
section: x11
installed_size: 6604
essential: false
icon: ./assets/images/icon.png
icon: ./assets/images/launch_icon.png
keywords:
- FlClash

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -317,10 +317,10 @@ packages:
dependency: transitive
description:
name: file_selector_windows
sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69"
sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0
url: "https://pub.dev"
source: hosted
version: "0.9.3+2"
version: "0.9.3+1"
fixnum:
dependency: transitive
description:
@@ -377,10 +377,10 @@ packages:
dependency: "direct main"
description:
name: freezed_annotation
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
sha256: f54946fdb1fa7b01f780841937b1a80783a20b393485f3f6cdf336fd6f4705f2
url: "https://pub.dev"
source: hosted
version: "2.4.4"
version: "2.4.2"
frontend_server_client:
dependency: transitive
description:
@@ -401,10 +401,10 @@ packages:
dependency: transitive
description:
name: graphs
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.3.1"
gtk:
dependency: transitive
description:
@@ -417,10 +417,10 @@ packages:
dependency: transitive
description:
name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
version: "1.2.1"
http_multi_server:
dependency: transitive
description:
@@ -457,10 +457,10 @@ packages:
dependency: transitive
description:
name: image_picker_android
sha256: a26dc9a03fe042440c1e4be554fb0fceae2bf6d887d7467fc48c688fa4a81889
sha256: "4161e1f843d8480d2e9025ee22411778c3c9eb7e40076dcf2da23d8242b7b51c"
url: "https://pub.dev"
source: hosted
version: "0.8.12+7"
version: "0.8.12+3"
image_picker_for_web:
dependency: transitive
description:
@@ -729,10 +729,10 @@ packages:
dependency: transitive
description:
name: path_provider_android
sha256: "30c5aa827a6ae95ce2853cdc5fe3971daaac00f6f081c419c013f7f57bff2f5e"
sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a
url: "https://pub.dev"
source: hosted
version: "2.2.7"
version: "2.2.6"
path_provider_foundation:
dependency: transitive
description:
@@ -761,10 +761,10 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.2.1"
petitparser:
dependency: transitive
description:
@@ -904,10 +904,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "034650b71e73629ca08a0bd789fd1d83cc63c2d1e405946f7cef7bc37432f93a"
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.3.2"
shared_preferences_web:
dependency: transitive
description:
@@ -1061,18 +1061,18 @@ packages:
dependency: transitive
description:
name: url_launcher_android
sha256: "95d8027db36a0e52caf55680f91e33ea6aa12a3ce608c90b06f4e429a21067ac"
sha256: ceb2625f0c24ade6ef6778d1de0b2e44f2db71fded235eb52295247feba8c5cf
url: "https://pub.dev"
source: hosted
version: "6.3.5"
version: "6.3.3"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
url: "https://pub.dev"
source: hosted
version: "6.3.1"
version: "6.3.0"
url_launcher_linux:
dependency: transitive
description:
@@ -1109,10 +1109,10 @@ packages:
dependency: transitive
description:
name: url_launcher_windows
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
url: "https://pub.dev"
source: hosted
version: "3.1.2"
version: "3.1.1"
vector_math:
dependency: transitive
description:
@@ -1149,18 +1149,18 @@ packages:
dependency: transitive
description:
name: web_socket
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
url: "https://pub.dev"
source: hosted
version: "0.1.6"
version: "0.1.5"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.0"
webdav_client:
dependency: "direct main"
description:

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.48+202407251
version: 0.8.45+202407201
environment:
sdk: '>=3.1.0 <4.0.0'
@@ -58,7 +58,6 @@ flutter:
assets:
- assets/data/
- assets/images/
- assets/images/avatars/
ffigen:
name: "ClashFFI"
output: 'lib/clash/generated/clash_ffi.dart'

View File

@@ -1,34 +1,10 @@
// ignore_for_file: avoid_print
import 'dart:io';
import 'package:fl_clash/common/common.dart';
void main() async {
// 定义服务器将要监听的地址和端口
final host = InternetAddress.anyIPv4; // 监听所有网络接口
const port = 8080; // 使用 8080 端口
try {
// 创建服务器
final server = await HttpServer.bind(host, port);
print('服务器正在监听 ${server.address.address}:${server.port}');
// 监听请求
await for (HttpRequest request in server) {
handleRequest(request);
}
} catch (e) {
print('服务器错误: $e');
}
print("https://pqjc.site:10000/test.ymal".isUrl);
print("abcd".isUrl);
print("http://10.31.1.221:8848/cfa.yaml".isUrl);
}
void handleRequest(HttpRequest request) {
print(request.headers);
// 处理请求
request.response
..statusCode = HttpStatus.ok
..headers.contentType = ContentType.html
..write('<html><body><h1>Hello, Dart Server!</h1></body></html>');
// 完成响应
request.response.close();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB