alroyso 1 рік тому
батько
коміт
17c1b744e6

+ 4 - 0
android/app/src/main/AndroidManifest.xml

@@ -1,4 +1,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <application
         android:label="naiyouwl"
         android:name="${applicationName}"

BIN
images/main/userstatus.png


BIN
images/node/nodebg.png


BIN
images/node/nodetablebg.png


+ 123 - 15
ios/Runner.xcodeproj/project.pbxproj

@@ -7,13 +7,15 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		002DAE5A8CB64FB8216ABEE6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 983A1C7269FB9DFA558976D1 /* Pods_Runner.framework */; };
 		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+		331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
 		74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
-		331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
+		D23CCCA3334D1D9762B2C9EC /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA1F14F2966409F835DF7109 /* Pods_RunnerTests.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -40,12 +42,20 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+		0E55539F675EF9FF4895197E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+		15BD07E3DEA4CA23BEF57D2E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
+		26C85DCADC969E1FA8C630BC /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
+		331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
+		331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+		5C5514BDB2EE7AF7D247F752 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
 		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
 		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		77367CF0EA3D3772ED4116ED /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
+		863E24671E0B1A2CE5F60168 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -53,21 +63,47 @@
 		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
-		331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		983A1C7269FB9DFA558976D1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		DA1F14F2966409F835DF7109 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
+		674D4167246B15340994A4DC /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				D23CCCA3334D1D9762B2C9EC /* Pods_RunnerTests.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		97C146EB1CF9000F007C117D /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				002DAE5A8CB64FB8216ABEE6 /* Pods_Runner.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		331C8082294A63A400263BE5 /* RunnerTests */ = {
+			isa = PBXGroup;
+			children = (
+				331C807B294A618700263BE5 /* RunnerTests.swift */,
+			);
+			path = RunnerTests;
+			sourceTree = "<group>";
+		};
+		55DDB4A31476AD223DA43FB0 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				983A1C7269FB9DFA558976D1 /* Pods_Runner.framework */,
+				DA1F14F2966409F835DF7109 /* Pods_RunnerTests.framework */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
 		9740EEB11CF90186004384FC /* Flutter */ = {
 			isa = PBXGroup;
 			children = (
@@ -79,14 +115,6 @@
 			name = Flutter;
 			sourceTree = "<group>";
 		};
-		331C8082294A63A400263BE5 /* RunnerTests */ = {
-			isa = PBXGroup;
-			children = (
-				331C807B294A618700263BE5 /* RunnerTests.swift */,
-			);
-			path = RunnerTests;
-			sourceTree = "<group>";
-		};
 		97C146E51CF9000F007C117D = {
 			isa = PBXGroup;
 			children = (
@@ -94,6 +122,8 @@
 				97C146F01CF9000F007C117D /* Runner */,
 				97C146EF1CF9000F007C117D /* Products */,
 				331C8082294A63A400263BE5 /* RunnerTests */,
+				E0842D8A8EDA5D3641E07EE2 /* Pods */,
+				55DDB4A31476AD223DA43FB0 /* Frameworks */,
 			);
 			sourceTree = "<group>";
 		};
@@ -121,6 +151,20 @@
 			path = Runner;
 			sourceTree = "<group>";
 		};
+		E0842D8A8EDA5D3641E07EE2 /* Pods */ = {
+			isa = PBXGroup;
+			children = (
+				77367CF0EA3D3772ED4116ED /* Pods-Runner.debug.xcconfig */,
+				863E24671E0B1A2CE5F60168 /* Pods-Runner.release.xcconfig */,
+				15BD07E3DEA4CA23BEF57D2E /* Pods-Runner.profile.xcconfig */,
+				26C85DCADC969E1FA8C630BC /* Pods-RunnerTests.debug.xcconfig */,
+				0E55539F675EF9FF4895197E /* Pods-RunnerTests.release.xcconfig */,
+				5C5514BDB2EE7AF7D247F752 /* Pods-RunnerTests.profile.xcconfig */,
+			);
+			name = Pods;
+			path = Pods;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -128,9 +172,10 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
 			buildPhases = (
+				56783D2414A4B5DFE3063745 /* [CP] Check Pods Manifest.lock */,
 				331C807D294A63A400263BE5 /* Sources */,
-				331C807E294A63A400263BE5 /* Frameworks */,
 				331C807F294A63A400263BE5 /* Resources */,
+				674D4167246B15340994A4DC /* Frameworks */,
 			);
 			buildRules = (
 			);
@@ -146,12 +191,14 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
 			buildPhases = (
+				1B520A36D1EAF00E636C6B93 /* [CP] Check Pods Manifest.lock */,
 				9740EEB61CF901F6004384FC /* Run Script */,
 				97C146EA1CF9000F007C117D /* Sources */,
 				97C146EB1CF9000F007C117D /* Frameworks */,
 				97C146EC1CF9000F007C117D /* Resources */,
 				9705A1C41CF9048500538489 /* Embed Frameworks */,
 				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+				1AE51FAFEE6057A91A9D4826 /* [CP] Embed Pods Frameworks */,
 			);
 			buildRules = (
 			);
@@ -223,6 +270,45 @@
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
+		1AE51FAFEE6057A91A9D4826 /* [CP] Embed Pods Frameworks */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+			);
+			name = "[CP] Embed Pods Frameworks";
+			outputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
+		1B520A36D1EAF00E636C6B93 /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
 		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
 			isa = PBXShellScriptBuildPhase;
 			alwaysOutOfDate = 1;
@@ -239,6 +325,28 @@
 			shellPath = /bin/sh;
 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
 		};
+		56783D2414A4B5DFE3063745 /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
 		9740EEB61CF901F6004384FC /* Run Script */ = {
 			isa = PBXShellScriptBuildPhase;
 			alwaysOutOfDate = 1;
@@ -377,7 +485,7 @@
 		};
 		331C8088294A63A400263BE5 /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */;
+			baseConfigurationReference = 26C85DCADC969E1FA8C630BC /* Pods-RunnerTests.debug.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CODE_SIGN_STYLE = Automatic;
@@ -395,7 +503,7 @@
 		};
 		331C8089294A63A400263BE5 /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */;
+			baseConfigurationReference = 0E55539F675EF9FF4895197E /* Pods-RunnerTests.release.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CODE_SIGN_STYLE = Automatic;
@@ -411,7 +519,7 @@
 		};
 		331C808A294A63A400263BE5 /* Profile */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */;
+			baseConfigurationReference = 5C5514BDB2EE7AF7D247F752 /* Pods-RunnerTests.profile.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CODE_SIGN_STYLE = Automatic;

+ 3 - 0
ios/Runner.xcworkspace/contents.xcworkspacedata

@@ -4,4 +4,7 @@
    <FileRef
       location = "group:Runner.xcodeproj">
    </FileRef>
+   <FileRef
+      location = "group:Pods/Pods.xcodeproj">
+   </FileRef>
 </Workspace>

+ 1 - 1
lib/app/component/connection_widget.dart

@@ -77,7 +77,7 @@ class _ConnectionWidgetState extends State<ConnectionWidget> {
       alignment: Alignment.center,
       children: <Widget>[
         // Gif作为背景
-        Image.asset(currentImage, fit: BoxFit.cover,width: 300,height: 300,),
+        Image.asset(currentImage, fit: BoxFit.cover,width: 250 ,height: 250,),
 
         // 圆形透明按钮
         ClipOval(

+ 2 - 0
lib/app/component/sys_app_bar.dart

@@ -1,3 +1,4 @@
+
 import 'dart:io';
 import 'package:flutter/material.dart';
 import 'package:window_manager/window_manager.dart';
@@ -17,6 +18,7 @@ class SysAppBar extends StatelessWidget implements PreferredSizeWidget {
     if (actions != null) {
       acs.addAll(actions!);
     }
+
     if (Platform.isWindows || Platform.isLinux) {
       acs.add(CloseButton(onPressed: () => windowManager.close()));
     }

+ 96 - 0
lib/app/data/model/NodeMode.dart

@@ -0,0 +1,96 @@
+class nodeMode {
+  int? id;
+  String? name;
+  String? host;
+  String? group;
+  String? type;
+  int? port;
+  String? uuid;
+  String? method;
+  int? v2AlterId;
+  String? v2Net;
+  String? v2Type;
+  String? v2Host;
+  String? v2Path;
+  String? v2Tls;
+  String? v2Sni;
+  int? udp;
+  int? vless;
+  String? vlessPulkey;
+  String? ip;
+  int? onlineUsers;
+  String? countryCode;
+
+  nodeMode(
+      {this.id,
+        this.name,
+        this.host,
+        this.group,
+        this.type,
+        this.port,
+        this.uuid,
+        this.method,
+        this.v2AlterId,
+        this.v2Net,
+        this.v2Type,
+        this.v2Host,
+        this.v2Path,
+        this.v2Tls,
+        this.v2Sni,
+        this.udp,
+        this.vless,
+        this.vlessPulkey,
+        this.ip,
+        this.onlineUsers,
+        this.countryCode});
+
+  nodeMode.fromJson(Map<String, dynamic> json) {
+    id = json['id'];
+    name = json['name'];
+    host = json['host'];
+    group = json['group'];
+    type = json['type'];
+    port = json['port'];
+    uuid = json['uuid'];
+    method = json['method'];
+    v2AlterId = json['v2_alter_id'];
+    v2Net = json['v2_net'];
+    v2Type = json['v2_type'];
+    v2Host = json['v2_host'];
+    v2Path = json['v2_path'];
+    v2Tls = json['v2_tls'];
+    v2Sni = json['v2_sni'];
+    udp = json['udp'];
+    vless = json['vless'];
+    vlessPulkey = json['vless_pulkey'];
+    ip = json['ip'];
+    onlineUsers = json['online_users'];
+    countryCode = json['country_code'];
+  }
+
+  Map<String, dynamic> toJson() {
+    final Map<String, dynamic> data = new Map<String, dynamic>();
+    data['id'] = this.id;
+    data['name'] = this.name;
+    data['host'] = this.host;
+    data['group'] = this.group;
+    data['type'] = this.type;
+    data['port'] = this.port;
+    data['uuid'] = this.uuid;
+    data['method'] = this.method;
+    data['v2_alter_id'] = this.v2AlterId;
+    data['v2_net'] = this.v2Net;
+    data['v2_type'] = this.v2Type;
+    data['v2_host'] = this.v2Host;
+    data['v2_path'] = this.v2Path;
+    data['v2_tls'] = this.v2Tls;
+    data['v2_sni'] = this.v2Sni;
+    data['udp'] = this.udp;
+    data['vless'] = this.vless;
+    data['vless_pulkey'] = this.vlessPulkey;
+    data['ip'] = this.ip;
+    data['online_users'] = this.onlineUsers;
+    data['country_code'] = this.countryCode;
+    return data;
+  }
+}

+ 51 - 1
lib/app/modules/home/controllers/home_controller.dart

@@ -1,9 +1,12 @@
-import 'package:flutter/cupertino.dart';
+
 import 'package:get/get.dart';
 import '../../../common/LogHelper.dart';
 import '../../../common/SharedPreferencesUtil.dart';
+import '../../../data/model/LocalUser.dart';
 import '../../../data/model/SysConfig.dart';
+import '../../../data/model/UserMode.dart';
 import '../../../network/api_service.dart';
+import '../../../routes/app_pages.dart';
 
 enum ImageType {
   CUSTOMER,
@@ -17,7 +20,10 @@ class HomeController extends GetxController {
 
   var isLoading = false.obs;
   var sysConfig = SysConfig().obs;
+  var localUsers = LocalUser().obs;
+  var userMode = User().obs;
   var errorMsg = ''.obs;
+  var selectNode = '选择节点'.obs;
 
   final Map<ImageType, String> imageMap = {
     ImageType.CUSTOMER: "images/main/customer.png",
@@ -27,6 +33,15 @@ class HomeController extends GetxController {
   };
 
   void onImageTap(ImageType type) {
+    if(type == ImageType.CUSTOMER){
+
+    } else if (type == ImageType.PROMOTION){
+
+    } else if (type == ImageType.TUTORIAL){
+
+    } else if (type == ImageType.RENEWAL){
+
+    }
     LogHelper().d("${imageMap[type]} tapped as ${type.toString().split('.').last}");
   }
 
@@ -42,12 +57,40 @@ class HomeController extends GetxController {
     } finally {
     }
   }
+  
+  Future<void> fetchUserinfo() async {
+    try {
+      isLoading.value = true;
+      userMode.value = await ApiService().userinfo("/api/client/v4/userinfo");
+    } catch (e) {
+      errorMsg.value = e.toString();
+    } finally {
+      isLoading.value = false;
+    }
+  }
+  //await SharedPreferencesUtil().setObject("localUser", userModes.toJson());
+
+  Future<void> fetchLocalUser() async {
+    try {
+      Map<String, dynamic>? userdata  = await SharedPreferencesUtil().getObject("localUser");
+      if(userdata  != null){
+        localUsers.value = LocalUser.fromJson(userdata);
+        LogHelper().d(sysConfig.value.toJson());
+      }
+    } catch (e) {
+      errorMsg.value = e.toString();
+    } finally {
+    }
+  }
+
 
   final count = 0.obs;
   @override
   void onInit() {
     super.onInit();
     fetchSysConfig();
+    fetchLocalUser();
+    fetchUserinfo();
   }
 
   @override
@@ -59,5 +102,12 @@ class HomeController extends GetxController {
   void onClose() {
     super.onClose();
   }
+  bool   GetEnable()  => userMode.value.enable == 1;
+  String GetUserName() => localUsers.value.email.toString();
+  String GetExpiredAt() => "到期时间:${userMode.value.expiredAt}";
+  String GetTraffic() => "用户流量:${userMode.value.unusedTraffic}";
+  String GetNode() => selectNode.value;
+
 
+  void RouteNode() =>  Get.toNamed(Routes.NODE);
 }

+ 27 - 3
lib/app/modules/home/views/home_view.dart

@@ -32,9 +32,12 @@ class HomeView extends GetView<HomeController> {
           body: Obx(() {
             return Column(
               children: [
-                UserStatusWidget(isActive: true,isLoading: controller.isLoading.value,username:"",expiryDate: "",userTraffic:"",onRefresh: () async {
+                UserStatusWidget(isActive: controller.GetEnable(),isLoading: controller.isLoading.value,username:controller.GetUserName(),expiryDate: controller.GetExpiredAt(),userTraffic:controller.GetTraffic(),onRefresh: () async {
                   // 这里插入刷新操作代码
                   //await Future.delayed(Duration(seconds: 2));
+                  if(controller.isLoading.value != true){
+                    controller.fetchUserinfo();
+                  }
                 },),
                 Padding(
                     padding: const EdgeInsets.fromLTRB(20, 20, 0, 0),
@@ -72,7 +75,28 @@ class HomeView extends GetView<HomeController> {
                 ConnectionWidget(
                   status: ConnectionStatus.disconnected, onStatusChange: (con) {
 
-                },)
+                },),
+                Padding(
+                  padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
+                  child: SizedBox(
+                    width: 200,
+                    height: 40,
+                    child: ElevatedButton(
+                      onPressed: () {
+                           controller.RouteNode();
+                      },
+                      style: ElevatedButton.styleFrom(
+                        primary: Colors.white, // 设置背景颜色为白色
+                        onPrimary: Colors.black, // 设置文字颜色为黑色,确保在白色背景上可见
+                        shape: RoundedRectangleBorder(
+                          borderRadius: BorderRadius.circular(20), // 设置圆角
+                        ),
+                      ),
+                      child: Text(controller.GetNode()),
+                    ),
+                  ),
+                ),
+
               ],
             );
           })
@@ -122,7 +146,7 @@ class _UserStatusWidgetState extends State<UserStatusWidget> {
                     const SizedBox(width: 10,),
                     Image(
                       image: AssetImage(imagePath),
-                      width: 100,
+                      width: 80,
                       height: 30,
                     ),
                     const SizedBox(width: 10,),

+ 12 - 0
lib/app/modules/node/bindings/node_binding.dart

@@ -0,0 +1,12 @@
+import 'package:get/get.dart';
+
+import '../controllers/node_controller.dart';
+
+class NodeBinding extends Bindings {
+  @override
+  void dependencies() {
+    Get.lazyPut<NodeController>(
+      () => NodeController(),
+    );
+  }
+}

+ 47 - 0
lib/app/modules/node/controllers/node_controller.dart

@@ -0,0 +1,47 @@
+import 'package:get/get.dart';
+
+import '../../../common/LogHelper.dart';
+import '../../../data/model/NodeMode.dart';
+import '../../../network/api_service.dart';
+
+class NodeController extends GetxController {
+  //TODO: Implement NodeController
+
+  final count = 0.obs;
+  var isLoading = false.obs;
+  var nodeModes = <nodeMode>[].obs;
+  var selectedIndex = (-1).obs;
+  var errorMsg = ''.obs;
+
+  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;
+    }
+  }
+
+
+  @override
+  void onInit() {
+    super.onInit();
+    fetchNodes();
+  }
+
+  @override
+  void onReady() {
+    super.onReady();
+  }
+
+  @override
+  void onClose() {
+    super.onClose();
+  }
+
+  void increment() => count.value++;
+}

+ 98 - 0
lib/app/modules/node/views/node_view.dart

@@ -0,0 +1,98 @@
+import 'package:flutter/material.dart';
+
+import 'package:get/get.dart';
+
+import '../../../component/sys_app_bar.dart';
+import '../controllers/node_controller.dart';
+
+class NodeView extends GetView<NodeController> {
+  const NodeView({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+
+    final _refreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
+
+
+    return Container(
+      decoration: const BoxDecoration(
+        image: DecorationImage(
+          image: AssetImage("images/node/nodebg.png"),
+          fit: BoxFit.fill,
+        ),
+      ),
+      child: Scaffold(
+          backgroundColor: Colors.transparent,
+          appBar: const SysAppBar(
+            title: Text("节点列表"), toolbarHeight: kToolbarHeight + 40,),
+          body: Container(
+            width: 376,
+            height: 600,
+            decoration: const BoxDecoration(
+              image: DecorationImage(
+                image: AssetImage("images/node/nodetablebg.png"),
+                fit: BoxFit.fill,
+              ),
+            ),
+            child: Padding(
+              padding: EdgeInsets.all(30.0),
+              child: Column(
+                children: [
+                  // 错误消息展示
+                  Obx(() {
+                    if (controller.errorMsg.value.isNotEmpty) {
+                      return Text(controller.errorMsg.value,
+                          style: TextStyle(color: Colors.red));
+                    }
+                    return const SizedBox.shrink(); // 返回一个不占空间的widget
+                  }),
+
+                  // 加载指示器
+                  Obx(() {
+                    if (controller.isLoading.value) {
+                      return const CircularProgressIndicator();
+                    }
+                    return const SizedBox.shrink();
+                  }),
+
+                  Expanded(
+                    child: Obx(() {
+                      return RefreshIndicator(
+                        key: _refreshIndicatorKey,
+                        onRefresh: controller.fetchNodes,
+                        child: ListView.builder(
+                          itemCount: controller.nodeModes.length,
+                          itemBuilder: (BuildContext context, int index) {
+                            final node = controller.nodeModes[index];
+
+                            return InkWell(
+                                onTap: () {
+                                  print("Item at index $index was tapped.");
+                                  controller.selectedIndex.value = index;
+                                },
+                                child: Obx(() {
+                                  final color = controller.selectedIndex.value == index
+                                      ? Colors.blueAccent
+                                      : null;
+                                  return Container(
+                                    color: color,
+                                    child: ListTile(
+                                      title: Text(node.name.toString()),
+                                    ),
+                                  );
+                                })
+                            );
+                          },
+                        ),
+                      );
+                    }),
+                  ),
+                ],
+              ),
+            ),
+          )
+
+      ),
+    );
+  }
+}

+ 29 - 3
lib/app/network/api_service.dart

@@ -1,7 +1,9 @@
 import 'package:dio/dio.dart';
 import 'package:naiyouwl/app/data/model/LoginMode.dart';
+import 'package:naiyouwl/app/data/model/NodeMode.dart';
 
 import '../data/model/SysConfig.dart';
+import '../data/model/UserMode.dart';
 import 'dio_client.dart';
 
 class ApiService {
@@ -14,18 +16,42 @@ class ApiService {
 
 
   Future<SysConfig> fetchSysConfig(String path) async {
-    final Map<String, dynamic> data = await _requestWrapper(() => _dioClient.get(path));
+    final  data = await _requestWrapper(() => _dioClient.get(path));
     return SysConfig.fromJson(data);
 
   }
 
   Future<LoginMode> login(String path, {Map<String, dynamic>? data}) async {
 
-      final Map<String, dynamic> retData =await _requestWrapper(() => _dioClient.post(path,data: data));
+      final  retData =await _requestWrapper(() => _dioClient.post(path,data: data));
       return LoginMode.fromJson(retData);
 
   }
 
+
+  Future<User> userinfo(String path) async {
+
+    final  retData =await _requestWrapper(() => _dioClient.get(path));
+    return User.fromJson(retData);
+
+  }
+
+
+  Future<List<nodeMode>> getNode(String path) async {
+    final  retData =await _requestWrapper(() => _dioClient.get(path));
+    if (retData is List) {
+      // 遍历List并为每一项调用NodeMode.fromJson
+      return (retData as List).map((item) {
+        return nodeMode.fromJson(item);
+      }).toList();
+
+    } else {
+      throw Exception("Expected a list but received ${retData.runtimeType}");
+    }
+
+  }
+
+
   Future<Map<String, dynamic>> fetchData(String path, {Map<String, dynamic>? queryParameters}) async {
     return await _dioClient.get(path, queryParameters: queryParameters);
   }
@@ -43,7 +69,7 @@ class ApiService {
   }
 
 
-  Future<Map<String, dynamic>> _requestWrapper(Future<Map<String, dynamic>> Function() apiCall) async {
+  Future<dynamic> _requestWrapper(Future<dynamic> Function() apiCall) async {
     try {
       return await apiCall();
     } catch (e) {

+ 17 - 6
lib/app/network/dio_client.dart

@@ -184,22 +184,33 @@ class AppException implements Exception {
   }
 }
 extension DioClientExtension on DioClient {
-  Future<Map<String, dynamic>> get(String path, {Map<String, dynamic>? queryParameters}) async {
+  Future<dynamic> get(String path, {Map<String, dynamic>? queryParameters}) async {
     final response = await _dio.get(path, queryParameters: queryParameters);
-    return response.data as Map<String, dynamic>;
+    return _handleResponse(response);
   }
 
-  Future<Map<String, dynamic>> post(String path, {Map<String, dynamic>? data}) async {
+  Future<dynamic> post(String path, {Map<String, dynamic>? data}) async {
     final response = await _dio.post(path, data: data);
-    return response.data as Map<String, dynamic>;
+    return _handleResponse(response);
   }
 
-  Future<Map<String, dynamic>> put(String path, {Map<String, dynamic>? data}) async {
+  Future<dynamic> put(String path, {Map<String, dynamic>? data}) async {
     final response = await _dio.put(path, data: data);
-    return response.data as Map<String, dynamic>;
+    return _handleResponse(response);
   }
 
   Future<void> download(String urlPath, String savePath) async {
     await _dio.download(urlPath, savePath);
   }
+
+  dynamic _handleResponse(Response response) {
+    if (response.data is List) {
+      return response.data as List<dynamic>;
+    } else if (response.data is Map) {
+      return response.data as Map<String, dynamic>;
+    } else {
+      throw Exception('Unsupported data type');
+    }
+  }
+
 }

+ 7 - 0
lib/app/routes/app_pages.dart

@@ -4,6 +4,8 @@ import '../modules/home/bindings/home_binding.dart';
 import '../modules/home/views/home_view.dart';
 import '../modules/login/bindings/login_binding.dart';
 import '../modules/login/views/login_view.dart';
+import '../modules/node/bindings/node_binding.dart';
+import '../modules/node/views/node_view.dart';
 import '../modules/welcome/bindings/welcome_binding.dart';
 import '../modules/welcome/views/welcome_view.dart';
 
@@ -30,5 +32,10 @@ class AppPages {
       page: () => const LoginView(),
       binding: LoginBinding(),
     ),
+    GetPage(
+      name: _Paths.NODE,
+      page: () => const NodeView(),
+      binding: NodeBinding(),
+    ),
   ];
 }

+ 2 - 0
lib/app/routes/app_routes.dart

@@ -6,6 +6,7 @@ abstract class Routes {
   static const HOME = _Paths.HOME;
   static const WELCOME = _Paths.WELCOME;
   static const LOGIN = _Paths.LOGIN;
+  static const NODE = _Paths.NODE;
 }
 
 abstract class _Paths {
@@ -13,4 +14,5 @@ abstract class _Paths {
   static const HOME = '/home';
   static const WELCOME = '/welcome';
   static const LOGIN = '/login';
+  static const NODE = '/node';
 }

+ 33 - 15
lib/main.dart

@@ -1,3 +1,6 @@
+import 'dart:io';
+import 'dart:ui';
+
 import 'package:flutter/material.dart';
 
 import 'package:get/get.dart';
@@ -8,25 +11,40 @@ import 'app/routes/app_pages.dart';
 void main() async {
   const width = 375.0;
   const height = 736.0;
-  WidgetsFlutterBinding.ensureInitialized();
-  await windowManager.ensureInitialized();
-  WindowOptions windowOptions = const WindowOptions(
-    minimumSize: Size(width, height),
-    maximumSize: Size(width, height - kToolbarHeight),
-    size: Size(width, height),
-    center: true,
-    backgroundColor: Colors.transparent,
-    skipTaskbar: false,
-    titleBarStyle: TitleBarStyle.hidden,
-  );
 
-  await windowManager.waitUntilReadyToShow(windowOptions, () async {
-    await windowManager.show();
-    await windowManager.focus();
-  });
+
+  if(Platform.isWindows || Platform.isMacOS){
+    WidgetsFlutterBinding.ensureInitialized();
+    await windowManager.ensureInitialized();
+    WindowOptions windowOptions = const WindowOptions(
+      minimumSize: Size(width, height),
+      maximumSize: Size(width, height - kToolbarHeight),
+      size: Size(width, height),
+      center: true,
+      backgroundColor: Colors.transparent,
+      skipTaskbar: false,
+      titleBarStyle: TitleBarStyle.hidden,
+    );
+
+    await windowManager.waitUntilReadyToShow(windowOptions, () async {
+      await windowManager.show();
+      await windowManager.focus();
+    });
+  }
+  const Set<PointerDeviceKind> _kTouchLikeDeviceTypes = <PointerDeviceKind>{
+    PointerDeviceKind.touch,
+    PointerDeviceKind.mouse,
+    PointerDeviceKind.stylus,
+    PointerDeviceKind.invertedStylus,
+    PointerDeviceKind.unknown
+  };
 
   runApp(
     GetMaterialApp(
+      scrollBehavior: const MaterialScrollBehavior().copyWith(
+          scrollbars: true,
+          dragDevices: _kTouchLikeDeviceTypes
+      ),
       title: "Application",
       initialRoute: AppPages.INITIAL,
       getPages: AppPages.routes,

+ 2 - 0
pubspec.yaml

@@ -39,5 +39,7 @@ flutter:
     - images/main/disconnected.gif
     - images/main/connecting.gif
     - images/main/stopped.gif
+    - images/node/nodebg.png
+    - images/node/nodetablebg.png
   uses-material-design: true