Optimize proxies page
Fix ua issues Optimize more details
This commit is contained in:
@@ -423,6 +423,7 @@ func applyConfig() {
|
|||||||
if configParams.IsPatch {
|
if configParams.IsPatch {
|
||||||
patchConfig(cfg.General)
|
patchConfig(cfg.General)
|
||||||
} else {
|
} else {
|
||||||
|
closeConnections()
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
hub.UltraApplyConfig(cfg, true)
|
hub.UltraApplyConfig(cfg, true)
|
||||||
patchSelectGroup()
|
patchSelectGroup()
|
||||||
|
|||||||
13
core/hub.go
13
core/hub.go
@@ -299,7 +299,7 @@ func getConnections() *C.char {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export closeConnections
|
//export closeConnections
|
||||||
func closeConnections() bool {
|
func closeConnections() {
|
||||||
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||||
err := c.Close()
|
err := c.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -307,17 +307,16 @@ func closeConnections() bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export closeConnection
|
//export closeConnection
|
||||||
func closeConnection(id *C.char) bool {
|
func closeConnection(id *C.char) {
|
||||||
connectionId := C.GoString(id)
|
connectionId := C.GoString(id)
|
||||||
err := statistic.DefaultManager.Get(connectionId).Close()
|
c := statistic.DefaultManager.Get(connectionId)
|
||||||
if err != nil {
|
if c == nil {
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
return true
|
_ = c.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
//export getProviders
|
//export getProviders
|
||||||
|
|||||||
@@ -157,9 +157,7 @@ class ApplicationState extends State<Application> {
|
|||||||
GlobalWidgetsLocalizations.delegate
|
GlobalWidgetsLocalizations.delegate
|
||||||
],
|
],
|
||||||
builder: (_, child) {
|
builder: (_, child) {
|
||||||
return PopContainer(
|
return _buildApp(child!);
|
||||||
child: _buildApp(child!),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
scrollBehavior: BaseScrollBehavior(),
|
scrollBehavior: BaseScrollBehavior(),
|
||||||
title: appName,
|
title: appName,
|
||||||
|
|||||||
@@ -319,11 +319,15 @@ class ClashCore {
|
|||||||
return connectionsRaw.map((e) => Connection.fromJson(e)).toList();
|
return connectionsRaw.map((e) => Connection.fromJson(e)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
closeConnections(String id) {
|
closeConnection(String id) {
|
||||||
final idChar = id.toNativeUtf8().cast<Char>();
|
final idChar = id.toNativeUtf8().cast<Char>();
|
||||||
clashFFI.closeConnection(idChar);
|
clashFFI.closeConnection(idChar);
|
||||||
malloc.free(idChar);
|
malloc.free(idChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeConnections() {
|
||||||
|
clashFFI.closeConnections();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final clashCore = ClashCore();
|
final clashCore = ClashCore();
|
||||||
|
|||||||
@@ -5351,16 +5351,16 @@ class ClashFFI {
|
|||||||
late final _getConnections =
|
late final _getConnections =
|
||||||
_getConnectionsPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
_getConnectionsPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||||
|
|
||||||
int closeConnections() {
|
void closeConnections() {
|
||||||
return _closeConnections();
|
return _closeConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _closeConnectionsPtr =
|
late final _closeConnectionsPtr =
|
||||||
_lookup<ffi.NativeFunction<GoUint8 Function()>>('closeConnections');
|
_lookup<ffi.NativeFunction<ffi.Void Function()>>('closeConnections');
|
||||||
late final _closeConnections =
|
late final _closeConnections =
|
||||||
_closeConnectionsPtr.asFunction<int Function()>();
|
_closeConnectionsPtr.asFunction<void Function()>();
|
||||||
|
|
||||||
int closeConnection(
|
void closeConnection(
|
||||||
ffi.Pointer<ffi.Char> id,
|
ffi.Pointer<ffi.Char> id,
|
||||||
) {
|
) {
|
||||||
return _closeConnection(
|
return _closeConnection(
|
||||||
@@ -5369,10 +5369,10 @@ class ClashFFI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
late final _closeConnectionPtr =
|
late final _closeConnectionPtr =
|
||||||
_lookup<ffi.NativeFunction<GoUint8 Function(ffi.Pointer<ffi.Char>)>>(
|
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
|
||||||
'closeConnection');
|
'closeConnection');
|
||||||
late final _closeConnection =
|
late final _closeConnection =
|
||||||
_closeConnectionPtr.asFunction<int Function(ffi.Pointer<ffi.Char>)>();
|
_closeConnectionPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
||||||
|
|
||||||
ffi.Pointer<ffi.Char> getProviders() {
|
ffi.Pointer<ffi.Char> getProviders() {
|
||||||
return _getProviders();
|
return _getProviders();
|
||||||
|
|||||||
@@ -10,4 +10,58 @@ extension IterableExt<T> on Iterable<T> {
|
|||||||
yield iterator.current;
|
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; // 这行理论上不会执行到,但为了完整性保留
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,3 +14,14 @@ class BaseScrollBehavior extends MaterialScrollBehavior {
|
|||||||
PointerDeviceKind.unknown,
|
PointerDeviceKind.unknown,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HiddenBarScrollBehavior extends BaseScrollBehavior {
|
||||||
|
@override
|
||||||
|
Widget buildScrollbar(
|
||||||
|
BuildContext context,
|
||||||
|
Widget child,
|
||||||
|
ScrollableDetails details,
|
||||||
|
) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class Window {
|
|||||||
await windowManager.ensureInitialized();
|
await windowManager.ensureInitialized();
|
||||||
WindowOptions windowOptions = WindowOptions(
|
WindowOptions windowOptions = WindowOptions(
|
||||||
size: Size(props.width, props.height),
|
size: Size(props.width, props.height),
|
||||||
minimumSize: const Size(380, 600),
|
minimumSize: const Size(380, 500),
|
||||||
titleBarStyle: TitleBarStyle.hidden,
|
titleBarStyle: TitleBarStyle.hidden,
|
||||||
);
|
);
|
||||||
if (props.left != null || props.top != null) {
|
if (props.left != null || props.top != null) {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class AppController {
|
|||||||
final updateId = config.profiles.first.id;
|
final updateId = config.profiles.first.id;
|
||||||
changeProfile(updateId);
|
changeProfile(updateId);
|
||||||
} else {
|
} else {
|
||||||
changeProfile(null);
|
updateSystemProxy(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,6 +193,7 @@ class AppController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleBackOrExit() async {
|
handleBackOrExit() async {
|
||||||
|
print(config.isMinimizeOnExit);
|
||||||
if (config.isMinimizeOnExit) {
|
if (config.isMinimizeOnExit) {
|
||||||
if (system.isDesktop) {
|
if (system.isDesktop) {
|
||||||
await savePreferences();
|
await savePreferences();
|
||||||
@@ -410,8 +411,7 @@ class AppController {
|
|||||||
addProfileFormURL(url);
|
addProfileFormURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
int get columns =>
|
int get columns => other.getColumns(appState.viewMode, config.proxiesColumns);
|
||||||
other.getColumns(appState.viewMode, config.proxiesColumns);
|
|
||||||
|
|
||||||
updateViewWidth(double width) {
|
updateViewWidth(double width) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
@@ -453,4 +453,9 @@ class AppController {
|
|||||||
ProxiesSortType.name => _sortOfName(proxies),
|
ProxiesSortType.name => _sortOfName(proxies),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getCurrentSelectedName(String groupName) {
|
||||||
|
final group = appState.getGroupWithName(groupName);
|
||||||
|
return config.currentSelectedMap[groupName] ?? group?.now ?? '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:fl_clash/common/common.dart';
|
|||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:fl_clash/widgets/list.dart';
|
import 'package:fl_clash/widgets/list.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class Contributor {
|
class Contributor {
|
||||||
@@ -90,7 +89,7 @@ class AboutFragment extends StatelessWidget {
|
|||||||
];
|
];
|
||||||
return generateSection(
|
return generateSection(
|
||||||
separated: false,
|
separated: false,
|
||||||
title: appLocalizations.contributors,
|
title: appLocalizations.otherContributors,
|
||||||
items: [
|
items: [
|
||||||
ListItem(
|
ListItem(
|
||||||
title: SingleChildScrollView(
|
title: SingleChildScrollView(
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
|||||||
return generateSection(
|
return generateSection(
|
||||||
title: appLocalizations.app,
|
title: appLocalizations.app,
|
||||||
items: [
|
items: [
|
||||||
if (Platform.isAndroid)
|
if (Platform.isAndroid)...[
|
||||||
Selector<Config, bool>(
|
Selector<Config, bool>(
|
||||||
selector: (_, config) => config.allowBypass,
|
selector: (_, config) => config.allowBypass,
|
||||||
builder: (_, allowBypass, __) {
|
builder: (_, allowBypass, __) {
|
||||||
@@ -159,7 +159,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (Platform.isAndroid)
|
|
||||||
Selector<Config, bool>(
|
Selector<Config, bool>(
|
||||||
selector: (_, config) => config.systemProxy,
|
selector: (_, config) => config.systemProxy,
|
||||||
builder: (_, systemProxy, __) {
|
builder: (_, systemProxy, __) {
|
||||||
@@ -177,6 +176,24 @@ 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, bool>(
|
||||||
selector: (_, config) => config.isCompatible,
|
selector: (_, config) => config.isCompatible,
|
||||||
builder: (_, isCompatible, __) {
|
builder: (_, isCompatible, __) {
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
|
|||||||
timer = Timer.periodic(
|
timer = Timer.periodic(
|
||||||
const Duration(seconds: 1),
|
const Duration(seconds: 1),
|
||||||
(timer) {
|
(timer) {
|
||||||
connectionsNotifier.value = connectionsNotifier.value
|
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
||||||
.copyWith(connections: clashCore.getConnections());
|
connections: clashCore.getConnections(),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -50,6 +51,18 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
|
|||||||
final commonScaffoldState =
|
final commonScaffoldState =
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
context.findAncestorStateOfType<CommonScaffoldState>();
|
||||||
commonScaffoldState?.actions = [
|
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(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showSearch(
|
showSearch(
|
||||||
@@ -87,7 +100,7 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_handleBlockConnection(String id) {
|
_handleBlockConnection(String id) {
|
||||||
clashCore.closeConnections(id);
|
clashCore.closeConnection(id);
|
||||||
connectionsNotifier.value = connectionsNotifier.value
|
connectionsNotifier.value = connectionsNotifier.value
|
||||||
.copyWith(connections: clashCore.getConnections());
|
.copyWith(connections: clashCore.getConnections());
|
||||||
}
|
}
|
||||||
@@ -227,7 +240,7 @@ class ConnectionsSearchDelegate extends SearchDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_handleBlockConnection(String id) {
|
_handleBlockConnection(String id) {
|
||||||
clashCore.closeConnections(id);
|
clashCore.closeConnection(id);
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
||||||
connections: clashCore.getConnections(),
|
connections: clashCore.getConnections(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class _DashboardFragmentState extends State<DashboardFragment> {
|
|||||||
// final viewMode = other.getViewMode(viewWidth);
|
// final viewMode = other.getViewMode(viewWidth);
|
||||||
// final isDesktop = viewMode == ViewMode.desktop;
|
// final isDesktop = viewMode == ViewMode.desktop;
|
||||||
return Grid(
|
return Grid(
|
||||||
crossAxisCount: max(4 * ((viewWidth / 320).ceil()), 8),
|
crossAxisCount: max(4 * ((viewWidth / 350).ceil()), 8),
|
||||||
crossAxisSpacing: 16,
|
crossAxisSpacing: 16,
|
||||||
mainAxisSpacing: 16,
|
mainAxisSpacing: 16,
|
||||||
children: const [
|
children: const [
|
||||||
|
|||||||
@@ -313,13 +313,6 @@ class _ProfileItemState extends State<ProfileItem> {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
|
||||||
appLocalizations.expirationTime,
|
|
||||||
style: textTheme.labelMedium?.toLighter,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 4,
|
|
||||||
),
|
|
||||||
Text(
|
Text(
|
||||||
expireShow,
|
expireShow,
|
||||||
style: textTheme.labelMedium?.toLighter,
|
style: textTheme.labelMedium?.toLighter,
|
||||||
|
|||||||
@@ -105,11 +105,10 @@ class ProxyCard extends StatelessWidget {
|
|||||||
groupName,
|
groupName,
|
||||||
proxy.name,
|
proxy.name,
|
||||||
);
|
);
|
||||||
clashCore.changeProxy(
|
globalState.changeProxy(
|
||||||
ChangeProxyParams(
|
config: appController.config,
|
||||||
groupName: groupName,
|
groupName: groupName,
|
||||||
proxyName: proxy.name,
|
proxyName: proxy.name,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
75
lib/fragments/proxies/common.dart
Normal file
75
lib/fragments/proxies/common.dart
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
@@ -1,404 +0,0 @@
|
|||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +1,512 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:fl_clash/models/models.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:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'group.dart';
|
import 'card.dart';
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
typedef GroupNameProxiesMap = Map<String, List<Proxy>>;
|
||||||
|
|
||||||
class ProxiesListFragment extends StatefulWidget {
|
class ProxiesListFragment extends StatefulWidget {
|
||||||
const ProxiesListFragment({super.key});
|
const ProxiesListFragment({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ProxiesListFragment> createState() =>
|
State<ProxiesListFragment> createState() => _ProxiesListFragmentState();
|
||||||
_ProxiesListFragmentState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ProxiesListFragmentState
|
class _ProxiesListFragmentState extends State<ProxiesListFragment> {
|
||||||
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Selector2<AppState, Config, ProxiesSelectorState>(
|
return Selector2<AppState, Config, ProxiesListSelectorState>(
|
||||||
selector: (_, appState, config) {
|
selector: (_, appState, config) {
|
||||||
final currentGroups = appState.currentGroups;
|
final currentGroups = appState.currentGroups;
|
||||||
final groupNames = currentGroups.map((e) => e.name).toList();
|
final groupNames = currentGroups.map((e) => e.name).toList();
|
||||||
return ProxiesSelectorState(
|
return ProxiesListSelectorState(
|
||||||
groupNames: groupNames,
|
groupNames: groupNames,
|
||||||
currentGroupName: config.currentGroupName,
|
currentUnfoldSet: config.currentUnfoldSet,
|
||||||
|
proxyCardType: config.proxyCardType,
|
||||||
|
proxiesSortType: config.proxiesSortType,
|
||||||
|
columns: globalState.appController.columns,
|
||||||
|
sortNum: appState.sortNum,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
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, __) {
|
builder: (_, state, __) {
|
||||||
return ListView.separated(
|
final items = _buildItems(
|
||||||
padding: const EdgeInsets.all(16),
|
groupNames: state.groupNames,
|
||||||
itemCount: state.groupNames.length,
|
currentUnfoldSet: state.currentUnfoldSet,
|
||||||
itemBuilder: (_, index) {
|
columns: state.columns,
|
||||||
final groupName = state.groupNames[index];
|
type: state.proxyCardType,
|
||||||
return ProxyGroupView(
|
);
|
||||||
key: PageStorageKey(groupName),
|
final itemsOffset = _getItemHeightList(items, state.proxyCardType);
|
||||||
groupName: groupName,
|
return Scrollbar(
|
||||||
type: ProxiesType.list,
|
controller: _controller,
|
||||||
);
|
thumbVisibility: true,
|
||||||
},
|
trackVisibility: true,
|
||||||
separatorBuilder: (BuildContext context, int index) {
|
thickness: 8,
|
||||||
return const SizedBox(
|
radius: const Radius.circular(8),
|
||||||
height: 16,
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,12 +17,26 @@ class ProxiesFragment extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ProxiesFragmentState extends State<ProxiesFragment> {
|
class _ProxiesFragmentState extends State<ProxiesFragment> {
|
||||||
|
final GlobalKey<ProxiesTabFragmentState> _proxiesTabKey = GlobalKey();
|
||||||
|
|
||||||
_initActions() {
|
_initActions(ProxiesType proxiesType) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
final commonScaffoldState =
|
final commonScaffoldState =
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
context.findAncestorStateOfType<CommonScaffoldState>();
|
||||||
commonScaffoldState?.actions = [
|
commonScaffoldState?.actions = [
|
||||||
|
if (proxiesType == ProxiesType.tab) ...[
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
_proxiesTabKey.currentState?.scrollToGroupSelected();
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.gps_fixed,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
)
|
||||||
|
],
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showSheet(
|
showSheet(
|
||||||
@@ -43,23 +57,24 @@ class _ProxiesFragmentState extends State<ProxiesFragment> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Selector<AppState, bool>(
|
return Selector<Config, ProxiesType>(
|
||||||
selector: (_, appState) => appState.currentLabel == 'proxies',
|
selector: (_, config) => config.proxiesType,
|
||||||
builder: (_, isCurrent, child) {
|
builder: (_, proxiesType, __) {
|
||||||
if (isCurrent) {
|
return Selector<AppState, bool>(
|
||||||
_initActions();
|
selector: (_, appState) => appState.currentLabel == 'proxies',
|
||||||
}
|
builder: (_, isCurrent, child) {
|
||||||
return child!;
|
if (isCurrent) {
|
||||||
|
_initActions(proxiesType);
|
||||||
|
}
|
||||||
|
return switch (proxiesType) {
|
||||||
|
ProxiesType.tab => ProxiesTabFragment(
|
||||||
|
key: _proxiesTabKey,
|
||||||
|
),
|
||||||
|
ProxiesType.list => const ProxiesListFragment(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: Selector<Config, ProxiesType>(
|
|
||||||
selector: (_, config) => config.proxiesType,
|
|
||||||
builder: (_, proxiesType, __) {
|
|
||||||
return switch (proxiesType) {
|
|
||||||
ProxiesType.tab => const ProxiesTabFragment(),
|
|
||||||
ProxiesType.list => const ProxiesListFragment(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,19 +8,23 @@ import 'package:fl_clash/widgets/widgets.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'group.dart';
|
import 'card.dart';
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
typedef GroupNameKeyMap = Map<String, GlobalObjectKey<ProxyGroupViewState>>;
|
||||||
|
|
||||||
class ProxiesTabFragment extends StatefulWidget {
|
class ProxiesTabFragment extends StatefulWidget {
|
||||||
const ProxiesTabFragment({super.key});
|
const ProxiesTabFragment({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ProxiesTabFragment> createState() => _ProxiesTabFragmentState();
|
State<ProxiesTabFragment> createState() => ProxiesTabFragmentState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
class ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
TabController? _tabController;
|
TabController? _tabController;
|
||||||
final hasMoreButtonNotifier = ValueNotifier<bool>(false);
|
final _hasMoreButtonNotifier = ValueNotifier<bool>(false);
|
||||||
|
GroupNameKeyMap _keyMap = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -28,6 +32,11 @@ class _ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
|||||||
_tabController?.dispose();
|
_tabController?.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollToGroupSelected() {
|
||||||
|
final currentGroupName = globalState.appController.config.currentGroupName;
|
||||||
|
_keyMap[currentGroupName]?.currentState?.scrollToSelected();
|
||||||
|
}
|
||||||
|
|
||||||
_buildMoreButton() {
|
_buildMoreButton() {
|
||||||
return Selector<AppState, bool>(
|
return Selector<AppState, bool>(
|
||||||
selector: (_, appState) => appState.viewMode == ViewMode.mobile,
|
selector: (_, appState) => appState.viewMode == ViewMode.mobile,
|
||||||
@@ -83,6 +92,7 @@ class _ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
|||||||
.updateCurrentGroupName(
|
.updateCurrentGroupName(
|
||||||
groupName,
|
groupName,
|
||||||
);
|
);
|
||||||
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
isSelected: groupName == state.currentGroupName,
|
isSelected: groupName == state.currentGroupName,
|
||||||
)
|
)
|
||||||
@@ -126,18 +136,29 @@ class _ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
|||||||
initialIndex: index == -1 ? 0 : index,
|
initialIndex: index == -1 ? 0 : index,
|
||||||
vsync: this,
|
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(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
NotificationListener<ScrollMetricsNotification>(
|
NotificationListener<ScrollMetricsNotification>(
|
||||||
onNotification: (scrollNotification) {
|
onNotification: (scrollNotification) {
|
||||||
hasMoreButtonNotifier.value =
|
_hasMoreButtonNotifier.value =
|
||||||
scrollNotification.metrics.maxScrollExtent > 0;
|
scrollNotification.metrics.maxScrollExtent > 0;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
child: ValueListenableBuilder(
|
child: ValueListenableBuilder(
|
||||||
valueListenable: hasMoreButtonNotifier,
|
valueListenable: _hasMoreButtonNotifier,
|
||||||
builder: (_, value, child) {
|
builder: (_, value, child) {
|
||||||
return Stack(
|
return Stack(
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
@@ -199,16 +220,7 @@ class _ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: [
|
children: children,
|
||||||
for (final groupName in state.groupNames)
|
|
||||||
KeepContainer(
|
|
||||||
key: ObjectKey(groupName),
|
|
||||||
child: ProxyGroupView(
|
|
||||||
groupName: groupName,
|
|
||||||
type: ProxiesType.tab,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -217,3 +229,205 @@ 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/models/models.dart';
|
import 'package:fl_clash/models/models.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
@@ -23,27 +21,6 @@ class ThemeModeItem {
|
|||||||
class ThemeFragment extends StatelessWidget {
|
class ThemeFragment extends StatelessWidget {
|
||||||
const ThemeFragment({super.key});
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final previewCard = Padding(
|
final previewCard = Padding(
|
||||||
|
|||||||
@@ -214,5 +214,7 @@
|
|||||||
"proxyGroup": "Proxy group",
|
"proxyGroup": "Proxy group",
|
||||||
"go": "Go",
|
"go": "Go",
|
||||||
"externalLink": "External link",
|
"externalLink": "External link",
|
||||||
"contributors": "Contributors"
|
"otherContributors": "Other contributors",
|
||||||
|
"autoCloseConnections": "Auto lose connections",
|
||||||
|
"autoCloseConnectionsDesc": "Auto close connections after change node"
|
||||||
}
|
}
|
||||||
@@ -214,5 +214,7 @@
|
|||||||
"proxyGroup": "代理组",
|
"proxyGroup": "代理组",
|
||||||
"go": "前往",
|
"go": "前往",
|
||||||
"externalLink": "外部链接",
|
"externalLink": "外部链接",
|
||||||
"contributors": "贡献者"
|
"otherContributors": "其他贡献者",
|
||||||
|
"autoCloseConnections": "自动关闭连接",
|
||||||
|
"autoCloseConnectionsDesc": "切换节点后自动关闭连接"
|
||||||
}
|
}
|
||||||
@@ -58,6 +58,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
MessageLookupByLibrary.simpleMessage("Auto check updates"),
|
MessageLookupByLibrary.simpleMessage("Auto check updates"),
|
||||||
"autoCheckUpdateDesc": MessageLookupByLibrary.simpleMessage(
|
"autoCheckUpdateDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"Auto check for updates when the app starts"),
|
"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"),
|
"autoLaunch": MessageLookupByLibrary.simpleMessage("AutoLaunch"),
|
||||||
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage(
|
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"Follow the system self startup"),
|
"Follow the system self startup"),
|
||||||
@@ -97,7 +101,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage(
|
"connectionsDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"View current connections data"),
|
"View current connections data"),
|
||||||
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
|
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
|
||||||
"contributors": MessageLookupByLibrary.simpleMessage("Contributors"),
|
|
||||||
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
|
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
|
||||||
"core": MessageLookupByLibrary.simpleMessage("Core"),
|
"core": MessageLookupByLibrary.simpleMessage("Core"),
|
||||||
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
|
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
|
||||||
@@ -206,6 +209,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"),
|
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"),
|
||||||
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
|
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
|
||||||
"other": MessageLookupByLibrary.simpleMessage("Other"),
|
"other": MessageLookupByLibrary.simpleMessage("Other"),
|
||||||
|
"otherContributors":
|
||||||
|
MessageLookupByLibrary.simpleMessage("Other contributors"),
|
||||||
"outboundMode": MessageLookupByLibrary.simpleMessage("Outbound mode"),
|
"outboundMode": MessageLookupByLibrary.simpleMessage("Outbound mode"),
|
||||||
"override": MessageLookupByLibrary.simpleMessage("Override"),
|
"override": MessageLookupByLibrary.simpleMessage("Override"),
|
||||||
"overrideDesc": MessageLookupByLibrary.simpleMessage(
|
"overrideDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"autoCheckUpdate": MessageLookupByLibrary.simpleMessage("自动检查更新"),
|
"autoCheckUpdate": MessageLookupByLibrary.simpleMessage("自动检查更新"),
|
||||||
"autoCheckUpdateDesc":
|
"autoCheckUpdateDesc":
|
||||||
MessageLookupByLibrary.simpleMessage("应用启动时自动检查更新"),
|
MessageLookupByLibrary.simpleMessage("应用启动时自动检查更新"),
|
||||||
|
"autoCloseConnections": MessageLookupByLibrary.simpleMessage("自动关闭连接"),
|
||||||
|
"autoCloseConnectionsDesc":
|
||||||
|
MessageLookupByLibrary.simpleMessage("切换节点后自动关闭连接"),
|
||||||
"autoLaunch": MessageLookupByLibrary.simpleMessage("自启动"),
|
"autoLaunch": MessageLookupByLibrary.simpleMessage("自启动"),
|
||||||
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage("跟随系统自启动"),
|
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage("跟随系统自启动"),
|
||||||
"autoRun": MessageLookupByLibrary.simpleMessage("自动运行"),
|
"autoRun": MessageLookupByLibrary.simpleMessage("自动运行"),
|
||||||
@@ -79,7 +82,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"connections": MessageLookupByLibrary.simpleMessage("连接"),
|
"connections": MessageLookupByLibrary.simpleMessage("连接"),
|
||||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
||||||
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
||||||
"contributors": MessageLookupByLibrary.simpleMessage("贡献者"),
|
|
||||||
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
||||||
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
||||||
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
|
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
|
||||||
@@ -169,6 +171,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
|
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
|
||||||
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
|
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
|
||||||
"other": MessageLookupByLibrary.simpleMessage("其他"),
|
"other": MessageLookupByLibrary.simpleMessage("其他"),
|
||||||
|
"otherContributors": MessageLookupByLibrary.simpleMessage("其他贡献者"),
|
||||||
"outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"),
|
"outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"),
|
||||||
"override": MessageLookupByLibrary.simpleMessage("覆写"),
|
"override": MessageLookupByLibrary.simpleMessage("覆写"),
|
||||||
"overrideDesc": MessageLookupByLibrary.simpleMessage("覆写代理相关配置"),
|
"overrideDesc": MessageLookupByLibrary.simpleMessage("覆写代理相关配置"),
|
||||||
|
|||||||
@@ -2200,11 +2200,31 @@ class AppLocalizations {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Contributors`
|
/// `Other contributors`
|
||||||
String get contributors {
|
String get otherContributors {
|
||||||
return Intl.message(
|
return Intl.message(
|
||||||
'Contributors',
|
'Other contributors',
|
||||||
name: '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: '',
|
desc: '',
|
||||||
args: [],
|
args: [],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ Future<void> main() async {
|
|||||||
Future<void> vpnService() async {
|
Future<void> vpnService() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
globalState.isVpnService = true;
|
globalState.isVpnService = true;
|
||||||
|
globalState.packageInfo = await PackageInfo.fromPlatform();
|
||||||
final config = await preferences.getConfig() ?? Config();
|
final config = await preferences.getConfig() ?? Config();
|
||||||
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
|
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
|
||||||
final appState = AppState(
|
final appState = AppState(
|
||||||
@@ -86,11 +87,10 @@ Future<void> vpnService() async {
|
|||||||
final currentSelectedMap = config.currentSelectedMap;
|
final currentSelectedMap = config.currentSelectedMap;
|
||||||
final proxyName = currentSelectedMap[groupName];
|
final proxyName = currentSelectedMap[groupName];
|
||||||
if (proxyName == null) return;
|
if (proxyName == null) return;
|
||||||
clashCore.changeProxy(
|
globalState.changeProxy(
|
||||||
ChangeProxyParams(
|
config: config,
|
||||||
groupName: groupName,
|
groupName: groupName,
|
||||||
proxyName: proxyName,
|
proxyName: proxyName,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -119,7 +119,7 @@ Future<void> vpnService() async {
|
|||||||
|
|
||||||
globalState.updateTraffic();
|
globalState.updateTraffic();
|
||||||
globalState.updateFunctionLists = [
|
globalState.updateFunctionLists = [
|
||||||
() {
|
() {
|
||||||
globalState.updateTraffic();
|
globalState.updateTraffic();
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -137,8 +137,7 @@ class ServiceMessageHandler with ServiceMessageListener {
|
|||||||
required Function(Process process) onProcess,
|
required Function(Process process) onProcess,
|
||||||
required Function(String runTime) onStarted,
|
required Function(String runTime) onStarted,
|
||||||
required Function(String groupName) onLoaded,
|
required Function(String groupName) onLoaded,
|
||||||
})
|
}) : _onProtect = onProtect,
|
||||||
: _onProtect = onProtect,
|
|
||||||
_onProcess = onProcess,
|
_onProcess = onProcess,
|
||||||
_onStarted = onStarted,
|
_onStarted = onStarted,
|
||||||
_onLoaded = onLoaded;
|
_onLoaded = onLoaded;
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ class ClashConfig extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(name: "global-ua", defaultValue: null)
|
@JsonKey(name: "global-ua", includeFromJson: false, includeToJson: true)
|
||||||
String get globalUa {
|
String get globalUa {
|
||||||
if (_globalRealUa == null) {
|
if (_globalRealUa == null) {
|
||||||
return globalState.packageInfo.ua;
|
return globalState.packageInfo.ua;
|
||||||
@@ -320,7 +320,6 @@ class ClashConfig extends ChangeNotifier {
|
|||||||
_geodataLoader = clashConfig._geodataLoader;
|
_geodataLoader = clashConfig._geodataLoader;
|
||||||
_dns = clashConfig._dns;
|
_dns = clashConfig._dns;
|
||||||
_rules = clashConfig._rules;
|
_rules = clashConfig._rules;
|
||||||
_globalRealUa = clashConfig.globalRealUa;
|
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ class Config extends ChangeNotifier {
|
|||||||
bool _systemProxy;
|
bool _systemProxy;
|
||||||
bool _isExclude;
|
bool _isExclude;
|
||||||
DAV? _dav;
|
DAV? _dav;
|
||||||
|
bool _isCloseConnections;
|
||||||
ProxiesType _proxiesType;
|
ProxiesType _proxiesType;
|
||||||
ProxyCardType _proxyCardType;
|
ProxyCardType _proxyCardType;
|
||||||
int _proxiesColumns;
|
int _proxiesColumns;
|
||||||
@@ -84,6 +85,7 @@ class Config extends ChangeNotifier {
|
|||||||
_autoLaunch = false,
|
_autoLaunch = false,
|
||||||
_silentLaunch = false,
|
_silentLaunch = false,
|
||||||
_autoRun = false,
|
_autoRun = false,
|
||||||
|
_isCloseConnections = false,
|
||||||
_themeMode = ThemeMode.system,
|
_themeMode = ThemeMode.system,
|
||||||
_openLog = false,
|
_openLog = false,
|
||||||
_isCompatible = true,
|
_isCompatible = true,
|
||||||
@@ -405,6 +407,18 @@ class Config extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool get isCloseConnections {
|
||||||
|
return _isCloseConnections;
|
||||||
|
}
|
||||||
|
|
||||||
|
set isCloseConnections(bool value) {
|
||||||
|
if (_isCloseConnections != value) {
|
||||||
|
_isCloseConnections = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JsonKey(
|
@JsonKey(
|
||||||
defaultValue: ProxiesType.tab,
|
defaultValue: ProxiesType.tab,
|
||||||
unknownEnumValue: ProxiesType.tab,
|
unknownEnumValue: ProxiesType.tab,
|
||||||
@@ -482,6 +496,7 @@ class Config extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
if (onlyProfiles) return;
|
if (onlyProfiles) return;
|
||||||
_currentProfileId = config._currentProfileId;
|
_currentProfileId = config._currentProfileId;
|
||||||
|
_isCloseConnections = config._isCloseConnections;
|
||||||
_isCompatible = config._isCompatible;
|
_isCompatible = config._isCompatible;
|
||||||
_autoLaunch = config._autoLaunch;
|
_autoLaunch = config._autoLaunch;
|
||||||
_silentLaunch = config._silentLaunch;
|
_silentLaunch = config._silentLaunch;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// ignore_for_file: invalid_annotation_target
|
// ignore_for_file: invalid_annotation_target
|
||||||
|
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
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:fl_clash/models/models.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) =>
|
|||||||
'tun': instance.tun,
|
'tun': instance.tun,
|
||||||
'dns': instance.dns,
|
'dns': instance.dns,
|
||||||
'rules': instance.rules,
|
'rules': instance.rules,
|
||||||
|
'global-ua': instance.globalUa,
|
||||||
'global-real-ua': instance.globalRealUa,
|
'global-real-ua': instance.globalRealUa,
|
||||||
'geox-url': instance.geoXUrl,
|
'geox-url': instance.geoXUrl,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
|
|||||||
..isCompatible = json['isCompatible'] as bool? ?? true
|
..isCompatible = json['isCompatible'] as bool? ?? true
|
||||||
..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true
|
..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true
|
||||||
..allowBypass = json['allowBypass'] as bool? ?? true
|
..allowBypass = json['allowBypass'] as bool? ?? true
|
||||||
..systemProxy = json['systemProxy'] as bool? ?? true
|
..systemProxy = json['systemProxy'] as bool? ?? false
|
||||||
|
..isCloseConnections = json['isCloseConnections'] as bool? ?? false
|
||||||
..proxiesType = $enumDecodeNullable(_$ProxiesTypeEnumMap, json['proxiesType'],
|
..proxiesType = $enumDecodeNullable(_$ProxiesTypeEnumMap, json['proxiesType'],
|
||||||
unknownValue: ProxiesType.tab) ??
|
unknownValue: ProxiesType.tab) ??
|
||||||
ProxiesType.tab
|
ProxiesType.tab
|
||||||
@@ -68,6 +69,7 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
|||||||
'autoCheckUpdate': instance.autoCheckUpdate,
|
'autoCheckUpdate': instance.autoCheckUpdate,
|
||||||
'allowBypass': instance.allowBypass,
|
'allowBypass': instance.allowBypass,
|
||||||
'systemProxy': instance.systemProxy,
|
'systemProxy': instance.systemProxy,
|
||||||
|
'isCloseConnections': instance.isCloseConnections,
|
||||||
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
|
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
|
||||||
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
|
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
|
||||||
'proxiesColumns': instance.proxiesColumns,
|
'proxiesColumns': instance.proxiesColumns,
|
||||||
|
|||||||
@@ -1756,6 +1756,257 @@ abstract class _ProxiesSelectorState implements ProxiesSelectorState {
|
|||||||
get copyWith => throw _privateConstructorUsedError;
|
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
|
/// @nodoc
|
||||||
mixin _$ProxyGroupSelectorState {
|
mixin _$ProxyGroupSelectorState {
|
||||||
ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError;
|
ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError;
|
||||||
@@ -2401,3 +2652,151 @@ abstract class _ColumnsSelectorState implements ColumnsSelectorState {
|
|||||||
_$$ColumnsSelectorStateImplCopyWith<_$ColumnsSelectorStateImpl>
|
_$$ColumnsSelectorStateImplCopyWith<_$ColumnsSelectorStateImpl>
|
||||||
get copyWith => throw _privateConstructorUsedError;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -99,6 +99,18 @@ class ProxiesSelectorState with _$ProxiesSelectorState {
|
|||||||
}) = _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
|
@freezed
|
||||||
class ProxyGroupSelectorState with _$ProxyGroupSelectorState {
|
class ProxyGroupSelectorState with _$ProxyGroupSelectorState {
|
||||||
const factory ProxyGroupSelectorState({
|
const factory ProxyGroupSelectorState({
|
||||||
@@ -133,3 +145,11 @@ class ColumnsSelectorState with _$ColumnsSelectorState {
|
|||||||
required ViewMode viewMode,
|
required ViewMode viewMode,
|
||||||
}) = _ColumnsSelectorState;
|
}) = _ColumnsSelectorState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class ProxiesListHeaderSelectorState with _$ProxiesListHeaderSelectorState {
|
||||||
|
const factory ProxiesListHeaderSelectorState({
|
||||||
|
required double offset,
|
||||||
|
required int currentIndex,
|
||||||
|
}) = _ProxiesListHeaderSelectorState;
|
||||||
|
}
|
||||||
@@ -95,55 +95,57 @@ class HomePage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(
|
return PopContainer(
|
||||||
builder: (_, container) {
|
child: LayoutBuilder(
|
||||||
final appController = globalState.appController;
|
builder: (_, container) {
|
||||||
final maxWidth = container.maxWidth;
|
final appController = globalState.appController;
|
||||||
if (appController.appState.viewWidth != maxWidth) {
|
final maxWidth = container.maxWidth;
|
||||||
globalState.appController.updateViewWidth(maxWidth);
|
if (appController.appState.viewWidth != maxWidth) {
|
||||||
}
|
globalState.appController.updateViewWidth(maxWidth);
|
||||||
return Selector2<AppState, Config, HomeSelectorState>(
|
}
|
||||||
selector: (_, appState, config) {
|
return Selector2<AppState, Config, HomeSelectorState>(
|
||||||
return HomeSelectorState(
|
selector: (_, appState, config) {
|
||||||
currentLabel: appState.currentLabel,
|
return HomeSelectorState(
|
||||||
navigationItems: appState.currentNavigationItems,
|
currentLabel: appState.currentLabel,
|
||||||
viewMode: other.getViewMode(maxWidth),
|
navigationItems: appState.currentNavigationItems,
|
||||||
locale: config.locale,
|
viewMode: other.getViewMode(maxWidth),
|
||||||
);
|
locale: config.locale,
|
||||||
},
|
);
|
||||||
builder: (_, state, child) {
|
},
|
||||||
final viewMode = state.viewMode;
|
builder: (_, state, child) {
|
||||||
final navigationItems = state.navigationItems;
|
final viewMode = state.viewMode;
|
||||||
final currentLabel = state.currentLabel;
|
final navigationItems = state.navigationItems;
|
||||||
final index = navigationItems.lastIndexWhere(
|
final currentLabel = state.currentLabel;
|
||||||
(element) => element.label == currentLabel,
|
final index = navigationItems.lastIndexWhere(
|
||||||
);
|
(element) => element.label == currentLabel,
|
||||||
final currentIndex = index == -1 ? 0 : index;
|
);
|
||||||
final navigationBar = _getNavigationBar(
|
final currentIndex = index == -1 ? 0 : index;
|
||||||
context: context,
|
final navigationBar = _getNavigationBar(
|
||||||
viewMode: viewMode,
|
context: context,
|
||||||
navigationItems: navigationItems,
|
viewMode: viewMode,
|
||||||
currentIndex: currentIndex,
|
navigationItems: navigationItems,
|
||||||
);
|
currentIndex: currentIndex,
|
||||||
final bottomNavigationBar =
|
);
|
||||||
viewMode == ViewMode.mobile ? navigationBar : null;
|
final bottomNavigationBar =
|
||||||
final sideNavigationBar =
|
viewMode == ViewMode.mobile ? navigationBar : null;
|
||||||
viewMode != ViewMode.mobile ? navigationBar : null;
|
final sideNavigationBar =
|
||||||
return CommonScaffold(
|
viewMode != ViewMode.mobile ? navigationBar : null;
|
||||||
key: globalState.homeScaffoldKey,
|
return CommonScaffold(
|
||||||
title: Intl.message(
|
key: globalState.homeScaffoldKey,
|
||||||
currentLabel,
|
title: Intl.message(
|
||||||
),
|
currentLabel,
|
||||||
sideNavigationBar: sideNavigationBar,
|
),
|
||||||
body: child!,
|
sideNavigationBar: sideNavigationBar,
|
||||||
bottomNavigationBar: bottomNavigationBar,
|
body: child!,
|
||||||
);
|
bottomNavigationBar: bottomNavigationBar,
|
||||||
},
|
);
|
||||||
child: const HomeBody(
|
},
|
||||||
key: Key("home_boy"),
|
child: const HomeBody(
|
||||||
),
|
key: Key("home_boy"),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ class Proxy extends ProxyPlatform {
|
|||||||
|
|
||||||
_handleServiceMessage(String message) {
|
_handleServiceMessage(String message) {
|
||||||
final m = ServiceMessage.fromJson(json.decode(message));
|
final m = ServiceMessage.fromJson(json.decode(message));
|
||||||
|
debugPrint(m.toString());
|
||||||
switch (m.type) {
|
switch (m.type) {
|
||||||
case ServiceMessageType.protect:
|
case ServiceMessageType.protect:
|
||||||
_serviceMessageHandler?.onProtect(Fd.fromJson(m.data));
|
_serviceMessageHandler?.onProtect(Fd.fromJson(m.data));
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class GlobalState {
|
|||||||
config: clashConfig,
|
config: clashConfig,
|
||||||
params: ConfigExtendedParams(
|
params: ConfigExtendedParams(
|
||||||
isPatch: isPatch,
|
isPatch: isPatch,
|
||||||
isCompatible: config.isCompatible,
|
isCompatible: true,
|
||||||
selectedMap: config.currentSelectedMap,
|
selectedMap: config.currentSelectedMap,
|
||||||
testUrl: config.testUrl,
|
testUrl: config.testUrl,
|
||||||
),
|
),
|
||||||
@@ -184,6 +184,22 @@ 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>({
|
Future<T?> showCommonDialog<T>({
|
||||||
required Widget child,
|
required Widget child,
|
||||||
}) async {
|
}) async {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class AndroidContainer extends StatefulWidget {
|
|||||||
|
|
||||||
class _AndroidContainerState extends State<AndroidContainer>
|
class _AndroidContainerState extends State<AndroidContainer>
|
||||||
with WidgetsBindingObserver {
|
with WidgetsBindingObserver {
|
||||||
|
|
||||||
Widget _excludeContainer(Widget child) {
|
Widget _excludeContainer(Widget child) {
|
||||||
return Selector<Config, bool>(
|
return Selector<Config, bool>(
|
||||||
selector: (_, config) => config.isExclude,
|
selector: (_, config) => config.isExclude,
|
||||||
|
|||||||
@@ -60,11 +60,10 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
|
|||||||
final currentSelectedMap = appController.config.currentSelectedMap;
|
final currentSelectedMap = appController.config.currentSelectedMap;
|
||||||
final proxyName = currentSelectedMap[groupName];
|
final proxyName = currentSelectedMap[groupName];
|
||||||
if (proxyName == null) return;
|
if (proxyName == null) return;
|
||||||
clashCore.changeProxy(
|
globalState.changeProxy(
|
||||||
ChangeProxyParams(
|
config: appController.config,
|
||||||
groupName: groupName,
|
groupName: groupName,
|
||||||
proxyName: proxyName,
|
proxyName: proxyName,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
appController.addCheckIpNumDebounce();
|
appController.addCheckIpNumDebounce();
|
||||||
super.onLoaded(proxyName);
|
super.onLoaded(proxyName);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class _PopContainerState extends State<PopContainer> {
|
|||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: false,
|
canPop: false,
|
||||||
onPopInvoked: (didPop) async {
|
onPopInvoked: (_) async {
|
||||||
final canPop = Navigator.canPop(context);
|
final canPop = Navigator.canPop(context);
|
||||||
if (canPop) {
|
if (canPop) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
class CommonScaffold extends StatefulWidget {
|
class CommonScaffold extends StatefulWidget {
|
||||||
final Widget body;
|
final Widget body;
|
||||||
|
|||||||
52
pubspec.lock
52
pubspec.lock
@@ -317,10 +317,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_windows
|
name: file_selector_windows
|
||||||
sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0
|
sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+1"
|
version: "0.9.3+2"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -377,10 +377,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: freezed_annotation
|
name: freezed_annotation
|
||||||
sha256: f54946fdb1fa7b01f780841937b1a80783a20b393485f3f6cdf336fd6f4705f2
|
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.4.4"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -401,10 +401,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: graphs
|
name: graphs
|
||||||
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
|
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.2"
|
||||||
gtk:
|
gtk:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -417,10 +417,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.2"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -457,10 +457,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: "4161e1f843d8480d2e9025ee22411778c3c9eb7e40076dcf2da23d8242b7b51c"
|
sha256: a26dc9a03fe042440c1e4be554fb0fceae2bf6d887d7467fc48c688fa4a81889
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.12+3"
|
version: "0.8.12+7"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -729,10 +729,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a
|
sha256: "30c5aa827a6ae95ce2853cdc5fe3971daaac00f6f081c419c013f7f57bff2f5e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.6"
|
version: "2.2.7"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -761,10 +761,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.3.0"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -904,10 +904,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_platform_interface
|
name: shared_preferences_platform_interface
|
||||||
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
|
sha256: "034650b71e73629ca08a0bd789fd1d83cc63c2d1e405946f7cef7bc37432f93a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.4.0"
|
||||||
shared_preferences_web:
|
shared_preferences_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1061,18 +1061,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: ceb2625f0c24ade6ef6778d1de0b2e44f2db71fded235eb52295247feba8c5cf
|
sha256: "95d8027db36a0e52caf55680f91e33ea6aa12a3ce608c90b06f4e429a21067ac"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.3"
|
version: "6.3.5"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
|
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.3.1"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1109,10 +1109,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_windows
|
name: url_launcher_windows
|
||||||
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.2"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1149,18 +1149,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket
|
name: web_socket
|
||||||
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
|
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.5"
|
version: "0.1.6"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276
|
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.1"
|
||||||
webdav_client:
|
webdav_client:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: fl_clash
|
name: fl_clash
|
||||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.8.47+202407222
|
version: 0.8.48+202407251
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.1.0 <4.0.0'
|
sdk: '>=3.1.0 <4.0.0'
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,34 @@
|
|||||||
// ignore_for_file: avoid_print
|
// ignore_for_file: avoid_print
|
||||||
import 'package:fl_clash/common/common.dart';
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
|
// 定义服务器将要监听的地址和端口
|
||||||
|
final host = InternetAddress.anyIPv4; // 监听所有网络接口
|
||||||
|
const port = 8080; // 使用 8080 端口
|
||||||
|
|
||||||
print("https://pqjc.site:10000/test.ymal".isUrl);
|
try {
|
||||||
print("abcd".isUrl);
|
// 创建服务器
|
||||||
print("http://10.31.1.221:8848/cfa.yaml".isUrl);
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user