diff --git a/IVPNClient.xcodeproj/project.pbxproj b/IVPNClient.xcodeproj/project.pbxproj index b6f00724..b0e00917 100644 --- a/IVPNClient.xcodeproj/project.pbxproj +++ b/IVPNClient.xcodeproj/project.pbxproj @@ -151,6 +151,7 @@ 828772F7221C008100D5E330 /* ServerConfigurationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 828772F6221C008100D5E330 /* ServerConfigurationCell.swift */; }; 828772F9221C01C300D5E330 /* ServersConfigurationTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 828772F8221C01C300D5E330 /* ServersConfigurationTableViewController.swift */; }; 828772FB221C28E000D5E330 /* FlagImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 828772FA221C28E000D5E330 /* FlagImageView.swift */; }; + 828C45692C88772E0064F365 /* AppIntentsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 828C45682C8877290064F365 /* AppIntentsHandler.swift */; }; 828D8A6D258245AD00CB0E5B /* TwoFactorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 828D8A6C258245AD00CB0E5B /* TwoFactorViewController.swift */; }; 828E9C95231E5780001E1FCF /* Data+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 828E9C94231E5780001E1FCF /* Data+Ext.swift */; }; 828E9C96231E5780001E1FCF /* Data+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 828E9C94231E5780001E1FCF /* Data+Ext.swift */; }; @@ -588,6 +589,7 @@ 828772F6221C008100D5E330 /* ServerConfigurationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfigurationCell.swift; sourceTree = ""; }; 828772F8221C01C300D5E330 /* ServersConfigurationTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServersConfigurationTableViewController.swift; sourceTree = ""; }; 828772FA221C28E000D5E330 /* FlagImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagImageView.swift; sourceTree = ""; }; + 828C45682C8877290064F365 /* AppIntentsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentsHandler.swift; sourceTree = ""; }; 828D8A6C258245AD00CB0E5B /* TwoFactorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoFactorViewController.swift; sourceTree = ""; }; 828E9C94231E5780001E1FCF /* Data+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Ext.swift"; sourceTree = ""; }; 8290195E243CB27500777B6E /* ControlPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlPanelView.swift; sourceTree = ""; }; @@ -927,6 +929,7 @@ 82C1D5BD23FE8BD90059A915 /* ControlPanel */, 8208525723FD5EB20008C112 /* MainViewController.swift */, 8208525B23FD64160008C112 /* MainViewController+Ext.swift */, + 828C45682C8877290064F365 /* AppIntentsHandler.swift */, ); path = MainScreen; sourceTree = ""; @@ -2337,6 +2340,7 @@ 8292E1A92174C11600123538 /* Interface.swift in Sources */, 82F638CC217DC25600410318 /* CIDRAddress.swift in Sources */, 824B86D226D40E7800D0101A /* FileManager+Extension.swift in Sources */, + 828C45692C88772E0064F365 /* AppIntentsHandler.swift in Sources */, 8208525A23FD5F670008C112 /* FloatingPanelController+Ext.swift in Sources */, 821429B722FC2BE90056B8FF /* Result.swift in Sources */, 827855B92472B27F00B3B6BD /* Account.swift in Sources */, diff --git a/IVPNClient/Models/AppIntents.swift b/IVPNClient/Models/AppIntents.swift index 5e0d7683..83879e37 100644 --- a/IVPNClient/Models/AppIntents.swift +++ b/IVPNClient/Models/AppIntents.swift @@ -24,6 +24,30 @@ import AppIntents +@available(iOS 16, *) +struct Connect: AppIntent { + static var title = LocalizedStringResource("Connect") + static var description = IntentDescription("Connect to the VPN") + + func perform() async throws -> some IntentResult { + log(.info, message: "App Intent handler: Connect") + NotificationCenter.default.post(name: Notification.Name.IntentConnect, object: nil) + return .result() + } +} + +@available(iOS 16, *) +struct Disconnect: AppIntent { + static var title = LocalizedStringResource("Disconnect") + static var description = IntentDescription("Disconnect from the VPN") + + func perform() async throws -> some IntentResult { + log(.info, message: "App Intent handler: Disconnect") + NotificationCenter.default.post(name: Notification.Name.IntentDisconnect, object: nil) + return .result() + } +} + @available(iOS 16, *) struct AntiTrackerEnable: AppIntent { static var title = LocalizedStringResource("Enable AntiTracker") @@ -31,7 +55,7 @@ struct AntiTrackerEnable: AppIntent { func perform() async throws -> some IntentResult { log(.info, message: "App Intent handler: EnableAntiTracker") - NotificationCenter.default.post(name: Notification.Name.IntentEnaableAntiTracker, object: nil) + NotificationCenter.default.post(name: Notification.Name.IntentAntiTrackerEnable, object: nil) return .result() } } @@ -43,7 +67,31 @@ struct AntiTrackerDisable: AppIntent { func perform() async throws -> some IntentResult { log(.info, message: "App Intent handler: DisableAntiTracker") - NotificationCenter.default.post(name: Notification.Name.IntentDisableAntiTracker, object: nil) + NotificationCenter.default.post(name: Notification.Name.IntentAntiTrackerDisable, object: nil) + return .result() + } +} + +@available(iOS 16, *) +struct CustomDNSEnable: AppIntent { + static var title = LocalizedStringResource("Enable Custom DNS") + static var description = IntentDescription("Enables the Custom DNS") + + func perform() async throws -> some IntentResult { + log(.info, message: "App Intent handler: EnableCustomDNS") + NotificationCenter.default.post(name: Notification.Name.IntentCustomDNSEnable, object: nil) + return .result() + } +} + +@available(iOS 16, *) +struct CustomDNSDisable: AppIntent { + static var title = LocalizedStringResource("Disable Custom DNS") + static var description = IntentDescription("Disables the Custom DNS") + + func perform() async throws -> some IntentResult { + log(.info, message: "App Intent handler: DisableCustomDNS") + NotificationCenter.default.post(name: Notification.Name.IntentCustomDNSDisable, object: nil) return .result() } } diff --git a/IVPNClient/Scenes/MainScreen/AppIntentsHandler.swift b/IVPNClient/Scenes/MainScreen/AppIntentsHandler.swift new file mode 100644 index 00000000..a9744dee --- /dev/null +++ b/IVPNClient/Scenes/MainScreen/AppIntentsHandler.swift @@ -0,0 +1,106 @@ +// +// AppIntentsHandler.swift +// IVPNClient +// +// Created by Juraj Hilje on 04.09.2024.. +// Copyright © 2024 IVPN. All rights reserved. +// + +extension MainViewController { + + // MARK: - App Intents - + + @objc func intentConnect() { + DispatchQueue.delay(0.75) { + if UserDefaults.shared.networkProtectionEnabled { + Application.shared.connectionManager.resetRulesAndConnectShortcut(closeApp: true, actionType: .connect) + return + } + Application.shared.connectionManager.connectShortcut(closeApp: true, actionType: .connect) + } + } + + @objc func intentDisconnect() { + DispatchQueue.delay(0.75) { + if UserDefaults.shared.networkProtectionEnabled { + Application.shared.connectionManager.resetRulesAndDisconnectShortcut(closeApp: true, actionType: .disconnect) + return + } + Application.shared.connectionManager.disconnectShortcut(closeApp: true, actionType: .disconnect) + } + } + + @objc func intentAntiTrackerEnable() { + DispatchQueue.async { + if let viewController = UIApplication.topViewController() { + if Application.shared.settings.connectionProtocol.tunnelType() == .ipsec { + viewController.showAlert(title: "IKEv2 not supported", message: "AntiTracker is supported only for OpenVPN and WireGuard protocols.") { _ in + } + return + } + + UserDefaults.shared.set(true, forKey: UserDefaults.Key.isAntiTracker) + NotificationCenter.default.post(name: Notification.Name.AntiTrackerUpdated, object: nil) + if UIApplication.topViewController() as? MainViewController != nil { + NotificationCenter.default.post(name: Notification.Name.EvaluateReconnect, object: nil) + } else { + viewController.evaluateReconnect(sender: viewController.view) + } + } + } + } + + @objc func intentAntiTrackerDisable() { + DispatchQueue.async { + if let viewController = UIApplication.topViewController() { + UserDefaults.shared.set(false, forKey: UserDefaults.Key.isAntiTracker) + NotificationCenter.default.post(name: Notification.Name.AntiTrackerUpdated, object: nil) + if UIApplication.topViewController() as? MainViewController != nil { + NotificationCenter.default.post(name: Notification.Name.EvaluateReconnect, object: nil) + } else { + viewController.evaluateReconnect(sender: viewController.view) + } + } + } + } + + @objc func intentCustomDNSEnable() { + DispatchQueue.async { + if let viewController = UIApplication.topViewController() { + if Application.shared.settings.connectionProtocol.tunnelType() == .ipsec { + viewController.showAlert(title: "IKEv2 not supported", message: "Custom DNS is supported only for OpenVPN and WireGuard protocols.") { _ in + } + return + } + + guard !UserDefaults.shared.customDNS.isEmpty else { + viewController.showAlert(title: "", message: "Please enter DNS server info") + return + } + + UserDefaults.shared.set(true, forKey: UserDefaults.Key.isCustomDNS) + NotificationCenter.default.post(name: Notification.Name.CustomDNSUpdated, object: nil) + if UIApplication.topViewController() as? MainViewController != nil { + NotificationCenter.default.post(name: Notification.Name.EvaluateReconnect, object: nil) + } else { + viewController.evaluateReconnect(sender: viewController.view) + } + } + } + } + + @objc func intentCustomDNSDisable() { + DispatchQueue.async { + if let viewController = UIApplication.topViewController() { + UserDefaults.shared.set(false, forKey: UserDefaults.Key.isCustomDNS) + NotificationCenter.default.post(name: Notification.Name.CustomDNSUpdated, object: nil) + if UIApplication.topViewController() as? MainViewController != nil { + NotificationCenter.default.post(name: Notification.Name.EvaluateReconnect, object: nil) + } else { + viewController.evaluateReconnect(sender: viewController.view) + } + } + } + } + +} diff --git a/IVPNClient/Scenes/MainScreen/MainViewController.swift b/IVPNClient/Scenes/MainScreen/MainViewController.swift index bb22e0b2..80e94f7f 100644 --- a/IVPNClient/Scenes/MainScreen/MainViewController.swift +++ b/IVPNClient/Scenes/MainScreen/MainViewController.swift @@ -198,29 +198,12 @@ class MainViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(vpnConfigurationDisabled), name: Notification.Name.VPNConfigurationDisabled, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(subscriptionActivated), name: Notification.Name.SubscriptionActivated, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(updateGeoLocation), name: Notification.Name.UpdateGeoLocation, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(intentConnect), name: Notification.Name.IntentConnect, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(intentDisconnect), name: Notification.Name.IntentDisconnect, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(intentAntiTrackerEnable), name: Notification.Name.IntentAntiTrackerEnable, object: nil) - } - - // MARK: - App Intents - - - @objc func intentAntiTrackerEnable() { - DispatchQueue.async { - if let viewController = UIApplication.topViewController() { - if Application.shared.settings.connectionProtocol.tunnelType() == .ipsec { - viewController.showAlert(title: "IKEv2 not supported", message: "AntiTracker is supported only for OpenVPN and WireGuard protocols.") { _ in - } - return - } - - UserDefaults.shared.set(true, forKey: UserDefaults.Key.isAntiTracker) - NotificationCenter.default.post(name: Notification.Name.AntiTrackerUpdated, object: nil) - if UIApplication.topViewController() as? MainViewController != nil { - NotificationCenter.default.post(name: Notification.Name.EvaluateReconnect, object: nil) - } else { - viewController.evaluateReconnect(sender: viewController.view) - } - } - } + NotificationCenter.default.addObserver(self, selector: #selector(intentAntiTrackerDisable), name: Notification.Name.IntentAntiTrackerDisable, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(intentCustomDNSEnable), name: Notification.Name.IntentCustomDNSEnable, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(intentCustomDNSDisable), name: Notification.Name.IntentCustomDNSDisable, object: nil) } // MARK: - Private methods -