diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 21a3cc1..3a6a651 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -2,7 +2,7 @@ + location = "group:VPN Client.xcodeproj"> diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index cbf8e75..2d87ba2 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,50 +1,75 @@ import Flutter import UIKit -///VPN -///import Foundation import NetworkExtension -class VpnConfigurator { - static func setupTunnel() { - let manager = NEVPNManager.shared() - manager.loadFromPreferences { error in - if let error = error { - print("Failed to load VPN preferences: \(error)") - return - } +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) - let proto = NETunnelProviderProtocol() - proto.providerBundleIdentifier = "com.vpnclient.VPNclientTunnel" - proto.serverAddress = "VPNclient" + let controller = window?.rootViewController as! FlutterViewController + let vpnChannel = FlutterMethodChannel( + name: "com.vpnclient/vpn_control", + binaryMessenger: controller.binaryMessenger + ) - manager.protocolConfiguration = proto - manager.localizedDescription = "VPNclient" - manager.isEnabled = true + vpnChannel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) in + guard let self = self else { return } + switch call.method { + case "setupVPN": + guard let args = call.arguments as? [String: String], + let tunAddr = args["tunAddr"], + let tunMask = args["tunMask"], + let tunDns = args["tunDns"], + let socks5Proxy = args["socks5Proxy"] else { + result(FlutterError(code: "INVALID_ARGS", message: "Invalid arguments", details: nil)) + return + } + print("AppDelegate: Setting up VPN with tunAddr=\(tunAddr), socks5Proxy=\(socks5Proxy)") + let vpnManager = VPNManager.sharedInstance ?? VPNManager() + vpnManager.setupVPNConfiguration(tunAddr: tunAddr, tunMask: tunMask, tunDns: tunDns, socks5Proxy: socks5Proxy) { error in + if let error = error { + print("AppDelegate: Setup VPN failed: \(error.localizedDescription)") + result(FlutterError(code: "SETUP_FAILED", message: error.localizedDescription, details: nil)) + } else { + print("AppDelegate: Setup VPN succeeded") + result(nil) + } + } - manager.saveToPreferences { error in - if let error = error { - print("Failed to save VPN configuration: \(error)") - } else { - print("VPN configuration saved successfully.") + case "startVPN": + let vpnManager = VPNManager.sharedInstance ?? VPNManager() + vpnManager.startVPN { error in + if let error = error { + print("AppDelegate: Start VPN failed: \(error.localizedDescription)") + result(FlutterError(code: "START_FAILED", message: error.localizedDescription, details: nil)) + } else { + print("AppDelegate: Start VPN succeeded") + result(nil) + } } - } - } - } -} -///VPN + case "stopVPN": + let vpnManager = VPNManager.sharedInstance ?? VPNManager() + vpnManager.stopVPN { + print("AppDelegate: Stop VPN succeeded") + result(nil) + } + case "getVPNStatus": + let vpnManager = VPNManager.sharedInstance ?? VPNManager() + let status = vpnManager.vpnStatus.description + print("AppDelegate: VPN status: \(status)") + result(status) -@main -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - ///vpn - VpnConfigurator.setupTunnel() - ///vpn - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} + default: + result(FlutterMethodNotImplemented) + } + } + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} \ No newline at end of file diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index f3c2851..ff3ee21 100644 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -1,8 +1,10 @@ - - + + + - + + @@ -14,13 +16,14 @@ - + - + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 8100ced..57a060e 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -33,6 +33,11 @@ UIApplicationSupportsIndirectInputEvents + UIBackgroundModes + + fetch + remote-notification + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -50,5 +55,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + com.apple.developer.networking.vpn.api + + allow-vpn + diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..ffab33e --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + + diff --git a/ios/Runner/RunnerRelease.entitlements b/ios/Runner/RunnerRelease.entitlements index 7b3112c..0c67376 100644 --- a/ios/Runner/RunnerRelease.entitlements +++ b/ios/Runner/RunnerRelease.entitlements @@ -1,18 +1,5 @@ - - com.apple.developer.networking.networkextension - - dns-settings - packet-tunnel-provider - dns-proxy - app-proxy-provider - content-filter-provider - - com.apple.developer.networking.vpn.api - - allow-vpn - - + diff --git a/ios/Runner/VPNManager.swift b/ios/Runner/VPNManager.swift new file mode 100644 index 0000000..7c018d7 --- /dev/null +++ b/ios/Runner/VPNManager.swift @@ -0,0 +1,180 @@ +import NetworkExtension +import Foundation + +class VPNManager { + private var vpnManager: NETunnelProviderManager? + private let profileName = "Controller" + private var isInitialized = false + private var initializationCompletion: ((Error?) -> Void)? + static var sharedInstance: VPNManager? + + init() { + print("VPNManager: Initializing...") + loadVPNManager { error in + if let error = error { + print("VPNManager: Initialization failed with error: \(error.localizedDescription)") + } else { + print("VPNManager: Initialization completed successfully") + } + self.isInitialized = true + self.initializationCompletion?(error) + self.initializationCompletion = nil + } + } + + private func loadVPNManager(completion: @escaping (Error?) -> Void) { + print("VPNManager: Loading existing VPN managers...") + NETunnelProviderManager.loadAllFromPreferences { managers, error in + if let error = error { + print("VPNManager: Error loading VPN managers: \(error.localizedDescription)") + self.vpnManager = NETunnelProviderManager() + self.vpnManager?.localizedDescription = self.profileName + print("VPNManager: Created new VPN profile due to error: \(self.profileName)") + completion(nil) + return + } + + print("VPNManager: Loaded \(managers?.count ?? 0) VPN managers") + if let existingManager = managers?.first(where: { $0.localizedDescription == self.profileName }) { + self.vpnManager = existingManager + print("VPNManager: Found existing VPN profile: \(self.profileName)") + } else { + self.vpnManager = NETunnelProviderManager() + self.vpnManager?.localizedDescription = self.profileName + print("VPNManager: Created new VPN profile: \(self.profileName)") + } + print("VPNManager: vpnManager is \(self.vpnManager != nil ? "set" : "nil")") + completion(nil) + } + + // Таймаут для loadAllFromPreferences + DispatchQueue.global().asyncAfter(deadline: .now() + 5) { + if !self.isInitialized { + print("VPNManager: loadAllFromPreferences timed out") + self.vpnManager = NETunnelProviderManager() + self.vpnManager?.localizedDescription = self.profileName + print("VPNManager: Created new VPN profile due to timeout: \(self.profileName)") + self.isInitialized = true + completion(nil) + } + } + } + + private func waitForInitialization(completion: @escaping (Error?) -> Void) { + if isInitialized { + print("VPNManager: Already initialized") + completion(nil) + return + } + print("VPNManager: Waiting for initialization...") + initializationCompletion = completion + } + + func setupVPNConfiguration(tunAddr: String, tunMask: String, tunDns: String, socks5Proxy: String, completion: @escaping (Error?) -> Void) { + print("VPNManager: Setting up VPN configuration with tunAddr=\(tunAddr), tunMask=\(tunMask), tunDns=\(tunDns), socks5Proxy=\(socks5Proxy)") + waitForInitialization { error in + if let error = error { + completion(error) + return + } + guard let vpnManager = self.vpnManager else { + print("VPNManager: VPN Manager not initialized") + completion(NSError(domain: "VPNError", code: -1, userInfo: [NSLocalizedDescriptionKey: "VPN Manager not initialized"])) + return + } + + vpnManager.loadFromPreferences { error in + if let error = error { + print("VPNManager: Load preferences error: \(error.localizedDescription)") + completion(error) + return + } + + let tunnelProtocol = NETunnelProviderProtocol() + tunnelProtocol.providerBundleIdentifier = "click.vpnclient.VPNclientTunnel" + tunnelProtocol.serverAddress = socks5Proxy + tunnelProtocol.providerConfiguration = [ + "tunAddr": tunAddr, + "tunMask": tunMask, + "tunDns": tunDns, + "socks5Proxy": socks5Proxy + ] + + vpnManager.protocolConfiguration = tunnelProtocol + vpnManager.isEnabled = true + vpnManager.isOnDemandEnabled = false + + print("VPNManager: Saving VPN configuration...") + vpnManager.saveToPreferences { error in + if let error = error { + print("VPNManager: Save preferences error: \(error.localizedDescription)") + completion(error) + } else { + print("VPNManager: VPN configuration saved successfully") + completion(nil) + } + } + } + } + } + + func startVPN(completion: @escaping (Error?) -> Void) { + print("VPNManager: Starting VPN...") + waitForInitialization { error in + if let error = error { + completion(error) + return + } + guard let vpnManager = self.vpnManager else { + print("VPNManager: VPN Manager not initialized") + completion(NSError(domain: "VPNError", code: -1, userInfo: [NSLocalizedDescriptionKey: "VPN Manager not initialized"])) + return + } + vpnManager.loadFromPreferences { error in + if let error = error { + print("VPNManager: Load preferences error before start: \(error.localizedDescription)") + completion(error) + return + } + do { + try vpnManager.connection.startVPNTunnel() + print("VPNManager: VPN tunnel started successfully") + completion(nil) + } catch { + print("VPNManager: Start VPN error: \(error.localizedDescription)") + completion(error) + } + } + } + } + + func stopVPN(completion: @escaping () -> Void) { + print("VPNManager: Stopping VPN...") + waitForInitialization { _ in + self.vpnManager?.connection.stopVPNTunnel() + completion() + } + } + + var vpnStatus: NEVPNStatus { + let status = vpnManager?.connection.status ?? .invalid + print("VPNManager: Current VPN status: \(status.description)") + return status + } + + static func cleanup() { + sharedInstance = nil + } +} + +extension NEVPNStatus { + var description: String { + switch self { + case .disconnected: return "Disconnected" + case .connecting: return "Connecting..." + case .connected: return "Connected" + case .disconnecting: return "Disconnecting..." + default: return "Not Added Profile" + } + } +} \ No newline at end of file diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/VPN Client.xcodeproj/project.pbxproj similarity index 69% rename from ios/Runner.xcodeproj/project.pbxproj rename to ios/VPN Client.xcodeproj/project.pbxproj index 6251e06..500eac7 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/VPN Client.xcodeproj/project.pbxproj @@ -3,19 +3,21 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 77; objects = { /* Begin PBXBuildFile section */ - 05623FCADE53740FE6EC38FC /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C1A7CF68EF82448274F79EE /* Pods_RunnerTests.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 2D7B47EE58217BA44F021C99 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A0366090C7CE5E83E3E66D2 /* Pods_RunnerTests.framework */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 5CF4606063F6CF5DDF0DEF1E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 474E7930403F702C7244270A /* Pods_Runner.framework */; }; 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 */; }; + ADCEA9EEA0674573FF96F9D6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6C9AB3A9273437DA77CF7190 /* Pods_Runner.framework */; }; + D86303622DDAA124009E3D50 /* VPNManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86303612DDAA124009E3D50 /* VPNManager.swift */; }; + D863037A2DDAD1CC009E3D50 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D86303792DDAD1CC009E3D50 /* NetworkExtension.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -26,12 +28,19 @@ remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; + D86303802DDAD1CC009E3D50 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = D86303772DDAD1CC009E3D50; + remoteInfo = VPNclientTunnel; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; + buildActionMask = 12; dstPath = ""; dstSubfolderSpec = 10; files = ( @@ -39,23 +48,31 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + D86303832DDAD1CC009E3D50 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 1; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 197284AFD158A57F33B7508F /* 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 = ""; }; - 2BABBD1CBB549C229028E3E6 /* 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 = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3A0366090C7CE5E83E3E66D2 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3AC403B42DBB3FD400E35EC1 /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 40D432E32B669D72F284E50A /* 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 = ""; }; - 474E7930403F702C7244270A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6C9AB3A9273437DA77CF7190 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 8C1A7CF68EF82448274F79EE /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8665B952CF21C952CDD28CC0 /* 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 = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -63,17 +80,44 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B67FBA7DE552A310D8682AF3 /* 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 = ""; }; - D486CAC8BA4A2B991D6B8DDF /* 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 = ""; }; - DED175BEB396A3E3B6FCC983 /* 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 = ""; }; + A232C605EB1ADE1BB86E5BF8 /* 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 = ""; }; + AC1B24E5523670E179DC665F /* 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 = ""; }; + ACFD93A1D03164446FE727AB /* 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 = ""; }; + C5E533B152977104C504A2D5 /* 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 = ""; }; + D86303602DDAA0DD009E3D50 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + D86303612DDAA124009E3D50 /* VPNManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNManager.swift; sourceTree = ""; }; + D86303782DDAD1CC009E3D50 /* VPNclientTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = VPNclientTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + D86303792DDAD1CC009E3D50 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; + E092053B252D5542B476BDA0 /* 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 = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + D86303872DDAD1CC009E3D50 /* Exceptions for "VPNclientTunnel" folder in "VPNclientTunnel" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = D86303772DDAD1CC009E3D50 /* VPNclientTunnel */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + D863037B2DDAD1CC009E3D50 /* VPNclientTunnel */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + D86303872DDAD1CC009E3D50 /* Exceptions for "VPNclientTunnel" folder in "VPNclientTunnel" target */, + ); + path = VPNclientTunnel; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 960B5143B6A69B8BB00D37F4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 05623FCADE53740FE6EC38FC /* Pods_RunnerTests.framework in Frameworks */, + 2D7B47EE58217BA44F021C99 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -81,7 +125,15 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5CF4606063F6CF5DDF0DEF1E /* Pods_Runner.framework in Frameworks */, + ADCEA9EEA0674573FF96F9D6 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D86303752DDAD1CC009E3D50 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D863037A2DDAD1CC009E3D50 /* NetworkExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -91,12 +143,12 @@ 1676946CE4F357204D9C4813 /* Pods */ = { isa = PBXGroup; children = ( - 197284AFD158A57F33B7508F /* Pods-Runner.debug.xcconfig */, - D486CAC8BA4A2B991D6B8DDF /* Pods-Runner.release.xcconfig */, - 2BABBD1CBB549C229028E3E6 /* Pods-Runner.profile.xcconfig */, - 40D432E32B669D72F284E50A /* Pods-RunnerTests.debug.xcconfig */, - DED175BEB396A3E3B6FCC983 /* Pods-RunnerTests.release.xcconfig */, - B67FBA7DE552A310D8682AF3 /* Pods-RunnerTests.profile.xcconfig */, + C5E533B152977104C504A2D5 /* Pods-Runner.debug.xcconfig */, + A232C605EB1ADE1BB86E5BF8 /* Pods-Runner.release.xcconfig */, + E092053B252D5542B476BDA0 /* Pods-Runner.profile.xcconfig */, + ACFD93A1D03164446FE727AB /* Pods-RunnerTests.debug.xcconfig */, + AC1B24E5523670E179DC665F /* Pods-RunnerTests.release.xcconfig */, + 8665B952CF21C952CDD28CC0 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -125,6 +177,7 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, + D863037B2DDAD1CC009E3D50 /* VPNclientTunnel */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, 1676946CE4F357204D9C4813 /* Pods */, @@ -137,6 +190,7 @@ children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + D86303782DDAD1CC009E3D50 /* VPNclientTunnel.appex */, ); name = Products; sourceTree = ""; @@ -144,6 +198,8 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + D86303612DDAA124009E3D50 /* VPNManager.swift */, + D86303602DDAA0DD009E3D50 /* Runner.entitlements */, 3AC403B42DBB3FD400E35EC1 /* RunnerRelease.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, @@ -160,8 +216,9 @@ E9078DB93BAB5787C511C451 /* Frameworks */ = { isa = PBXGroup; children = ( - 474E7930403F702C7244270A /* Pods_Runner.framework */, - 8C1A7CF68EF82448274F79EE /* Pods_RunnerTests.framework */, + D86303792DDAD1CC009E3D50 /* NetworkExtension.framework */, + 6C9AB3A9273437DA77CF7190 /* Pods_Runner.framework */, + 3A0366090C7CE5E83E3E66D2 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -173,7 +230,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 81BFDB42C18FDD849CBD3B91 /* [CP] Check Pods Manifest.lock */, + 4D1B5BEE14246A87461BBB9B /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, 960B5143B6A69B8BB00D37F4 /* Frameworks */, @@ -192,24 +249,46 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 6540B4C79A6EB9CD6CA8C965 /* [CP] Check Pods Manifest.lock */, + 2119E14845FE31FAB4D234BF /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - F6B5A4EE9456ACB2EE7DD28F /* [CP] Embed Pods Frameworks */, + D86303832DDAD1CC009E3D50 /* Embed Foundation Extensions */, + F443A19394F529718D644FF5 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( + D86303812DDAD1CC009E3D50 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; + D86303772DDAD1CC009E3D50 /* VPNclientTunnel */ = { + isa = PBXNativeTarget; + buildConfigurationList = D86303882DDAD1CC009E3D50 /* Build configuration list for PBXNativeTarget "VPNclientTunnel" */; + buildPhases = ( + D86303742DDAD1CC009E3D50 /* Sources */, + D86303752DDAD1CC009E3D50 /* Frameworks */, + D86303762DDAD1CC009E3D50 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + D863037B2DDAD1CC009E3D50 /* VPNclientTunnel */, + ); + name = VPNclientTunnel; + productName = VPNclientTunnel; + productReference = D86303782DDAD1CC009E3D50 /* VPNclientTunnel.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -217,6 +296,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 1620; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -228,10 +308,12 @@ CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; + D86303772DDAD1CC009E3D50 = { + CreatedOnToolsVersion = 16.2; + }; }; }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "VPN Client" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -239,12 +321,14 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + preferredProjectObjectVersion = 77; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, 331C8080294A63A400263BE5 /* RunnerTests */, + D86303772DDAD1CC009E3D50 /* VPNclientTunnel */, ); }; /* End PBXProject section */ @@ -268,26 +352,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; + D86303762DDAD1CC009E3D50 /* Resources */ = { + isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 6540B4C79A6EB9CD6CA8C965 /* [CP] Check Pods Manifest.lock */ = { +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 2119E14845FE31FAB4D234BF /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -309,7 +384,23 @@ 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; }; - 81BFDB42C18FDD849CBD3B91 /* [CP] Check Pods Manifest.lock */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 12; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; + }; + 4D1B5BEE14246A87461BBB9B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -346,7 +437,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - F6B5A4EE9456ACB2EE7DD28F /* [CP] Embed Pods Frameworks */ = { + F443A19394F529718D644FF5 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -380,6 +471,14 @@ files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + D86303622DDAA124009E3D50 /* VPNManager.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D86303742DDAD1CC009E3D50 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; @@ -391,6 +490,11 @@ target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; }; + D86303812DDAD1CC009E3D50 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D86303772DDAD1CC009E3D50 /* VPNclientTunnel */; + targetProxy = D86303802DDAD1CC009E3D50 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -472,17 +576,20 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6XT4R7V83F; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "VPN Client"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.5; - PRODUCT_BUNDLE_IDENTIFIER = click.vpnclient; + MARKETING_VERSION = 1.0.12; + PRODUCT_BUNDLE_IDENTIFIER = vpnclient.click; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -493,7 +600,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 40D432E32B669D72F284E50A /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = ACFD93A1D03164446FE727AB /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -511,7 +618,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DED175BEB396A3E3B6FCC983 /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = AC1B24E5523670E179DC665F /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -527,7 +634,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B67FBA7DE552A310D8682AF3 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 8665B952CF21C952CDD28CC0 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -660,17 +767,20 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6XT4R7V83F; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "VPN Client"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.5; - PRODUCT_BUNDLE_IDENTIFIER = click.vpnclient; + MARKETING_VERSION = 1.0.12; + PRODUCT_BUNDLE_IDENTIFIER = vpnclient.click; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -688,17 +798,18 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6XT4R7V83F; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "VPN Client"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.5; - PRODUCT_BUNDLE_IDENTIFIER = click.vpnclient; + MARKETING_VERSION = 1.0.12; + PRODUCT_BUNDLE_IDENTIFIER = vpnclient.click; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -707,6 +818,123 @@ }; name = Release; }; + D86303842DDAD1CC009E3D50 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = VPNclientTunnel/VPNclientTunnel.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6XT4R7V83F; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = VPNclientTunnel/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = VPNclientTunnel; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = click.vpnclient.VPNclientTunnel; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D86303852DDAD1CC009E3D50 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = VPNclientTunnel/VPNclientTunnel.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6XT4R7V83F; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = VPNclientTunnel/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = VPNclientTunnel; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = click.vpnclient.VPNclientTunnel; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + D86303862DDAD1CC009E3D50 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = VPNclientTunnel/VPNclientTunnel.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6XT4R7V83F; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = VPNclientTunnel/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = VPNclientTunnel; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = click.vpnclient.VPNclientTunnel; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -720,7 +948,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "VPN Client" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, @@ -740,6 +968,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D86303882DDAD1CC009E3D50 /* Build configuration list for PBXNativeTarget "VPNclientTunnel" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D86303842DDAD1CC009E3D50 /* Debug */, + D86303852DDAD1CC009E3D50 /* Release */, + D86303862DDAD1CC009E3D50 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/VPN Client.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to ios/VPN Client.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/VPN Client.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to ios/VPN Client.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/VPN Client.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to ios/VPN Client.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/VPN Client.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 90% rename from ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to ios/VPN Client.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 15cada4..ba31c1f 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/VPN Client.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -17,7 +17,7 @@ BlueprintIdentifier = "97C146ED1CF9000F007C117D" BuildableName = "Runner.app" BlueprintName = "Runner" - ReferencedContainer = "container:Runner.xcodeproj"> + ReferencedContainer = "container:VPN Client.xcodeproj"> @@ -33,7 +33,7 @@ BlueprintIdentifier = "97C146ED1CF9000F007C117D" BuildableName = "Runner.app" BlueprintName = "Runner" - ReferencedContainer = "container:Runner.xcodeproj"> + ReferencedContainer = "container:VPN Client.xcodeproj"> @@ -45,7 +45,7 @@ BlueprintIdentifier = "331C8080294A63A400263BE5" BuildableName = "RunnerTests.xctest" BlueprintName = "RunnerTests" - ReferencedContainer = "container:Runner.xcodeproj"> + ReferencedContainer = "container:VPN Client.xcodeproj"> @@ -68,7 +68,7 @@ BlueprintIdentifier = "97C146ED1CF9000F007C117D" BuildableName = "Runner.app" BlueprintName = "Runner" - ReferencedContainer = "container:Runner.xcodeproj"> + ReferencedContainer = "container:VPN Client.xcodeproj"> @@ -85,7 +85,7 @@ BlueprintIdentifier = "97C146ED1CF9000F007C117D" BuildableName = "Runner.app" BlueprintName = "Runner" - ReferencedContainer = "container:Runner.xcodeproj"> + ReferencedContainer = "container:VPN Client.xcodeproj"> diff --git a/ios/VPNclientTunnel/Info.plist b/ios/VPNclientTunnel/Info.plist new file mode 100644 index 0000000..9a63630 --- /dev/null +++ b/ios/VPNclientTunnel/Info.plist @@ -0,0 +1,22 @@ + + + + + NSExtension + + com.apple.developer.networking.vpn.api + + allow-vpn + + UIBackgroundModes + + fetch + remote-notification + + NSExtensionPointIdentifier + com.apple.networkextension.packet-tunnel + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).PacketTunnelProvider + + + diff --git a/ios/VPNclientTunnel/PacketTunnelProvider.swift b/ios/VPNclientTunnel/PacketTunnelProvider.swift new file mode 100644 index 0000000..6d96061 --- /dev/null +++ b/ios/VPNclientTunnel/PacketTunnelProvider.swift @@ -0,0 +1,137 @@ +import NetworkExtension +import os.log + +class PacketTunnelProvider: NEPacketTunnelProvider { + private var tunnelRunning = false + + override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { + os_log(.debug, "PacketTunnelProvider: Starting tunnel with options: %@", String(describing: options)) + + guard let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol, + let providerConfig = protocolConfiguration.providerConfiguration, + let tunAddr = providerConfig["tunAddr"] as? String, + let tunMask = providerConfig["tunMask"] as? String, + let tunDns = providerConfig["tunDns"] as? String, + let socks5Proxy = providerConfig["socks5Proxy"] as? String else { + os_log(.error, "PacketTunnelProvider: Failed to load provider configuration") + completionHandler(NSError(domain: "PacketTunnelProvider", code: -1, userInfo: [NSLocalizedDescriptionKey: "Missing provider configuration"])) + return + } + + os_log(.debug, "PacketTunnelProvider: Config - tunAddr: %@, tunMask: %@, tunDns: %@, socks5Proxy: %@", tunAddr, tunMask, tunDns, socks5Proxy) + + let proxyComponents = socks5Proxy.components(separatedBy: ":") + guard proxyComponents.count == 2, + let socks5Address = proxyComponents.first, + let socks5Port = UInt16(proxyComponents.last ?? "1080") else { + os_log(.error, "PacketTunnelProvider: Invalid SOCKS5 proxy format: %@", socks5Proxy) + completionHandler(NSError(domain: "PacketTunnelProvider", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid SOCKS5 proxy format"])) + return + } + + let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: socks5Address) + settings.mtu = 1500 + + let ipv4Settings = NEIPv4Settings(addresses: [tunAddr], subnetMasks: [tunMask]) + ipv4Settings.includedRoutes = [NEIPv4Route.default()] + settings.ipv4Settings = ipv4Settings + + let dnsSettings = NEDNSSettings(servers: [tunDns]) + settings.dnsSettings = dnsSettings + + os_log(.debug, "PacketTunnelProvider: Applying tunnel network settings...") + setTunnelNetworkSettings(settings) { error in + if let error = error { + os_log(.error, "PacketTunnelProvider: Failed to set tunnel network settings: %@", error.localizedDescription) + completionHandler(error) + return + } + + os_log(.info, "PacketTunnelProvider: Tunnel network settings applied successfully") + + let config = """ + tunnel: + name: tun0 + mtu: 8500 + socks5: + address: "\(socks5Address)" + port: \(socks5Port) + """ + + os_log(.debug, "PacketTunnelProvider: Starting hev-socks5-tunnel with config: %@", config) + DispatchQueue.global().async { + self.startHevSocks5Tunnel(withConfig: config) + } + + self.monitorTunnelActivity() + + os_log(.debug, "PacketTunnelProvider: Calling completion handler with success") + completionHandler(nil) + } + } + + func startHevSocks5Tunnel(withConfig config: String) { + os_log(.debug, "PacketTunnelProvider: Starting hev-socks5-tunnel...") + + guard let configData = config.data(using: .utf8) else { + os_log(.error, "PacketTunnelProvider: Failed to convert config to UTF-8 data") + return + } + let configLen = UInt32(configData.count) + + // Поскольку мы не можем получить tun_fd, нужно переработать логику + os_log(.debug, "PacketTunnelProvider: Using packetFlow instead of tun_fd") + + // Запускаем hev-socks5-tunnel в режиме, где мы сами обрабатываем пакеты + tunnelRunning = true + DispatchQueue.global().async { + // Читаем пакеты из packetFlow и передаём их в SOCKS5 + self.handlePackets() + } + } + + func handlePackets() { + let flow = self.packetFlow + + flow.readPackets { packets, protocols in + if !self.tunnelRunning { + os_log(.info, "PacketTunnelProvider: Stopping packet handling") + return + } + + for (packet, proto) in zip(packets, protocols) { + os_log(.debug, "PacketTunnelProvider: Received packet of size %d, protocol: %@", packet.count, proto.description) + // Здесь нужно передать пакет в hev-socks5-tunnel + // Например, через кастомный интерфейс или сокет + } + + // Продолжаем читать пакеты + self.handlePackets() + } + } + + func monitorTunnelActivity() { + DispatchQueue.global().async { + while self.tunnelRunning { + usleep(1000000) // Проверка каждую секунду + os_log(.debug, "PacketTunnelProvider: Tunnel still active, checking packets...") + } + } + } + + func checkTunnelStatus() { + os_log(.debug, "PacketTunnelProvider: Checking tunnel status...") + if self.packetFlow == nil { + os_log(.error, "PacketTunnelProvider: Tunnel flow is nil, possible disconnection") + } else { + os_log(.info, "PacketTunnelProvider: Tunnel flow is active") + } + } + + override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { + os_log(.debug, "PacketTunnelProvider: Stopping tunnel with reason: %@", reason.rawValue.description) + tunnelRunning = false +// hev_socks5_tunnel_quit() + completionHandler() + } +} diff --git a/ios/VPNclientTunnel/VPNclientTunnel.entitlements b/ios/VPNclientTunnel/VPNclientTunnel.entitlements new file mode 100644 index 0000000..0154eb4 --- /dev/null +++ b/ios/VPNclientTunnel/VPNclientTunnel.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.click.vpnclient + + + diff --git a/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/build-request.json b/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/build-request.json new file mode 100644 index 0000000..38eab09 --- /dev/null +++ b/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/build-request.json @@ -0,0 +1,27 @@ +{ + "buildCommand" : { + "command" : "build", + "skipDependencies" : false, + "style" : "buildOnly" + }, + "configuredTargets" : [ + + ], + "continueBuildingAfterErrors" : false, + "dependencyScope" : "workspace", + "enableIndexBuildArena" : false, + "hideShellScriptEnvironment" : false, + "parameters" : { + "action" : "build", + "overrides" : { + + } + }, + "qos" : "utility", + "schemeCommand" : "launch", + "showNonLoggedProgress" : true, + "useDryRun" : false, + "useImplicitDependencies" : false, + "useLegacyBuildLocations" : false, + "useParallelTargets" : true +} \ No newline at end of file diff --git a/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/description.msgpack b/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/description.msgpack new file mode 100644 index 0000000..0e18711 Binary files /dev/null and b/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/description.msgpack differ diff --git a/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/manifest.json b/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/manifest.json new file mode 100644 index 0000000..7391713 --- /dev/null +++ b/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/manifest.json @@ -0,0 +1 @@ +{"client":{"name":"basic","version":0,"file-system":"device-agnostic","perform-ownership-analysis":"no"},"targets":{"":[""]},"commands":{"":{"tool":"phony","inputs":[""],"outputs":[""]},"P0:::Gate WorkspaceHeaderMapVFSFilesWritten":{"tool":"phony","inputs":[],"outputs":[""]}}} \ No newline at end of file diff --git a/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/target-graph.txt b/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/target-graph.txt new file mode 100644 index 0000000..b83b158 --- /dev/null +++ b/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/target-graph.txt @@ -0,0 +1 @@ +Target dependency graph (0 target) \ No newline at end of file diff --git a/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/task-store.msgpack b/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/task-store.msgpack new file mode 100644 index 0000000..6cef3fe Binary files /dev/null and b/ios/build/XCBuildData/2b3a1334de51e4dc6dd39387be36d594.xcbuilddata/task-store.msgpack differ diff --git a/lib/pages/main/main_btn.dart b/lib/pages/main/main_btn.dart index 7e5d82a..c288ab7 100644 --- a/lib/pages/main/main_btn.dart +++ b/lib/pages/main/main_btn.dart @@ -1,15 +1,11 @@ +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:vpn_client/design/colors.dart'; import 'package:flutter_v2ray/flutter_v2ray.dart'; import 'package:vpn_client/localization_service.dart'; import 'package:vpn_client/vpn_state.dart'; - -final FlutterV2ray flutterV2ray = FlutterV2ray( - onStatusChanged: (status) { - // Handle status changes if needed - }, -); +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class MainBtn extends StatefulWidget { const MainBtn({super.key}); @@ -62,37 +58,45 @@ class MainBtnState extends State with SingleTickerProviderStateMixin { Future _toggleConnection(BuildContext context) async { final vpnState = Provider.of(context, listen: false); - switch (vpnState.connectionStatus) { - case ConnectionStatus.disconnected: - vpnState.setConnectionStatus(ConnectionStatus.connecting); - _animationController.repeat(reverse: true); - String link = - "vless://c61daf3e-83ff-424f-a4ff-5bfcb46f0b30@45.77.190.146:8443?encryption=none&flow=&security=reality&sni=www.gstatic.com&fp=chrome&pbk=rLCmXWNVoRBiknloDUsbNS5ONjiI70v-BWQpWq0HCQ0&sid=108108108108#%F0%9F%87%BA%F0%9F%87%B8+%F0%9F%99%8F+USA+%231"; - V2RayURL parser = FlutterV2ray.parseFromURL(link); + try { + switch (vpnState.connectionStatus) { + case ConnectionStatus.disconnected: + vpnState.setConnectionStatus(ConnectionStatus.connecting); + _animationController.repeat(reverse: true); + + if (Platform.isIOS) { + await vpnState.setupVPN( + tunAddr: '192.168.1.2', + tunMask: '255.255.255.0', + tunDns: '8.8.8.8', + socks5Proxy: '176.226.244.28:1080', + ); + await vpnState.startVPN(); + } else if (Platform.isAndroid) { + await vpnState.startVPN(); + } + await _animationController.forward(); + _animationController.stop(); + break; - if (await flutterV2ray.requestPermission()) { - await flutterV2ray.startV2Ray( - remark: parser.remark, - config: parser.getFullConfiguration(), - blockedApps: null, - bypassSubnets: null, - proxyOnly: false, - ); - } + case ConnectionStatus.connected: + vpnState.setConnectionStatus(ConnectionStatus.disconnecting); + _animationController.repeat(reverse: true); + if (Platform.isIOS || Platform.isAndroid) { + await vpnState.stopVPN(); + } + await _animationController.reverse(); + _animationController.stop(); + break; - vpnState.startTimer(); - vpnState.setConnectionStatus(ConnectionStatus.connected); - await _animationController.forward(); - _animationController.stop(); - case ConnectionStatus.connected: - vpnState.setConnectionStatus(ConnectionStatus.disconnecting); - _animationController.repeat(reverse: true); - await flutterV2ray.stopV2Ray(); - vpnState.stopTimer(); - vpnState.setConnectionStatus(ConnectionStatus.disconnected); - await _animationController.reverse(); - _animationController.stop(); - default: + default: + break; + } + } catch (e) { + print('Error toggling connection: $e'); + vpnState.setConnectionStatus(ConnectionStatus.disconnected); + _animationController.reverse(); + _animationController.stop(); } } diff --git a/lib/vpn_state.dart b/lib/vpn_state.dart index b8e5945..492b4ad 100644 --- a/lib/vpn_state.dart +++ b/lib/vpn_state.dart @@ -1,6 +1,8 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; -// import 'package:flutter_v2ray/flutter_v2ray.dart'; +import 'package:flutter_v2ray/flutter_v2ray.dart'; +import 'package:flutter/services.dart'; enum ConnectionStatus { disconnected, @@ -14,15 +16,149 @@ class VpnState with ChangeNotifier { ConnectionStatus _connectionStatus = ConnectionStatus.disconnected; Timer? _timer; String _connectionTimeText = "00:00:00"; + static const _vpnChannel = MethodChannel('com.vpnclient/vpn_control'); + late FlutterV2ray _v2ray; // Сохраняем экземпляр FlutterV2ray ConnectionStatus get connectionStatus => _connectionStatus; String get connectionTimeText => _connectionTimeText; VpnState() { - // Initializing V2Ray when creating a provider + if (Platform.isAndroid) { + _v2ray = FlutterV2ray(onStatusChanged: (status) { + _updateStatusFromV2Ray(status); + })..initializeV2Ray(); + } else if (Platform.isIOS) { + _updateStatusFromiOS(); + } + // Initializing V2Ray when creating a provider (from development branch) // FlutterV2ray(onStatusChanged: (status) {}).initializeV2Ray(); } + void _updateStatusFromV2Ray(V2RayStatus status) { + switch (status) { + case 'CONNECTING': + setConnectionStatus(ConnectionStatus.connecting); + break; + case ConnectionStatus.connected: + setConnectionStatus(ConnectionStatus.connected); + startTimer(); + break; + case ConnectionStatus.disconnected: + setConnectionStatus(ConnectionStatus.disconnected); + stopTimer(); + break; + default: + setConnectionStatus(ConnectionStatus.disconnected); + } + } + + Future _updateStatusFromiOS() async { + try { + final status = await _vpnChannel.invokeMethod('getVPNStatus'); + switch (status) { + case 'Connected': + setConnectionStatus(ConnectionStatus.connected); + startTimer(); + break; + case 'Connecting...': + setConnectionStatus(ConnectionStatus.connecting); + break; + case 'Disconnecting...': + setConnectionStatus(ConnectionStatus.disconnecting); + break; + case 'Disconnected': + case 'Not Added Profile': + default: + setConnectionStatus(ConnectionStatus.disconnected); + stopTimer(); + break; + } + } catch (e) { + print('Error getting VPN status on iOS: $e'); + setConnectionStatus(ConnectionStatus.disconnected); + } + } + + Future setupVPN({ + required String tunAddr, + required String tunMask, + required String tunDns, + required String socks5Proxy, + }) async { + if (Platform.isIOS) { + try { + await _vpnChannel.invokeMethod('setupVPN', { + 'tunAddr': tunAddr, + 'tunMask': tunMask, + 'tunDns': tunDns, + 'socks5Proxy': socks5Proxy, + }); + } catch (e) { + print('Error setting up VPN: $e'); + rethrow; + } + } + } + + Future startVPN() async { + if (Platform.isIOS) { + try { + await _vpnChannel.invokeMethod('startVPN'); + setConnectionStatus(ConnectionStatus.connected); + startTimer(); + } catch (e) { + print('Error starting VPN on iOS: $e'); + setConnectionStatus(ConnectionStatus.disconnected); + rethrow; + } + } else if (Platform.isAndroid) { + try { + if (await _v2ray.requestPermission()) { + final parser = FlutterV2ray.parseFromURL( + 'vless://c61daf3e-83ff-424f-a4ff-5bfcb46f0b30@45.77.190.146:8443?encryption=none&flow=&security=reality&sni=www.gstatic.com&fp=chrome&pbk=rLCmXWNVoRBiknloDUsbNS5ONjiI70v-BWQpWq0HCQ0&sid=108108108108#%F0%9F%87%BA%F0%9F%87%B8+%F0%9F%99%8F+USA+%231', + ); + await _v2ray.startV2Ray( + remark: parser.remark, + config: parser.getFullConfiguration(), + blockedApps: null, + bypassSubnets: null, + proxyOnly: false, + ); + } + setConnectionStatus(ConnectionStatus.connected); + startTimer(); + } catch (e) { + print('Error starting VPN on Android: $e'); + setConnectionStatus(ConnectionStatus.disconnected); + rethrow; + } + } + } + + Future stopVPN() async { + if (Platform.isIOS) { + try { + await _vpnChannel.invokeMethod('stopVPN'); + setConnectionStatus(ConnectionStatus.disconnected); + stopTimer(); + } catch (e) { + print('Error stopping VPN on iOS: $e'); + setConnectionStatus(ConnectionStatus.disconnected); + rethrow; + } + } else if (Platform.isAndroid) { + try { + await _v2ray.stopV2Ray(); + setConnectionStatus(ConnectionStatus.disconnected); + stopTimer(); + } catch (e) { + print('Error stopping VPN on Android: $e'); + setConnectionStatus(ConnectionStatus.disconnected); + rethrow; + } + } + } + void setConnectionStatus(ConnectionStatus status) { _connectionStatus = status; notifyListeners(); @@ -36,7 +172,7 @@ class VpnState with ChangeNotifier { int minutes = (seconds % 3600) ~/ 60; int remainingSeconds = seconds % 60; _connectionTimeText = - '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}'; + '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}'; notifyListeners(); seconds++; }); @@ -53,4 +189,4 @@ class VpnState with ChangeNotifier { _timer?.cancel(); super.dispose(); } -} +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 320fca9..cad6e55 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,8 +35,8 @@ dependencies: flutter_svg: ^2.0.17 flutter_localizations: sdk: flutter - shared_preferences: ^2.2.3 - flutter_native_splash: ^2.3.1 + shared_preferences: ^2.5.3 + flutter_native_splash: ^2.4.6 flutter_bloc: ^9.0.0 # vpnclient_engine_flutter: # git: