2024-08-25 23:30:18 +08:00
|
|
|
import 'package:fl_clash/common/common.dart';
|
|
|
|
|
import 'package:fl_clash/enum/enum.dart';
|
|
|
|
|
import 'package:fl_clash/models/models.dart';
|
2025-02-09 18:39:38 +08:00
|
|
|
import 'package:fl_clash/providers/providers.dart';
|
2024-08-25 23:30:18 +08:00
|
|
|
import 'package:fl_clash/state.dart';
|
|
|
|
|
import 'package:fl_clash/widgets/widgets.dart';
|
|
|
|
|
import 'package:flutter/material.dart';
|
2025-02-09 18:39:38 +08:00
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
2024-08-25 23:30:18 +08:00
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class LogLevelItem extends ConsumerWidget {
|
2024-08-26 20:44:30 +08:00
|
|
|
const LogLevelItem({super.key});
|
2024-08-25 23:30:18 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final logLevel =
|
|
|
|
|
ref.watch(patchClashConfigProvider.select((state) => state.logLevel));
|
|
|
|
|
return ListItem<LogLevel>.options(
|
|
|
|
|
leading: const Icon(Icons.info_outline),
|
|
|
|
|
title: Text(appLocalizations.logLevel),
|
|
|
|
|
subtitle: Text(logLevel.name),
|
|
|
|
|
delegate: OptionsDelegate<LogLevel>(
|
|
|
|
|
title: appLocalizations.logLevel,
|
|
|
|
|
options: LogLevel.values,
|
|
|
|
|
onChanged: (LogLevel? value) {
|
|
|
|
|
if (value == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
logLevel: value,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
textBuilder: (logLevel) => logLevel.name,
|
|
|
|
|
value: logLevel,
|
|
|
|
|
),
|
2024-08-25 23:30:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class UaItem extends ConsumerWidget {
|
2024-08-26 20:44:30 +08:00
|
|
|
const UaItem({super.key});
|
2024-08-25 23:30:18 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final globalUa =
|
|
|
|
|
ref.watch(patchClashConfigProvider.select((state) => state.globalUa));
|
|
|
|
|
return ListItem<String?>.options(
|
|
|
|
|
leading: const Icon(Icons.computer_outlined),
|
|
|
|
|
title: const Text("UA"),
|
|
|
|
|
subtitle: Text(globalUa ?? appLocalizations.defaultText),
|
|
|
|
|
delegate: OptionsDelegate<String?>(
|
|
|
|
|
title: "UA",
|
|
|
|
|
options: [
|
|
|
|
|
null,
|
|
|
|
|
"clash-verge/v1.6.6",
|
|
|
|
|
"ClashforWindows/0.19.23",
|
|
|
|
|
],
|
|
|
|
|
value: globalUa,
|
|
|
|
|
onChanged: (value) {
|
|
|
|
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
globalUa: value,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
textBuilder: (ua) => ua ?? appLocalizations.defaultText,
|
|
|
|
|
),
|
2024-08-25 23:30:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class KeepAliveIntervalItem extends ConsumerWidget {
|
2024-08-26 20:44:30 +08:00
|
|
|
const KeepAliveIntervalItem({super.key});
|
2024-08-25 23:30:18 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final keepAliveInterval = ref.watch(
|
|
|
|
|
patchClashConfigProvider.select((state) => state.keepAliveInterval));
|
|
|
|
|
return ListItem.input(
|
|
|
|
|
leading: const Icon(Icons.timer_outlined),
|
|
|
|
|
title: Text(appLocalizations.keepAliveIntervalDesc),
|
|
|
|
|
subtitle: Text("$keepAliveInterval ${appLocalizations.seconds}"),
|
|
|
|
|
delegate: InputDelegate(
|
|
|
|
|
title: appLocalizations.keepAliveIntervalDesc,
|
|
|
|
|
suffixText: appLocalizations.seconds,
|
|
|
|
|
resetValue: "$defaultKeepAliveInterval",
|
|
|
|
|
value: "$keepAliveInterval",
|
|
|
|
|
onChanged: (String? value) {
|
|
|
|
|
if (value == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
globalState.safeRun(
|
|
|
|
|
() {
|
|
|
|
|
final intValue = int.parse(value);
|
|
|
|
|
if (intValue <= 0) {
|
|
|
|
|
throw "Invalid keepAliveInterval";
|
|
|
|
|
}
|
|
|
|
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
keepAliveInterval: intValue,
|
2024-08-26 20:44:30 +08:00
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
2025-02-09 18:39:38 +08:00
|
|
|
silence: false,
|
|
|
|
|
title: appLocalizations.keepAliveIntervalDesc,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-08-25 23:30:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class TestUrlItem extends ConsumerWidget {
|
2024-08-26 20:44:30 +08:00
|
|
|
const TestUrlItem({super.key});
|
2024-08-25 23:30:18 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final testUrl =
|
|
|
|
|
ref.watch(appSettingProvider.select((state) => state.testUrl));
|
|
|
|
|
return ListItem.input(
|
|
|
|
|
leading: const Icon(Icons.timeline),
|
|
|
|
|
title: Text(appLocalizations.testUrl),
|
|
|
|
|
subtitle: Text(testUrl),
|
|
|
|
|
delegate: InputDelegate(
|
|
|
|
|
resetValue: defaultTestUrl,
|
|
|
|
|
title: appLocalizations.testUrl,
|
|
|
|
|
value: testUrl,
|
|
|
|
|
onChanged: (String? value) {
|
|
|
|
|
if (value == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
globalState.safeRun(
|
|
|
|
|
() {
|
|
|
|
|
if (!value.isUrl) {
|
|
|
|
|
throw "Invalid url";
|
2024-08-26 20:44:30 +08:00
|
|
|
}
|
2025-02-09 18:39:38 +08:00
|
|
|
ref.read(appSettingProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
testUrl: value,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
silence: false,
|
|
|
|
|
title: appLocalizations.testUrl,
|
|
|
|
|
);
|
|
|
|
|
}),
|
2024-08-25 23:30:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class MixedPortItem extends ConsumerWidget {
|
2024-08-26 20:44:30 +08:00
|
|
|
const MixedPortItem({super.key});
|
2024-08-25 23:30:18 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final mixedPort =
|
|
|
|
|
ref.watch(patchClashConfigProvider.select((state) => state.mixedPort));
|
|
|
|
|
return ListItem.input(
|
|
|
|
|
leading: const Icon(Icons.adjust_outlined),
|
|
|
|
|
title: Text(appLocalizations.proxyPort),
|
|
|
|
|
subtitle: Text("$mixedPort"),
|
|
|
|
|
delegate: InputDelegate(
|
|
|
|
|
title: appLocalizations.proxyPort,
|
|
|
|
|
value: "$mixedPort",
|
|
|
|
|
onChanged: (String? value) {
|
|
|
|
|
if (value == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
globalState.safeRun(
|
|
|
|
|
() {
|
|
|
|
|
final mixedPort = int.parse(value);
|
|
|
|
|
if (mixedPort < 1024 || mixedPort > 49151) {
|
|
|
|
|
throw "Invalid port";
|
|
|
|
|
}
|
|
|
|
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
mixedPort: mixedPort,
|
2024-08-26 20:44:30 +08:00
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
2025-02-09 18:39:38 +08:00
|
|
|
silence: false,
|
|
|
|
|
title: appLocalizations.proxyPort,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
resetValue: "$defaultMixedPort",
|
|
|
|
|
),
|
2024-08-25 23:30:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class HostsItem extends ConsumerWidget {
|
2024-08-25 23:30:18 +08:00
|
|
|
const HostsItem({super.key});
|
|
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final hosts =
|
|
|
|
|
ref.watch(patchClashConfigProvider.select((state) => state.hosts));
|
2024-08-25 23:30:18 +08:00
|
|
|
return ListItem.open(
|
|
|
|
|
leading: const Icon(Icons.view_list_outlined),
|
|
|
|
|
title: const Text("Hosts"),
|
2024-08-26 20:44:30 +08:00
|
|
|
subtitle: Text(appLocalizations.hostsDesc),
|
2024-08-25 23:30:18 +08:00
|
|
|
delegate: OpenDelegate(
|
2024-08-26 20:44:30 +08:00
|
|
|
isBlur: false,
|
2024-08-25 23:30:18 +08:00
|
|
|
title: "Hosts",
|
2025-02-09 18:39:38 +08:00
|
|
|
widget: ListPage(
|
|
|
|
|
title: "Hosts",
|
|
|
|
|
items: hosts.entries,
|
|
|
|
|
titleBuilder: (item) => Text(item.key),
|
|
|
|
|
subtitleBuilder: (item) => Text(item.value),
|
|
|
|
|
onChange: (items) {
|
|
|
|
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
hosts: Map.fromEntries(items),
|
|
|
|
|
),
|
|
|
|
|
);
|
2024-08-26 20:44:30 +08:00
|
|
|
},
|
|
|
|
|
),
|
2024-08-25 23:30:18 +08:00
|
|
|
extendPageWidth: 360,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class Ipv6Item extends ConsumerWidget {
|
2024-08-26 20:44:30 +08:00
|
|
|
const Ipv6Item({super.key});
|
2024-08-25 23:30:18 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final ipv6 =
|
|
|
|
|
ref.watch(patchClashConfigProvider.select((state) => state.ipv6));
|
|
|
|
|
return ListItem.switchItem(
|
|
|
|
|
leading: const Icon(Icons.water_outlined),
|
|
|
|
|
title: const Text("IPv6"),
|
|
|
|
|
subtitle: Text(appLocalizations.ipv6Desc),
|
|
|
|
|
delegate: SwitchDelegate(
|
|
|
|
|
value: ipv6,
|
|
|
|
|
onChanged: (bool value) async {
|
|
|
|
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
ipv6: value,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-08-25 23:30:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class AllowLanItem extends ConsumerWidget {
|
2024-08-26 20:44:30 +08:00
|
|
|
const AllowLanItem({super.key});
|
2024-08-25 23:30:18 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final allowLan =
|
|
|
|
|
ref.watch(patchClashConfigProvider.select((state) => state.allowLan));
|
|
|
|
|
return ListItem.switchItem(
|
|
|
|
|
leading: const Icon(Icons.device_hub),
|
|
|
|
|
title: Text(appLocalizations.allowLan),
|
|
|
|
|
subtitle: Text(appLocalizations.allowLanDesc),
|
|
|
|
|
delegate: SwitchDelegate(
|
|
|
|
|
value: allowLan,
|
|
|
|
|
onChanged: (bool value) async {
|
|
|
|
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
allowLan: value,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-08-25 23:30:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class UnifiedDelayItem extends ConsumerWidget {
|
2024-08-26 20:44:30 +08:00
|
|
|
const UnifiedDelayItem({super.key});
|
2024-08-25 23:30:18 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final unifiedDelay = ref
|
|
|
|
|
.watch(patchClashConfigProvider.select((state) => state.unifiedDelay));
|
|
|
|
|
|
|
|
|
|
return ListItem.switchItem(
|
|
|
|
|
leading: const Icon(Icons.compress_outlined),
|
|
|
|
|
title: Text(appLocalizations.unifiedDelay),
|
|
|
|
|
subtitle: Text(appLocalizations.unifiedDelayDesc),
|
|
|
|
|
delegate: SwitchDelegate(
|
|
|
|
|
value: unifiedDelay,
|
|
|
|
|
onChanged: (bool value) async {
|
|
|
|
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
unifiedDelay: value,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-08-25 23:30:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class FindProcessItem extends ConsumerWidget {
|
2024-08-26 20:44:30 +08:00
|
|
|
const FindProcessItem({super.key});
|
2024-08-25 23:30:18 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final findProcess = ref.watch(patchClashConfigProvider
|
|
|
|
|
.select((state) => state.findProcessMode == FindProcessMode.always));
|
|
|
|
|
|
|
|
|
|
return ListItem.switchItem(
|
|
|
|
|
leading: const Icon(Icons.polymer_outlined),
|
|
|
|
|
title: Text(appLocalizations.findProcessMode),
|
|
|
|
|
subtitle: Text(appLocalizations.findProcessModeDesc),
|
|
|
|
|
delegate: SwitchDelegate(
|
|
|
|
|
value: findProcess,
|
|
|
|
|
onChanged: (bool value) async {
|
|
|
|
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
findProcessMode:
|
|
|
|
|
value ? FindProcessMode.always : FindProcessMode.off,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-08-25 23:30:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class TcpConcurrentItem extends ConsumerWidget {
|
2024-08-26 20:44:30 +08:00
|
|
|
const TcpConcurrentItem({super.key});
|
2024-08-25 23:30:18 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final tcpConcurrent = ref
|
|
|
|
|
.watch(patchClashConfigProvider.select((state) => state.tcpConcurrent));
|
|
|
|
|
return ListItem.switchItem(
|
|
|
|
|
leading: const Icon(Icons.double_arrow_outlined),
|
|
|
|
|
title: Text(appLocalizations.tcpConcurrent),
|
|
|
|
|
subtitle: Text(appLocalizations.tcpConcurrentDesc),
|
|
|
|
|
delegate: SwitchDelegate(
|
|
|
|
|
value: tcpConcurrent,
|
|
|
|
|
onChanged: (value) async {
|
|
|
|
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
tcpConcurrent: value,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-08-25 23:30:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class GeodataLoaderItem extends ConsumerWidget {
|
2024-08-26 20:44:30 +08:00
|
|
|
const GeodataLoaderItem({super.key});
|
2024-08-25 23:30:18 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final isMemconservative = ref.watch(patchClashConfigProvider.select(
|
|
|
|
|
(state) => state.geodataLoader == GeodataLoader.memconservative));
|
|
|
|
|
return ListItem.switchItem(
|
|
|
|
|
leading: const Icon(Icons.memory),
|
|
|
|
|
title: Text(appLocalizations.geodataLoader),
|
|
|
|
|
subtitle: Text(appLocalizations.geodataLoaderDesc),
|
|
|
|
|
delegate: SwitchDelegate(
|
|
|
|
|
value: isMemconservative,
|
|
|
|
|
onChanged: (bool value) async {
|
|
|
|
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
geodataLoader: value
|
|
|
|
|
? GeodataLoader.memconservative
|
|
|
|
|
: GeodataLoader.standard,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-08-25 23:30:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class ExternalControllerItem extends ConsumerWidget {
|
2024-08-26 20:44:30 +08:00
|
|
|
const ExternalControllerItem({super.key});
|
2024-08-25 23:30:18 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-02-09 18:39:38 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
final hasExternalController = ref.watch(patchClashConfigProvider.select(
|
|
|
|
|
(state) => state.externalController == ExternalControllerStatus.open));
|
|
|
|
|
return ListItem.switchItem(
|
|
|
|
|
leading: const Icon(Icons.api_outlined),
|
|
|
|
|
title: Text(appLocalizations.externalController),
|
|
|
|
|
subtitle: Text(appLocalizations.externalControllerDesc),
|
|
|
|
|
delegate: SwitchDelegate(
|
|
|
|
|
value: hasExternalController,
|
|
|
|
|
onChanged: (bool value) async {
|
|
|
|
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
externalController: value
|
|
|
|
|
? ExternalControllerStatus.open
|
|
|
|
|
: ExternalControllerStatus.close,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-08-25 23:30:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
final generalItems = <Widget>[
|
2024-08-26 20:44:30 +08:00
|
|
|
LogLevelItem(),
|
|
|
|
|
UaItem(),
|
2025-02-09 18:39:38 +08:00
|
|
|
if (system.isDesktop) KeepAliveIntervalItem(),
|
2024-08-26 20:44:30 +08:00
|
|
|
TestUrlItem(),
|
|
|
|
|
MixedPortItem(),
|
2024-08-25 23:30:18 +08:00
|
|
|
HostsItem(),
|
2024-08-26 20:44:30 +08:00
|
|
|
Ipv6Item(),
|
|
|
|
|
AllowLanItem(),
|
|
|
|
|
UnifiedDelayItem(),
|
|
|
|
|
FindProcessItem(),
|
|
|
|
|
TcpConcurrentItem(),
|
|
|
|
|
GeodataLoaderItem(),
|
|
|
|
|
ExternalControllerItem(),
|
|
|
|
|
]
|
|
|
|
|
.separated(
|
|
|
|
|
const Divider(
|
|
|
|
|
height: 0,
|
2024-08-25 23:30:18 +08:00
|
|
|
),
|
2024-08-26 20:44:30 +08:00
|
|
|
)
|
|
|
|
|
.toList();
|