|
@@ -1,36 +1,191 @@
|
|
|
+import 'dart:io';
|
|
|
+
|
|
|
import 'package:get/get.dart';
|
|
|
+import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
|
|
import '../../../common/LogHelper.dart';
|
|
|
import '../../../data/model/NodeMode.dart';
|
|
|
+import '../../../global_controller/GlobalController.dart';
|
|
|
import '../../../network/api_service.dart';
|
|
|
+enum NodeDisplayStrategy {
|
|
|
+ All,
|
|
|
+ LeastUsers,
|
|
|
+ SelectedFirst,
|
|
|
+}
|
|
|
|
|
|
class NodeController extends GetxController {
|
|
|
//TODO: Implement NodeController
|
|
|
|
|
|
final count = 0.obs;
|
|
|
var isLoading = false.obs;
|
|
|
- var nodeModes = <nodeMode>[].obs;
|
|
|
+ var nodesToShow = <nodeMode>[].obs;
|
|
|
+ var nodeModes = <nodeMode>[];
|
|
|
var selectedIndex = (-1).obs;
|
|
|
var errorMsg = ''.obs;
|
|
|
+ final selectedNode = Rx<nodeMode?>(null);
|
|
|
+ var pingResults = <int, String>{}.obs;
|
|
|
+ //final selectedNodeId = (-1).obs;
|
|
|
+ final isLoadingMap = <int, bool>{}.obs;
|
|
|
+ var displayStrategy = Rx<NodeDisplayStrategy>(NodeDisplayStrategy.All);
|
|
|
+ late final GlobalController globalController ;
|
|
|
+ // Future<void> fetchNodes() async {
|
|
|
+ // try {
|
|
|
+ // isLoading.value = true;
|
|
|
+ // nodeModes.clear();
|
|
|
+ // nodeModes = await ApiService().getNode("/api/client/v4/nodes?vless=1");
|
|
|
+ // nodesToShow.value = nodeModes;
|
|
|
+ // LogHelper().d(nodeModes.toList());
|
|
|
+ // loadSelectedNode();
|
|
|
+ // } catch (e) {
|
|
|
+ // errorMsg.value = e.toString();
|
|
|
+ // } finally {
|
|
|
+ // isLoading.value = false;
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+
|
|
|
+ Future<void> tcpPing(nodeMode node) async {
|
|
|
+ int? nodeId = node.id;
|
|
|
+
|
|
|
+ if (nodeId != null) {
|
|
|
+ isLoadingMap[nodeId] = true; // 开始ping时设置为true
|
|
|
+ isLoadingMap.refresh(); // 通知观察者
|
|
|
+
|
|
|
+ final stopwatch = Stopwatch()..start();
|
|
|
+ Socket? socket;
|
|
|
+ try {
|
|
|
+ socket = await Socket.connect(node.host!, node.port!, timeout: const Duration(seconds: 3));
|
|
|
+ final elapsed = stopwatch.elapsedMilliseconds;
|
|
|
+ pingResults[nodeId] = '${elapsed}ms'; // 使用普通的映射赋值
|
|
|
+ pingResults.refresh(); // 通知观察者
|
|
|
+ socket.destroy();
|
|
|
+ } catch (e) {
|
|
|
+ pingResults[nodeId] = 'Error'; // 使用普通的映射赋值
|
|
|
+ pingResults.refresh(); // 通知观察者
|
|
|
+ }
|
|
|
+
|
|
|
+ isLoadingMap[nodeId] = false; // ping结束后设置为false
|
|
|
+ isLoadingMap.refresh(); // 通知观察者
|
|
|
+ }
|
|
|
+ }
|
|
|
+ void pingAllNodes() async {
|
|
|
+ for (var node in nodeModes) {
|
|
|
+ tcpPing(node); // 这里用了 await 使其串行 ping,若希望并行可移除 await
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- Future<void> fetchNodes() async {
|
|
|
- try {
|
|
|
- isLoading.value = true;
|
|
|
- nodeModes.clear();
|
|
|
- nodeModes.value = await ApiService().getNode("/api/client/v4/nodes?vless=1");
|
|
|
- LogHelper().d(nodeModes.toList());
|
|
|
- } catch (e) {
|
|
|
- errorMsg.value = e.toString();
|
|
|
- } finally {
|
|
|
- isLoading.value = false;
|
|
|
+ Future<void> pingSingleNode(nodeMode node) async {
|
|
|
+
|
|
|
+ await tcpPing(node);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ void selectNode(nodeMode node) {
|
|
|
+ selectedNode.value = node;
|
|
|
+ _storeSelectedNode(node);
|
|
|
+ //selectedIndex.value = nodeModes.indexWhere((item) => item.id == node.id);
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<void> _storeSelectedNode(nodeMode node) async {
|
|
|
+ final prefs = await SharedPreferences.getInstance();
|
|
|
+ // 为简化起见,我们只存储node的ID,但您可以根据需要存储更多信息
|
|
|
+ prefs.setInt('selectedNodeId', node.id!);
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<void> loadSelectedNode() async {
|
|
|
+ final prefs = await SharedPreferences.getInstance();
|
|
|
+ final selectedNodeId = prefs.getInt('selectedNodeId');
|
|
|
+ if (selectedNodeId != null) {
|
|
|
+ //selectedIndex.value = nodeModes.indexWhere((item) => item.id == selectedNodeId);
|
|
|
+ selectedNode.value = nodeModes.firstWhere((node) => node.id == selectedNodeId);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ //自动选择人数最小的线路
|
|
|
+ nodeMode selectBestNode(List<nodeMode> nodes) {
|
|
|
+ return nodes.where((node) => node.countryCode == 'HK') //筛选地区是HK的节点
|
|
|
+ .reduce((value, element) =>
|
|
|
+ value.onlineUsers! < element.onlineUsers! ? value : element); //选择人数最小的
|
|
|
+ }
|
|
|
+
|
|
|
+ void selectMinOnlineUsersNodeInRegion(String region) {
|
|
|
+ List<nodeMode> nodesInRegion = nodeModes.where((node) => node.countryCode == region).toList();
|
|
|
+ if (nodesInRegion.isNotEmpty) {
|
|
|
+ nodeMode? minNode = nodesInRegion.reduce((curr, next) => curr.onlineUsers! <= next.onlineUsers! ? curr : next);
|
|
|
+ if (minNode != null) {
|
|
|
+ selectNode(minNode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void filterNodesWithLeastUsersInHK() {
|
|
|
+ displayStrategy.value = NodeDisplayStrategy.LeastUsers;
|
|
|
+ updateNodesToDisplay();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ void filterNodesByRegion(String region) {
|
|
|
+ nodesToShow.value = nodeModes.where((node) => node.countryCode == region).toList();
|
|
|
+ updateNodesToDisplay();
|
|
|
+ }
|
|
|
+
|
|
|
+ void showAllNodes() {
|
|
|
+ displayStrategy.value = NodeDisplayStrategy.All;
|
|
|
+ updateNodesToDisplay();
|
|
|
+ // nodesToShow.value = List.from(nodeModes);
|
|
|
+ }
|
|
|
+ void showSelectedFirst() {
|
|
|
+ displayStrategy.value = NodeDisplayStrategy.SelectedFirst;
|
|
|
+ updateNodesToDisplay();
|
|
|
+ // 如果需要,可以添加其他逻辑。
|
|
|
+ }
|
|
|
+
|
|
|
+ void updateNodesToDisplay() {
|
|
|
+ List<nodeMode> nodes = List.from(nodeModes);
|
|
|
+
|
|
|
+ switch (displayStrategy.value) {
|
|
|
+ case NodeDisplayStrategy.LeastUsers:
|
|
|
+ List<nodeMode> filteredNodes = nodeModes.where((node) => node.countryCode == "hk" && node.onlineUsers != null).toList();
|
|
|
+
|
|
|
+ if (filteredNodes.isNotEmpty) {
|
|
|
+ int? minUsers = filteredNodes.map((node) => node.onlineUsers!).reduce((curr, next) => curr < next ? curr : next);
|
|
|
+ nodes = nodes.where((node) => node.onlineUsers == minUsers && node.countryCode == "hk").toList();
|
|
|
+ selectNode(nodes.first);
|
|
|
+ } else {
|
|
|
+ // Handle the case where no nodes match the criteria. Maybe display a message or use a fallback.
|
|
|
+ nodes = []; // Empty list or some fallback
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ case NodeDisplayStrategy.SelectedFirst:
|
|
|
+
|
|
|
+ break;
|
|
|
+ case NodeDisplayStrategy.All:
|
|
|
+ // // 将选中的节点移动到前面
|
|
|
+ // if (selectedNode.value != null) {
|
|
|
+ // nodes.removeWhere((node) => node.id == selectedNode.value?.id);
|
|
|
+ // nodes.insert(0, selectedNode.value!);
|
|
|
+ // }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ // 不需要额外的处理,因为nodes已经包含所有节点。
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ nodesToShow.value = nodes; // 使用 assignAll() 方法触发更新
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
@override
|
|
|
void onInit() {
|
|
|
super.onInit();
|
|
|
- fetchNodes();
|
|
|
+ //fetchNodes();
|
|
|
+
|
|
|
+ globalController = Get.put(GlobalController());
|
|
|
+ nodeModes = globalController.nodeModes;
|
|
|
+ nodesToShow.value = nodeModes;
|
|
|
}
|
|
|
|
|
|
@override
|