]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
Reorganized ViewControllers (split out UIViews and UITableViewCells into their own...
authorEric Kuck <eric@bluelinelabs.com>
Thu, 13 Dec 2018 18:58:50 +0000 (12:58 -0600)
committerEric Kuck <eric@bluelinelabs.com>
Thu, 13 Dec 2018 18:58:50 +0000 (12:58 -0600)
All swiftlint warnings except one fixed up

Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
31 files changed:
WireGuard/.swiftlint.yml
WireGuard/WireGuard.xcodeproj/project.pbxproj
WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift
WireGuard/WireGuard/Tunnel/TunnelsManager.swift
WireGuard/WireGuard/UI/TunnelViewModel.swift
WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditButtonCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditKeyValueCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditReadOnlyKeyValueCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditSectionListCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditSwitchCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditTableViewController.swift [moved from WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift with 60% similarity]
WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift
WireGuard/WireGuard/UI/iOS/QRScanViewController.swift
WireGuard/WireGuard/UI/iOS/ScrollableLabel.swift
WireGuard/WireGuard/UI/iOS/Settings/SettingsButtonCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/Settings/SettingsKeyValueCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/Settings/SettingsTableViewController.swift [moved from WireGuard/WireGuard/UI/iOS/SettingsTableViewController.swift with 76% similarity]
WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailActivateOnDemandCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailButtonCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailKeyValueCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailStatusCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailTableViewController.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/TunnelList/BorderedTextButton.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/TunnelList/TunnelListCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/TunnelList/TunnelsListTableViewController.swift [moved from WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift with 57% similarity]
WireGuard/WireGuard/WireGuardAppError.swift
WireGuard/WireGuard/ZipArchive/ZipArchive.swift
WireGuard/WireGuard/ZipArchive/ZipExporter.swift
WireGuard/WireGuard/ZipArchive/ZipImporter.swift
WireGuard/WireGuardNetworkExtension/DNSResolver.swift
WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift

index 2c414b60fa85461a4dbc084e655c8bc9339a95b6..f0a450f1a400fa5b9289ae5e20bea95063e036da 100644 (file)
@@ -10,3 +10,5 @@ file_length:
 cyclomatic_complexity:
   warning: 10
   error: 25
+function_body_length:
+  warning: 45
index 4e6f6d874d622b628047bb67be167dec5c5dc826..53f25241b4c6db9ea80ffdd5e8ebeb9d40274c84 100644 (file)
@@ -8,6 +8,19 @@
 
 /* Begin PBXBuildFile section */
                5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */; };
+               5F45418A21C2D45B00994C13 /* TunnelEditKeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418921C2D45B00994C13 /* TunnelEditKeyValueCell.swift */; };
+               5F45418C21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418B21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift */; };
+               5F45418E21C2D51100994C13 /* TunnelEditButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418D21C2D51100994C13 /* TunnelEditButtonCell.swift */; };
+               5F45419021C2D53800994C13 /* TunnelEditSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418F21C2D53800994C13 /* TunnelEditSwitchCell.swift */; };
+               5F45419221C2D55800994C13 /* TunnelEditSectionListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419121C2D55800994C13 /* TunnelEditSectionListCell.swift */; };
+               5F45419421C2D5C500994C13 /* TunnelDetailActivateOnDemandCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419321C2D5C500994C13 /* TunnelDetailActivateOnDemandCell.swift */; };
+               5F45419621C2D5DB00994C13 /* TunnelDetailButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419521C2D5DB00994C13 /* TunnelDetailButtonCell.swift */; };
+               5F45419821C2D60500994C13 /* TunnelDetailKeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419721C2D60500994C13 /* TunnelDetailKeyValueCell.swift */; };
+               5F45419A21C2D61D00994C13 /* TunnelDetailStatusCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419921C2D61D00994C13 /* TunnelDetailStatusCell.swift */; };
+               5F45419C21C2D64800994C13 /* SettingsButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419B21C2D64800994C13 /* SettingsButtonCell.swift */; };
+               5F45419E21C2D66400994C13 /* SettingsKeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419D21C2D66400994C13 /* SettingsKeyValueCell.swift */; };
+               5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419F21C2D6B700994C13 /* TunnelListCell.swift */; };
+               5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */; };
                6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */; };
                6F0068572191AFD200419BE9 /* ScrollableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0068562191AFD200419BE9 /* ScrollableLabel.swift */; };
                6F5A2B4621AFDED40081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; };
 
 /* Begin PBXFileReference section */
                5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewCell+Reuse.swift"; sourceTree = "<group>"; };
+               5F45418921C2D45B00994C13 /* TunnelEditKeyValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditKeyValueCell.swift; sourceTree = "<group>"; };
+               5F45418B21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditReadOnlyKeyValueCell.swift; sourceTree = "<group>"; };
+               5F45418D21C2D51100994C13 /* TunnelEditButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditButtonCell.swift; sourceTree = "<group>"; };
+               5F45418F21C2D53800994C13 /* TunnelEditSwitchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditSwitchCell.swift; sourceTree = "<group>"; };
+               5F45419121C2D55800994C13 /* TunnelEditSectionListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditSectionListCell.swift; sourceTree = "<group>"; };
+               5F45419321C2D5C500994C13 /* TunnelDetailActivateOnDemandCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailActivateOnDemandCell.swift; sourceTree = "<group>"; };
+               5F45419521C2D5DB00994C13 /* TunnelDetailButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailButtonCell.swift; sourceTree = "<group>"; };
+               5F45419721C2D60500994C13 /* TunnelDetailKeyValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailKeyValueCell.swift; sourceTree = "<group>"; };
+               5F45419921C2D61D00994C13 /* TunnelDetailStatusCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailStatusCell.swift; sourceTree = "<group>"; };
+               5F45419B21C2D64800994C13 /* SettingsButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButtonCell.swift; sourceTree = "<group>"; };
+               5F45419D21C2D66400994C13 /* SettingsKeyValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyValueCell.swift; sourceTree = "<group>"; };
+               5F45419F21C2D6B700994C13 /* TunnelListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListCell.swift; sourceTree = "<group>"; };
+               5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BorderedTextButton.swift; sourceTree = "<group>"; };
                6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CopyableLabelTableViewCell.swift; sourceTree = "<group>"; };
                6F0068562191AFD200419BE9 /* ScrollableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableLabel.swift; sourceTree = "<group>"; };
                6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extension.swift"; sourceTree = "<group>"; };
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+               5F45418521C2C6AB00994C13 /* Settings */ = {
+                       isa = PBXGroup;
+                       children = (
+                               6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */,
+                               5F45419B21C2D64800994C13 /* SettingsButtonCell.swift */,
+                               5F45419D21C2D66400994C13 /* SettingsKeyValueCell.swift */,
+                       );
+                       path = Settings;
+                       sourceTree = "<group>";
+               };
+               5F45418621C2C6B400994C13 /* EditTunnel */ = {
+                       isa = PBXGroup;
+                       children = (
+                               6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */,
+                               5F45418921C2D45B00994C13 /* TunnelEditKeyValueCell.swift */,
+                               5F45418B21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift */,
+                               5F45418D21C2D51100994C13 /* TunnelEditButtonCell.swift */,
+                               5F45418F21C2D53800994C13 /* TunnelEditSwitchCell.swift */,
+                               5F45419121C2D55800994C13 /* TunnelEditSectionListCell.swift */,
+                       );
+                       path = EditTunnel;
+                       sourceTree = "<group>";
+               };
+               5F45418721C2C6C100994C13 /* TunnelDetail */ = {
+                       isa = PBXGroup;
+                       children = (
+                               6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */,
+                               5F45419321C2D5C500994C13 /* TunnelDetailActivateOnDemandCell.swift */,
+                               5F45419521C2D5DB00994C13 /* TunnelDetailButtonCell.swift */,
+                               5F45419721C2D60500994C13 /* TunnelDetailKeyValueCell.swift */,
+                               5F45419921C2D61D00994C13 /* TunnelDetailStatusCell.swift */,
+                       );
+                       path = TunnelDetail;
+                       sourceTree = "<group>";
+               };
+               5F45418821C2C6CC00994C13 /* TunnelList */ = {
+                       isa = PBXGroup;
+                       children = (
+                               6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */,
+                               5F45419F21C2D6B700994C13 /* TunnelListCell.swift */,
+                               5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */,
+                       );
+                       path = TunnelList;
+                       sourceTree = "<group>";
+               };
                6F5D0C1B218352EF000F85AD /* WireGuardNetworkExtension */ = {
                        isa = PBXGroup;
                        children = (
                6F7774DE217181B1006A79B3 /* iOS */ = {
                        isa = PBXGroup;
                        children = (
+                               5F45418821C2C6CC00994C13 /* TunnelList */,
+                               5F45418721C2C6C100994C13 /* TunnelDetail */,
+                               5F45418621C2C6B400994C13 /* EditTunnel */,
+                               5F45418521C2C6AB00994C13 /* Settings */,
                                6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */,
                                6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */,
                                6F7774E0217181B1006A79B3 /* AppDelegate.swift */,
                                6F7774DF217181B1006A79B3 /* MainViewController.swift */,
-                               6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */,
-                               6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */,
-                               6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */,
-                               6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */,
                                6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */,
                                6F0068562191AFD200419BE9 /* ScrollableLabel.swift */,
                                5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */,
                                6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */,
                                6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */,
                                5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */,
+                               5F45419221C2D55800994C13 /* TunnelEditSectionListCell.swift in Sources */,
                                6FE254FF219C60290028284D /* ZipExporter.swift in Sources */,
                                6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
+                               5F45418E21C2D51100994C13 /* TunnelEditButtonCell.swift in Sources */,
                                6F0068572191AFD200419BE9 /* ScrollableLabel.swift in Sources */,
                                6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
                                6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
                                6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */,
+                               5F45418A21C2D45B00994C13 /* TunnelEditKeyValueCell.swift in Sources */,
                                6F6899A62180447E0012E523 /* x25519.c in Sources */,
                                6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */,
                                6FDEF80021863C0100D8FBF6 /* ioapi.c in Sources */,
                                6FDEF7FC21863B6100D8FBF6 /* zip.c in Sources */,
                                6F628C3F217F3413003482A3 /* DNSServer.swift in Sources */,
+                               5F45419C21C2D64800994C13 /* SettingsButtonCell.swift in Sources */,
                                6F628C3D217F09E9003482A3 /* TunnelViewModel.swift in Sources */,
+                               5F45419821C2D60500994C13 /* TunnelDetailKeyValueCell.swift in Sources */,
                                6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */,
                                6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */,
+                               5F45418C21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift in Sources */,
                                6FDEF8082187442100D8FBF6 /* WgQuickConfigFileWriter.swift in Sources */,
                                6FE254FB219C10800028284D /* ZipImporter.swift in Sources */,
+                               5F45419A21C2D61D00994C13 /* TunnelDetailStatusCell.swift in Sources */,
                                6F7774EA217229DB006A79B3 /* IPAddressRange.swift in Sources */,
+                               5F45419621C2D5DB00994C13 /* TunnelDetailButtonCell.swift in Sources */,
                                6F7774E82172020C006A79B3 /* Configuration.swift in Sources */,
                                6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */,
+                               5F45419E21C2D66400994C13 /* SettingsKeyValueCell.swift in Sources */,
                                6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */,
+                               5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */,
                                6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */,
                                6F61F1EB21B937EF00483816 /* WireGuardResult.swift in Sources */,
                                6F7774F321774263006A79B3 /* TunnelEditTableViewController.swift in Sources */,
                                6FDEF802218646BA00D8FBF6 /* ZipArchive.swift in Sources */,
+                               5F45419021C2D53800994C13 /* TunnelEditSwitchCell.swift in Sources */,
                                6FDEF806218725D200D8FBF6 /* SettingsTableViewController.swift in Sources */,
+                               5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */,
                                6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
                                6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */,
+                               5F45419421C2D5C500994C13 /* TunnelDetailActivateOnDemandCell.swift in Sources */,
+                               6FF717E521B2CB1E0045A474 /* InternetReachability.swift in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index a87472061fa9906d8cb9e8dd4eb52b458cf94a7c..b7239e5ca9b5eaf9e6cd616ffef9cec99cf43220 100644 (file)
@@ -20,6 +20,7 @@ class WgQuickConfigFileParser {
         case invalidPeer
     }
 
+    //swiftlint:disable:next cyclomatic_complexity function_body_length
     static func parse(_ text: String, name: String) throws -> TunnelConfiguration {
         assert(!name.isEmpty)
 
@@ -95,6 +96,7 @@ class WgQuickConfigFileParser {
         }
     }
 
+    //swiftlint:disable:next cyclomatic_complexity
     private static func collate(interfaceAttributes attributes: [String: String], name: String) -> InterfaceConfiguration? {
         // required wg fields
         guard let privateKeyString = attributes["privatekey"] else { return nil }
@@ -131,6 +133,7 @@ class WgQuickConfigFileParser {
         return interface
     }
 
+    //swiftlint:disable:next cyclomatic_complexity
     private static func collate(peerAttributes attributes: [String: String]) -> PeerConfiguration? {
         // required wg fields
         guard let publicKeyString = attributes["publickey"] else { return nil }
index a968419e9f97965dc8fabbd6c22ba8836e5e3ec1..64f694bad57f027757cc60cd6febf223794280e4 100644 (file)
@@ -27,7 +27,7 @@ enum TunnelsManagerActivationAttemptError: WireGuardAppError {
     case failedWhileLoading // reloading config throwed
     case failedBecauseOfTooManyErrors // recursion limit reached
 
-    func alertText() -> AlertText {
+    var alertText: AlertText {
         switch self {
         case .tunnelIsNotInactive:
             return ("Activation failure", "The tunnel is already active or in the process of being activated")
@@ -41,13 +41,12 @@ enum TunnelsManagerActivationAttemptError: WireGuardAppError {
 
 enum TunnelsManagerActivationError: WireGuardAppError {
     case activationFailed
-    func alertText() -> AlertText {
+    var alertText: AlertText {
         return ("Activation failure", "The tunnel could not be activated. Please ensure you are connected to the Internet.")
     }
 }
 
 enum TunnelsManagerError: WireGuardAppError {
-    // Tunnels list management
     case tunnelNameEmpty
     case tunnelAlreadyExistsWithThatName
     case systemErrorOnListingTunnels
@@ -55,7 +54,7 @@ enum TunnelsManagerError: WireGuardAppError {
     case systemErrorOnModifyTunnel
     case systemErrorOnRemoveTunnel
 
-    func alertText() -> AlertText {
+    var alertText: AlertText {
         switch self {
         case .tunnelNameEmpty:
             return ("No name provided", "Can't create tunnel with an empty name")
@@ -87,7 +86,6 @@ class TunnelsManager {
 
     static func create(completionHandler: @escaping (WireGuardResult<TunnelsManager>) -> Void) {
         #if targetEnvironment(simulator)
-        // NETunnelProviderManager APIs don't work on the simulator
         completionHandler(.success(TunnelsManager(tunnelProviders: [])))
         #else
         NETunnelProviderManager.loadAllFromPreferences { managers, error in
@@ -101,9 +99,7 @@ class TunnelsManager {
         #endif
     }
 
-    func add(tunnelConfiguration: TunnelConfiguration,
-             activateOnDemandSetting: ActivateOnDemandSetting = ActivateOnDemandSetting.defaultSetting,
-             completionHandler: @escaping (WireGuardResult<TunnelContainer>) -> Void) {
+    func add(tunnelConfiguration: TunnelConfiguration, activateOnDemandSetting: ActivateOnDemandSetting = ActivateOnDemandSetting.defaultSetting, completionHandler: @escaping (WireGuardResult<TunnelContainer>) -> Void) {
         let tunnelName = tunnelConfiguration.interface.name
         if tunnelName.isEmpty {
             completionHandler(.failure(TunnelsManagerError.tunnelNameEmpty))
@@ -128,13 +124,14 @@ class TunnelsManager {
                 completionHandler(.failure(TunnelsManagerError.systemErrorOnAddTunnel))
                 return
             }
-            if let self = self {
-                let tunnel = TunnelContainer(tunnel: tunnelProviderManager)
-                self.tunnels.append(tunnel)
-                self.tunnels.sort { $0.name < $1.name }
-                self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!)
-                completionHandler(.success(tunnel))
-            }
+            
+            guard let self = self else { return }
+            
+            let tunnel = TunnelContainer(tunnel: tunnelProviderManager)
+            self.tunnels.append(tunnel)
+            self.tunnels.sort { $0.name < $1.name }
+            self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!)
+            completionHandler(.success(tunnel))
         }
     }
 
@@ -155,8 +152,7 @@ class TunnelsManager {
         }
     }
 
-    func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration,
-                activateOnDemandSetting: ActivateOnDemandSetting, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
+    func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration, activateOnDemandSetting: ActivateOnDemandSetting, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
         let tunnelName = tunnelConfiguration.interface.name
         if tunnelName.isEmpty {
             completionHandler(TunnelsManagerError.tunnelNameEmpty)
@@ -185,36 +181,37 @@ class TunnelsManager {
                 completionHandler(TunnelsManagerError.systemErrorOnModifyTunnel)
                 return
             }
-            if let self = self {
-                if isNameChanged {
-                    let oldIndex = self.tunnels.firstIndex(of: tunnel)!
-                    self.tunnels.sort { $0.name < $1.name }
-                    let newIndex = self.tunnels.firstIndex(of: tunnel)!
-                    self.tunnelsListDelegate?.tunnelMoved(from: oldIndex, to: newIndex)
-                }
-                self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!)
-
-                if tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting {
-                    // Turn off the tunnel, and then turn it back on, so the changes are made effective
-                    tunnel.status = .restarting
-                    (tunnel.tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel()
-                }
-
-                if isActivatingOnDemand {
-                    // Reload tunnel after saving.
-                    // Without this, the tunnel stopes getting updates on the tunnel status from iOS.
-                    tunnelProviderManager.loadFromPreferences { error in
-                        tunnel.isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled
-                        guard error == nil else {
-                            wg_log(.error, message: "Modify: Re-loading after saving configuration failed: \(error!)")
-                            completionHandler(TunnelsManagerError.systemErrorOnModifyTunnel)
-                            return
-                        }
-                        completionHandler(nil)
+            
+            guard let self = self else { return }
+            
+            if isNameChanged {
+                let oldIndex = self.tunnels.firstIndex(of: tunnel)!
+                self.tunnels.sort { $0.name < $1.name }
+                let newIndex = self.tunnels.firstIndex(of: tunnel)!
+                self.tunnelsListDelegate?.tunnelMoved(from: oldIndex, to: newIndex)
+            }
+            self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!)
+            
+            if tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting {
+                // Turn off the tunnel, and then turn it back on, so the changes are made effective
+                tunnel.status = .restarting
+                (tunnel.tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel()
+            }
+            
+            if isActivatingOnDemand {
+                // Reload tunnel after saving.
+                // Without this, the tunnel stopes getting updates on the tunnel status from iOS.
+                tunnelProviderManager.loadFromPreferences { error in
+                    tunnel.isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled
+                    guard error == nil else {
+                        wg_log(.error, message: "Modify: Re-loading after saving configuration failed: \(error!)")
+                        completionHandler(TunnelsManagerError.systemErrorOnModifyTunnel)
+                        return
                     }
-                } else {
                     completionHandler(nil)
                 }
+            } else {
+                completionHandler(nil)
             }
         }
     }
@@ -274,9 +271,7 @@ class TunnelsManager {
 
     func startDeactivation(of tunnel: TunnelContainer) {
         tunnel.isAttemptingActivation = false
-        if tunnel.status == .inactive || tunnel.status == .deactivating {
-            return
-        }
+        guard tunnel.status != .inactive && tunnel.status != .deactivating else { return }
         tunnel.startDeactivation()
     }
 
@@ -288,10 +283,10 @@ class TunnelsManager {
         guard statusObservationToken == nil else { return }
 
         statusObservationToken = NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: OperationQueue.main) { [weak self] statusChangeNotification in
-            guard let self = self else { return }
-            guard let session = statusChangeNotification.object as? NETunnelProviderSession else { return }
-            guard let tunnelProvider = session.manager as? NETunnelProviderManager else { return }
-            guard let tunnel = self.tunnels.first(where: { $0.tunnelProvider == tunnelProvider }) else { return }
+            guard let self = self,
+                let session = statusChangeNotification.object as? NETunnelProviderSession,
+                let tunnelProvider = session.manager as? NETunnelProviderManager,
+                let tunnel = self.tunnels.first(where: { $0.tunnelProvider == tunnelProvider }) else { return }
 
             wg_log(.debug, message: "Tunnel '\(tunnel.name)' connection status changed to '\(tunnel.tunnelProvider.connection.status)'")
 
@@ -339,7 +334,7 @@ class TunnelContainer: NSObject {
 
     @objc dynamic var isActivateOnDemandEnabled: Bool
 
-    var isAttemptingActivation: Bool = false
+    var isAttemptingActivation = false
 
     fileprivate let tunnelProvider: NETunnelProviderManager
     private var lastTunnelConnectionStatus: NEVPNStatus?
@@ -375,10 +370,8 @@ class TunnelContainer: NSObject {
         startActivation(tunnelConfiguration: tunnelConfiguration, activationDelegate: activationDelegate)
     }
 
-    fileprivate func startActivation(recursionCount: UInt = 0,
-                                     lastError: Error? = nil,
-                                     tunnelConfiguration: TunnelConfiguration,
-                                     activationDelegate: TunnelsManagerActivationDelegate?) {
+    //swiftlint:disable:next function_body_length
+    fileprivate func startActivation(recursionCount: UInt = 0, lastError: Error? = nil, tunnelConfiguration: TunnelConfiguration, activationDelegate: TunnelsManagerActivationDelegate?) {
         if recursionCount >= 8 {
             wg_log(.error, message: "startActivation: Failed after 8 attempts. Giving up with \(lastError!)")
             activationDelegate?.tunnelActivationAttemptFailed(tunnel: self, error: .failedBecauseOfTooManyErrors)
@@ -457,7 +450,6 @@ class TunnelContainer: NSObject {
     case active
     case deactivating
     case reasserting // Not a possible state at present
-
     case restarting // Restarting tunnel (done after saving modifications to an active tunnel)
     case waiting    // Waiting for another tunnel to be brought down
 
index 80a6092b23d0d34d97fb115eb486de52508692f4..c9b1cf14806cbd20a469b4d3dac3948bf3c10937 100644 (file)
@@ -95,6 +95,7 @@ class TunnelViewModel {
             }
         }
 
+        //swiftlint:disable:next cyclomatic_complexity function_body_length
         func save() -> SaveResult<InterfaceConfiguration> {
             if let validatedConfiguration = validatedConfiguration {
                 // It's already validated and saved
@@ -236,6 +237,7 @@ class TunnelViewModel {
             updateExcludePrivateIPsFieldState()
         }
 
+        //swiftlint:disable:next cyclomatic_complexity
         func save() -> SaveResult<PeerConfiguration> {
             if let validatedConfiguration = validatedConfiguration {
                 // It's already validated and saved
diff --git a/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditButtonCell.swift b/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditButtonCell.swift
new file mode 100644 (file)
index 0000000..af70183
--- /dev/null
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class TunnelEditButtonCell: UITableViewCell {
+    var buttonText: String {
+        get { return button.title(for: .normal) ?? "" }
+        set(value) { button.setTitle(value, for: .normal) }
+    }
+    var hasDestructiveAction: Bool {
+        get { return button.tintColor == UIColor.red }
+        set(value) { button.tintColor = value ? UIColor.red : buttonStandardTintColor }
+    }
+    var onTapped: (() -> Void)?
+    
+    let button: UIButton = {
+        let button = UIButton(type: .system)
+        button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
+        button.titleLabel?.adjustsFontForContentSizeCategory = true
+        return button
+    }()
+    
+    var buttonStandardTintColor: UIColor
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        buttonStandardTintColor = button.tintColor
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        
+        contentView.addSubview(button)
+        button.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            button.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
+            contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: button.bottomAnchor),
+            button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
+            ])
+        
+        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
+    }
+    
+    @objc func buttonTapped() {
+        onTapped?()
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        buttonText = ""
+        onTapped = nil
+        hasDestructiveAction = false
+    }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditKeyValueCell.swift b/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditKeyValueCell.swift
new file mode 100644 (file)
index 0000000..432d75b
--- /dev/null
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class TunnelEditKeyValueCell: UITableViewCell {
+    var key: String {
+        get { return keyLabel.text ?? "" }
+        set(value) {keyLabel.text = value }
+    }
+    var value: String {
+        get { return valueTextField.text ?? "" }
+        set(value) { valueTextField.text = value }
+    }
+    var placeholderText: String {
+        get { return valueTextField.placeholder ?? "" }
+        set(value) { valueTextField.placeholder = value }
+    }
+    var isValueValid = true {
+        didSet {
+            if isValueValid {
+                keyLabel.textColor = .black
+            } else {
+                keyLabel.textColor = .red
+            }
+        }
+    }
+    var keyboardType: UIKeyboardType {
+        get { return valueTextField.keyboardType }
+        set(value) { valueTextField.keyboardType = value }
+    }
+    
+    var onValueChanged: ((String) -> Void)?
+    var onValueBeingEdited: ((String) -> Void)?
+    
+    let keyLabel: UILabel = {
+        let keyLabel = UILabel()
+        keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
+        keyLabel.adjustsFontForContentSizeCategory = true
+        return keyLabel
+    }()
+    
+    let valueTextField: UITextField = {
+        let valueTextField = UITextField()
+        valueTextField.font = UIFont.preferredFont(forTextStyle: .body)
+        valueTextField.adjustsFontForContentSizeCategory = true
+        valueTextField.autocapitalizationType = .none
+        valueTextField.autocorrectionType = .no
+        valueTextField.spellCheckingType = .no
+        return valueTextField
+    }()
+    
+    var isStackedHorizontally = false
+    var isStackedVertically = false
+    var contentSizeBasedConstraints = [NSLayoutConstraint]()
+    
+    private var textFieldValueOnBeginEditing: String = ""
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        
+        contentView.addSubview(keyLabel)
+        keyLabel.translatesAutoresizingMaskIntoConstraints = false
+        keyLabel.textAlignment = .right
+        let widthRatioConstraint = NSLayoutConstraint(item: keyLabel, attribute: .width,
+                                                      relatedBy: .equal,
+                                                      toItem: self, attribute: .width,
+                                                      multiplier: 0.4, constant: 0)
+        // The "Persistent Keepalive" key doesn't fit into 0.4 * width on the iPhone SE,
+        // so set a CR priority > the 0.4-constraint's priority.
+        widthRatioConstraint.priority = .defaultHigh + 1
+        keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
+        NSLayoutConstraint.activate([
+            keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
+            keyLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5),
+            widthRatioConstraint
+            ])
+        
+        contentView.addSubview(valueTextField)
+        valueTextField.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            valueTextField.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor),
+            contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueTextField.bottomAnchor, multiplier: 0.5)
+            ])
+        valueTextField.delegate = self
+        
+        configureForContentSize()
+    }
+    
+    func configureForContentSize() {
+        var constraints = [NSLayoutConstraint]()
+        if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
+            // Stack vertically
+            if !isStackedVertically {
+                constraints = [
+                    valueTextField.topAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
+                    valueTextField.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
+                    keyLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor)
+                ]
+                isStackedVertically = true
+                isStackedHorizontally = false
+            }
+        } else {
+            // Stack horizontally
+            if !isStackedHorizontally {
+                constraints = [
+                    contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
+                    valueTextField.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
+                    valueTextField.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
+                ]
+                isStackedHorizontally = true
+                isStackedVertically = false
+            }
+        }
+        if !constraints.isEmpty {
+            NSLayoutConstraint.deactivate(self.contentSizeBasedConstraints)
+            NSLayoutConstraint.activate(constraints)
+            self.contentSizeBasedConstraints = constraints
+        }
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        key = ""
+        value = ""
+        placeholderText = ""
+        isValueValid = true
+        keyboardType = .default
+        onValueChanged = nil
+        onValueBeingEdited = nil
+        configureForContentSize()
+    }
+}
+
+extension TunnelEditKeyValueCell: UITextFieldDelegate {
+    func textFieldDidBeginEditing(_ textField: UITextField) {
+        textFieldValueOnBeginEditing = textField.text ?? ""
+        isValueValid = true
+    }
+    func textFieldDidEndEditing(_ textField: UITextField) {
+        let isModified = (textField.text ?? "" != textFieldValueOnBeginEditing)
+        guard isModified else { return }
+        if let onValueChanged = onValueChanged {
+            onValueChanged(textField.text ?? "")
+        }
+    }
+    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+        if let onValueBeingEdited = onValueBeingEdited {
+            let modifiedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
+            onValueBeingEdited(modifiedText)
+        }
+        return true
+    }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditReadOnlyKeyValueCell.swift b/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditReadOnlyKeyValueCell.swift
new file mode 100644 (file)
index 0000000..48c8798
--- /dev/null
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class TunnelEditReadOnlyKeyValueCell: CopyableLabelTableViewCell {
+    var key: String {
+        get { return keyLabel.text ?? "" }
+        set(value) {keyLabel.text = value }
+    }
+    var value: String {
+        get { return valueLabel.text }
+        set(value) { valueLabel.text = value }
+    }
+    
+    let keyLabel: UILabel
+    let valueLabel: ScrollableLabel
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        keyLabel = UILabel()
+        keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
+        keyLabel.adjustsFontForContentSizeCategory = true
+        valueLabel = ScrollableLabel()
+        valueLabel.label.font = UIFont.preferredFont(forTextStyle: .body)
+        valueLabel.label.adjustsFontForContentSizeCategory = true
+        
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        
+        keyLabel.textColor = UIColor.gray
+        valueLabel.textColor = UIColor.gray
+        
+        contentView.addSubview(keyLabel)
+        keyLabel.translatesAutoresizingMaskIntoConstraints = false
+        keyLabel.textAlignment = .right
+        let widthRatioConstraint = NSLayoutConstraint(item: keyLabel, attribute: .width,
+                                                      relatedBy: .equal,
+                                                      toItem: self, attribute: .width,
+                                                      multiplier: 0.4, constant: 0)
+        // In case the key doesn't fit into 0.4 * width,
+        // so set a CR priority > the 0.4-constraint's priority.
+        widthRatioConstraint.priority = .defaultHigh + 1
+        keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
+        NSLayoutConstraint.activate([
+            keyLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+            keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
+            widthRatioConstraint
+            ])
+        
+        contentView.addSubview(valueLabel)
+        valueLabel.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            valueLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+            valueLabel.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
+            valueLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor)
+            ])
+    }
+    
+    override var textToCopy: String? {
+        return self.valueLabel.text
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        key = ""
+        value = ""
+    }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditSectionListCell.swift b/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditSectionListCell.swift
new file mode 100644 (file)
index 0000000..ca0352e
--- /dev/null
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class TunnelEditSelectionListCell: UITableViewCell {
+    var message: String {
+        get { return textLabel?.text ?? "" }
+        set(value) { textLabel!.text = value }
+    }
+    var isChecked: Bool {
+        didSet {
+            accessoryType = isChecked ? .checkmark : .none
+        }
+    }
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        isChecked = false
+        super.init(style: .default, reuseIdentifier: reuseIdentifier)
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        message = ""
+        isChecked = false
+    }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditSwitchCell.swift b/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditSwitchCell.swift
new file mode 100644 (file)
index 0000000..658fb95
--- /dev/null
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class TunnelEditSwitchCell: UITableViewCell {
+    var message: String {
+        get { return textLabel?.text ?? "" }
+        set(value) { textLabel!.text = value }
+    }
+    var isOn: Bool {
+        get { return switchView.isOn }
+        set(value) { switchView.isOn = value }
+    }
+    var isEnabled: Bool {
+        get { return switchView.isEnabled }
+        set(value) {
+            switchView.isEnabled = value
+            textLabel?.textColor = value ? UIColor.black : UIColor.gray
+        }
+    }
+    
+    var onSwitchToggled: ((Bool) -> Void)?
+    
+    let switchView: UISwitch
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        switchView = UISwitch()
+        super.init(style: .default, reuseIdentifier: reuseIdentifier)
+        accessoryView = switchView
+        switchView.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
+    }
+    
+    @objc func switchToggled() {
+        onSwitchToggled?(switchView.isOn)
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        message = ""
+        isOn = false
+    }
+}
similarity index 60%
rename from WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift
rename to WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditTableViewController.swift
index 0386b0aa77d660f8b40876ad41b0fe5edda8b57b..8d055d235af81141becac3142a751815463a0efa 100644 (file)
@@ -79,11 +79,11 @@ class TunnelEditTableViewController: UITableViewController {
         self.tableView.estimatedRowHeight = 44
         self.tableView.rowHeight = UITableView.automaticDimension
 
-        self.tableView.register(KeyValueCell.self)
-        self.tableView.register(ReadOnlyKeyValueCell.self)
-        self.tableView.register(ButtonCell.self)
-        self.tableView.register(SwitchCell.self)
-        self.tableView.register(SelectionListCell.self)
+        self.tableView.register(TunnelEditKeyValueCell.self)
+        self.tableView.register(TunnelEditReadOnlyKeyValueCell.self)
+        self.tableView.register(TunnelEditButtonCell.self)
+        self.tableView.register(TunnelEditSwitchCell.self)
+        self.tableView.register(TunnelEditSelectionListCell.self)
     }
 
     private func loadSections() {
@@ -201,7 +201,7 @@ extension TunnelEditTableViewController {
     }
 
     private func generateKeyPairCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell {
-        let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
+        let cell: TunnelEditButtonCell = tableView.dequeueReusableCell(for: indexPath)
         cell.buttonText = field.rawValue
         cell.onTapped = { [weak self] in
             guard let self = self else { return }
@@ -218,14 +218,14 @@ extension TunnelEditTableViewController {
     }
 
     private func publicKeyCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell {
-        let cell: ReadOnlyKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
+        let cell: TunnelEditReadOnlyKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
         cell.key = field.rawValue
         cell.value = tunnelViewModel.interfaceData[field]
         return cell
     }
 
     private func interfaceFieldKeyValueCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell {
-        let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
+        let cell: TunnelEditKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
         cell.key = field.rawValue
 
         switch field {
@@ -287,7 +287,7 @@ extension TunnelEditTableViewController {
     }
 
     private func deletePeerCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell {
-        let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
+        let cell: TunnelEditButtonCell = tableView.dequeueReusableCell(for: indexPath)
         cell.buttonText = field.rawValue
         cell.hasDestructiveAction = true
         cell.onTapped = { [weak self, weak peerData] in
@@ -313,7 +313,7 @@ extension TunnelEditTableViewController {
     }
 
     private func excludePrivateIPsCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell {
-        let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
+        let cell: TunnelEditSwitchCell = tableView.dequeueReusableCell(for: indexPath)
         cell.message = field.rawValue
         cell.isEnabled = peerData.shouldAllowExcludePrivateIPsControl
         cell.isOn = peerData.excludePrivateIPsValue
@@ -328,7 +328,7 @@ extension TunnelEditTableViewController {
     }
 
     private func peerFieldKeyValueCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell {
-        let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
+        let cell: TunnelEditKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
         cell.key = field.rawValue
 
         switch field {
@@ -377,7 +377,7 @@ extension TunnelEditTableViewController {
     }
 
     private func addPeerCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
-        let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
+        let cell: TunnelEditButtonCell = tableView.dequeueReusableCell(for: indexPath)
         cell.buttonText = "Add peer"
         cell.onTapped = { [weak self] in
             guard let self = self else { return }
@@ -398,7 +398,7 @@ extension TunnelEditTableViewController {
 
     private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
         if indexPath.row == 0 {
-            let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
+            let cell: TunnelEditSwitchCell = tableView.dequeueReusableCell(for: indexPath)
             cell.message = "Activate on demand"
             cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled
             cell.onSwitchToggled = { [weak self] isOn in
@@ -419,7 +419,7 @@ extension TunnelEditTableViewController {
             }
             return cell
         } else {
-            let cell: SelectionListCell = tableView.dequeueReusableCell(for: indexPath)
+            let cell: TunnelEditSelectionListCell = tableView.dequeueReusableCell(for: indexPath)
             let rowOption = activateOnDemandOptions[indexPath.row - 1]
             let selectedOption = activateOnDemandSetting.activateOnDemandOption
             assert(selectedOption != .none)
@@ -486,333 +486,3 @@ extension TunnelEditTableViewController {
         }
     }
 }
-
-private class KeyValueCell: UITableViewCell {
-    var key: String {
-        get { return keyLabel.text ?? "" }
-        set(value) {keyLabel.text = value }
-    }
-    var value: String {
-        get { return valueTextField.text ?? "" }
-        set(value) { valueTextField.text = value }
-    }
-    var placeholderText: String {
-        get { return valueTextField.placeholder ?? "" }
-        set(value) { valueTextField.placeholder = value }
-    }
-    var isValueValid: Bool = true {
-        didSet {
-            if isValueValid {
-                keyLabel.textColor = UIColor.black
-            } else {
-                keyLabel.textColor = UIColor.red
-            }
-        }
-    }
-    var keyboardType: UIKeyboardType {
-        get { return valueTextField.keyboardType }
-        set(value) { valueTextField.keyboardType = value }
-    }
-
-    var onValueChanged: ((String) -> Void)?
-    var onValueBeingEdited: ((String) -> Void)?
-
-    let keyLabel: UILabel
-    let valueTextField: UITextField
-
-    var isStackedHorizontally: Bool = false
-    var isStackedVertically: Bool = false
-    var contentSizeBasedConstraints = [NSLayoutConstraint]()
-
-    private var textFieldValueOnBeginEditing: String = ""
-
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        keyLabel = UILabel()
-        keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
-        keyLabel.adjustsFontForContentSizeCategory = true
-        valueTextField = UITextField()
-        valueTextField.font = UIFont.preferredFont(forTextStyle: .body)
-        valueTextField.adjustsFontForContentSizeCategory = true
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
-        contentView.addSubview(keyLabel)
-        keyLabel.translatesAutoresizingMaskIntoConstraints = false
-        keyLabel.textAlignment = .right
-        let widthRatioConstraint = NSLayoutConstraint(item: keyLabel, attribute: .width,
-                                                      relatedBy: .equal,
-                                                      toItem: self, attribute: .width,
-                                                      multiplier: 0.4, constant: 0)
-        // The "Persistent Keepalive" key doesn't fit into 0.4 * width on the iPhone SE,
-        // so set a CR priority > the 0.4-constraint's priority.
-        widthRatioConstraint.priority = .defaultHigh + 1
-        keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
-        NSLayoutConstraint.activate([
-            keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
-            keyLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5),
-            widthRatioConstraint
-        ])
-        contentView.addSubview(valueTextField)
-        valueTextField.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            valueTextField.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor),
-            contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueTextField.bottomAnchor, multiplier: 0.5)
-        ])
-        valueTextField.delegate = self
-
-        valueTextField.autocapitalizationType = .none
-        valueTextField.autocorrectionType = .no
-        valueTextField.spellCheckingType = .no
-
-        configureForContentSize()
-    }
-
-    func configureForContentSize() {
-        var constraints = [NSLayoutConstraint]()
-        if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
-            // Stack vertically
-            if !isStackedVertically {
-                constraints = [
-                    valueTextField.topAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
-                    valueTextField.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
-                    keyLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor)
-                ]
-                isStackedVertically = true
-                isStackedHorizontally = false
-            }
-        } else {
-            // Stack horizontally
-            if !isStackedHorizontally {
-                constraints = [
-                    contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
-                    valueTextField.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
-                    valueTextField.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
-                ]
-                isStackedHorizontally = true
-                isStackedVertically = false
-            }
-        }
-        if !constraints.isEmpty {
-            NSLayoutConstraint.deactivate(self.contentSizeBasedConstraints)
-            NSLayoutConstraint.activate(constraints)
-            self.contentSizeBasedConstraints = constraints
-        }
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        key = ""
-        value = ""
-        placeholderText = ""
-        isValueValid = true
-        keyboardType = .default
-        onValueChanged = nil
-        onValueBeingEdited = nil
-        configureForContentSize()
-    }
-}
-
-extension KeyValueCell: UITextFieldDelegate {
-    func textFieldDidBeginEditing(_ textField: UITextField) {
-        textFieldValueOnBeginEditing = textField.text ?? ""
-        isValueValid = true
-    }
-    func textFieldDidEndEditing(_ textField: UITextField) {
-        let isModified = (textField.text ?? "" != textFieldValueOnBeginEditing)
-        guard isModified else { return }
-        if let onValueChanged = onValueChanged {
-            onValueChanged(textField.text ?? "")
-        }
-    }
-    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
-        if let onValueBeingEdited = onValueBeingEdited {
-            let modifiedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
-            onValueBeingEdited(modifiedText)
-        }
-        return true
-    }
-}
-
-private class ReadOnlyKeyValueCell: CopyableLabelTableViewCell {
-    var key: String {
-        get { return keyLabel.text ?? "" }
-        set(value) {keyLabel.text = value }
-    }
-    var value: String {
-        get { return valueLabel.text }
-        set(value) { valueLabel.text = value }
-    }
-
-    let keyLabel: UILabel
-    let valueLabel: ScrollableLabel
-
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        keyLabel = UILabel()
-        keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
-        keyLabel.adjustsFontForContentSizeCategory = true
-        valueLabel = ScrollableLabel()
-        valueLabel.label.font = UIFont.preferredFont(forTextStyle: .body)
-        valueLabel.label.adjustsFontForContentSizeCategory = true
-
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
-
-        keyLabel.textColor = UIColor.gray
-        valueLabel.textColor = UIColor.gray
-
-        contentView.addSubview(keyLabel)
-        keyLabel.translatesAutoresizingMaskIntoConstraints = false
-        keyLabel.textAlignment = .right
-        let widthRatioConstraint = NSLayoutConstraint(item: keyLabel, attribute: .width,
-                                                      relatedBy: .equal,
-                                                      toItem: self, attribute: .width,
-                                                      multiplier: 0.4, constant: 0)
-        // In case the key doesn't fit into 0.4 * width,
-        // so set a CR priority > the 0.4-constraint's priority.
-        widthRatioConstraint.priority = .defaultHigh + 1
-        keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
-        NSLayoutConstraint.activate([
-            keyLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
-            keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
-            widthRatioConstraint
-        ])
-
-        contentView.addSubview(valueLabel)
-        valueLabel.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            valueLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
-            valueLabel.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
-            valueLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor)
-        ])
-    }
-
-    override var textToCopy: String? {
-        return self.valueLabel.text
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        key = ""
-        value = ""
-    }
-}
-
-private class ButtonCell: UITableViewCell {
-    var buttonText: String {
-        get { return button.title(for: .normal) ?? "" }
-        set(value) { button.setTitle(value, for: .normal) }
-    }
-    var hasDestructiveAction: Bool {
-        get { return button.tintColor == UIColor.red }
-        set(value) { button.tintColor = value ? UIColor.red : buttonStandardTintColor }
-    }
-    var onTapped: (() -> Void)?
-
-    let button: UIButton
-    var buttonStandardTintColor: UIColor
-
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        button = UIButton(type: .system)
-        button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
-        button.titleLabel?.adjustsFontForContentSizeCategory = true
-        buttonStandardTintColor = button.tintColor
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
-        contentView.addSubview(button)
-        button.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            button.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
-            contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: button.bottomAnchor),
-            button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
-        ])
-        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
-    }
-
-    @objc func buttonTapped() {
-        onTapped?()
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        buttonText = ""
-        onTapped = nil
-        hasDestructiveAction = false
-    }
-}
-
-private class SwitchCell: UITableViewCell {
-    var message: String {
-        get { return textLabel?.text ?? "" }
-        set(value) { textLabel!.text = value }
-    }
-    var isOn: Bool {
-        get { return switchView.isOn }
-        set(value) { switchView.isOn = value }
-    }
-    var isEnabled: Bool {
-        get { return switchView.isEnabled }
-        set(value) {
-            switchView.isEnabled = value
-            textLabel?.textColor = value ? UIColor.black : UIColor.gray
-        }
-    }
-
-    var onSwitchToggled: ((Bool) -> Void)?
-
-    let switchView: UISwitch
-
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        switchView = UISwitch()
-        super.init(style: .default, reuseIdentifier: reuseIdentifier)
-        accessoryView = switchView
-        switchView.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
-    }
-
-    @objc func switchToggled() {
-        onSwitchToggled?(switchView.isOn)
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        message = ""
-        isOn = false
-    }
-}
-
-private class SelectionListCell: UITableViewCell {
-    var message: String {
-        get { return textLabel?.text ?? "" }
-        set(value) { textLabel!.text = value }
-    }
-    var isChecked: Bool {
-        didSet {
-            accessoryType = isChecked ? .checkmark : .none
-        }
-    }
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        isChecked = false
-        super.init(style: .default, reuseIdentifier: reuseIdentifier)
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        message = ""
-        isChecked = false
-    }
-}
index ccbd00d252369a57009f0430fda13c4639088ee7..6d57006baeee954ce3a606e751bcd4dff456ebdd 100644 (file)
@@ -8,7 +8,7 @@ class ErrorPresenter {
     static func showErrorAlert(error: WireGuardAppError, from sourceVC: UIViewController?, onPresented: (() -> Void)? = nil, onDismissal: (() -> Void)? = nil) {
         guard let sourceVC = sourceVC else { return }
 
-        let (title, message) = error.alertText()
+        let (title, message) = error.alertText
         let okAction = UIAlertAction(title: "OK", style: .default) { _ in
             onDismissal?()
         }
index ad0fe79598190e987d99f78b7b075c01a913c7cf..a03b70944fa32e1f7142750c1197362f4f6e40ad 100644 (file)
@@ -24,7 +24,7 @@ class QRScanViewController: UIViewController {
         let tipLabel = UILabel()
         tipLabel.text = "Tip: Generate with `qrencode -t ansiutf8 < tunnel.conf`"
         tipLabel.adjustsFontSizeToFitWidth = true
-        tipLabel.textColor = UIColor.lightGray
+        tipLabel.textColor = .lightGray
         tipLabel.textAlignment = .center
 
         view.addSubview(tipLabel)
index f2d0f58504aa14cc71f8c1e2521f85c331bcc20d..bd6f547141175609c6698e7276ae3a9e44da7dc4 100644 (file)
@@ -13,33 +13,31 @@ class ScrollableLabel: UIScrollView {
         set(value) { label.textColor = value }
     }
 
-    let label: UILabel
-
-    init() {
+    let label: UILabel = {
         let label = UILabel()
         label.translatesAutoresizingMaskIntoConstraints = false
         label.textAlignment = .right
-        self.label = label
+        return label
+    }()
 
+    init() {
         super.init(frame: CGRect.zero)
 
-        self.isDirectionalLockEnabled = true
-        self.showsHorizontalScrollIndicator = false
-        self.showsVerticalScrollIndicator = false
+        isDirectionalLockEnabled = true
+        showsHorizontalScrollIndicator = false
+        showsVerticalScrollIndicator = false
 
         addSubview(label)
         label.translatesAutoresizingMaskIntoConstraints = false
         NSLayoutConstraint.activate([
-            label.leftAnchor.constraint(equalTo: self.contentLayoutGuide.leftAnchor),
-            label.topAnchor.constraint(equalTo: self.contentLayoutGuide.topAnchor),
-            label.bottomAnchor.constraint(equalTo: self.contentLayoutGuide.bottomAnchor),
-            label.rightAnchor.constraint(equalTo: self.contentLayoutGuide.rightAnchor),
-            label.heightAnchor.constraint(equalTo: self.heightAnchor)
+            label.leftAnchor.constraint(equalTo: contentLayoutGuide.leftAnchor),
+            label.topAnchor.constraint(equalTo: contentLayoutGuide.topAnchor),
+            label.bottomAnchor.constraint(equalTo: contentLayoutGuide.bottomAnchor),
+            label.rightAnchor.constraint(equalTo: contentLayoutGuide.rightAnchor),
+            label.heightAnchor.constraint(equalTo: heightAnchor)
         ])
-        // If label has less content, it should expand to fit the scrollView,
-        // so that right-alignment works in the label.
-        let expandToFitValueLabelConstraint = NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal,
-                                                                 toItem: self, attribute: .width, multiplier: 1, constant: 0)
+
+        let expandToFitValueLabelConstraint = NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)
         expandToFitValueLabelConstraint.priority = .defaultLow + 1
         expandToFitValueLabelConstraint.isActive = true
     }
diff --git a/WireGuard/WireGuard/UI/iOS/Settings/SettingsButtonCell.swift b/WireGuard/WireGuard/UI/iOS/Settings/SettingsButtonCell.swift
new file mode 100644 (file)
index 0000000..d795ab4
--- /dev/null
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class SettingsButtonCell: UITableViewCell {
+    var buttonText: String {
+        get { return button.title(for: .normal) ?? "" }
+        set(value) { button.setTitle(value, for: .normal) }
+    }
+    var onTapped: (() -> Void)?
+    
+    let button: UIButton = {
+        let button = UIButton(type: .system)
+        button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
+        button.titleLabel?.adjustsFontForContentSizeCategory = true
+        return button
+    }()
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        
+        contentView.addSubview(button)
+        button.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            button.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
+            contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: button.bottomAnchor),
+            button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
+            ])
+        
+        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
+    }
+    
+    @objc func buttonTapped() {
+        onTapped?()
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        buttonText = ""
+        onTapped = nil
+    }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/Settings/SettingsKeyValueCell.swift b/WireGuard/WireGuard/UI/iOS/Settings/SettingsKeyValueCell.swift
new file mode 100644 (file)
index 0000000..532f1d1
--- /dev/null
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class SettingsKeyValueCell: UITableViewCell {
+    var key: String {
+        get { return textLabel?.text ?? "" }
+        set(value) { textLabel?.text = value }
+    }
+    var value: String {
+        get { return detailTextLabel?.text ?? "" }
+        set(value) { detailTextLabel?.text = value }
+    }
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: .value1, reuseIdentifier: SettingsKeyValueCell.reuseIdentifier)
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        key = ""
+        value = ""
+    }
+}
similarity index 76%
rename from WireGuard/WireGuard/UI/iOS/SettingsTableViewController.swift
rename to WireGuard/WireGuard/UI/iOS/Settings/SettingsTableViewController.swift
index af9893dd271a83fda13eb0151ea70f5ea4d1090c..c87d452daa1b8188b8996131e7894450468c240b 100644 (file)
@@ -40,8 +40,8 @@ class SettingsTableViewController: UITableViewController {
         tableView.rowHeight = UITableView.automaticDimension
         tableView.allowsSelection = false
 
-        tableView.register(KeyValueCell.self)
-        tableView.register(ButtonCell.self)
+        tableView.register(SettingsKeyValueCell.self)
+        tableView.register(SettingsButtonCell.self)
 
         tableView.tableFooterView = UIImageView(image: UIImage(named: "wireguard.pdf"))
     }
@@ -167,7 +167,7 @@ extension SettingsTableViewController {
     override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let field = settingsFieldsBySection[indexPath.section][indexPath.row]
         if field == .iosAppVersion || field == .goBackendVersion {
-            let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
+            let cell: SettingsKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
             cell.key = field.rawValue
             if field == .iosAppVersion {
                 var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version"
@@ -180,7 +180,7 @@ extension SettingsTableViewController {
             }
             return cell
         } else if field == .exportZipArchive {
-            let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
+            let cell: SettingsButtonCell = tableView.dequeueReusableCell(for: indexPath)
             cell.buttonText = field.rawValue
             cell.onTapped = { [weak self] in
                 self?.exportConfigurationsAsZipFile(sourceView: cell.button)
@@ -188,7 +188,7 @@ extension SettingsTableViewController {
             return cell
         } else {
             assert(field == .exportLogFile)
-            let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
+            let cell: SettingsButtonCell = tableView.dequeueReusableCell(for: indexPath)
             cell.buttonText = field.rawValue
             cell.onTapped = { [weak self] in
                 self?.exportLogForLastActivatedTunnel(sourceView: cell.button)
@@ -197,67 +197,3 @@ extension SettingsTableViewController {
         }
     }
 }
-
-private class KeyValueCell: UITableViewCell {
-    var key: String {
-        get { return textLabel?.text ?? "" }
-        set(value) { textLabel?.text = value }
-    }
-    var value: String {
-        get { return detailTextLabel?.text ?? "" }
-        set(value) { detailTextLabel?.text = value }
-    }
-
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        super.init(style: .value1, reuseIdentifier: KeyValueCell.reuseIdentifier)
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        key = ""
-        value = ""
-    }
-}
-
-private class ButtonCell: UITableViewCell {
-    var buttonText: String {
-        get { return button.title(for: .normal) ?? "" }
-        set(value) { button.setTitle(value, for: .normal) }
-    }
-    var onTapped: (() -> Void)?
-
-    let button: UIButton
-
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        button = UIButton(type: .system)
-        button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
-        button.titleLabel?.adjustsFontForContentSizeCategory = true
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
-        contentView.addSubview(button)
-        button.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            button.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
-            contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: button.bottomAnchor),
-            button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
-        ])
-        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
-    }
-
-    @objc func buttonTapped() {
-        onTapped?()
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        buttonText = ""
-        onTapped = nil
-    }
-}
diff --git a/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailActivateOnDemandCell.swift b/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailActivateOnDemandCell.swift
new file mode 100644 (file)
index 0000000..9507c45
--- /dev/null
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class TunnelDetailActivateOnDemandCell: UITableViewCell {
+    var tunnel: TunnelContainer? {
+        didSet(value) {
+            update(from: tunnel?.activateOnDemandSetting())
+            onDemandStatusObservervationToken = tunnel?.observe(\.isActivateOnDemandEnabled) { [weak self] tunnel, _ in
+                self?.update(from: tunnel.activateOnDemandSetting())
+            }
+        }
+    }
+    
+    var onDemandStatusObservervationToken: AnyObject?
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: .value1, reuseIdentifier: reuseIdentifier)
+        textLabel?.text = "Activate on demand"
+        textLabel?.font = UIFont.preferredFont(forTextStyle: .body)
+        textLabel?.adjustsFontForContentSizeCategory = true
+        detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .body)
+        detailTextLabel?.adjustsFontForContentSizeCategory = true
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    func update(from activateOnDemandSetting: ActivateOnDemandSetting?) {
+        detailTextLabel?.text = TunnelViewModel.activateOnDemandDetailText(for: activateOnDemandSetting)
+    }
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        textLabel?.text = "Activate on demand"
+        detailTextLabel?.text = ""
+    }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailButtonCell.swift b/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailButtonCell.swift
new file mode 100644 (file)
index 0000000..8710616
--- /dev/null
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class TunnelDetailButtonCell: UITableViewCell {
+    var buttonText: String {
+        get { return button.title(for: .normal) ?? "" }
+        set(value) { button.setTitle(value, for: .normal) }
+    }
+    var hasDestructiveAction: Bool {
+        get { return button.tintColor == UIColor.red }
+        set(value) { button.tintColor = value ? UIColor.red : buttonStandardTintColor }
+    }
+    var onTapped: (() -> Void)?
+    
+    let button: UIButton = {
+        let button = UIButton(type: .system)
+        button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
+        button.titleLabel?.adjustsFontForContentSizeCategory = true
+        return button
+    }()
+    
+    var buttonStandardTintColor: UIColor
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        buttonStandardTintColor = button.tintColor
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        
+        contentView.addSubview(button)
+        button.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            button.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
+            contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: button.bottomAnchor),
+            button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
+            ])
+        
+        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
+    }
+    
+    @objc func buttonTapped() {
+        onTapped?()
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        buttonText = ""
+        onTapped = nil
+        hasDestructiveAction = false
+    }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailKeyValueCell.swift b/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailKeyValueCell.swift
new file mode 100644 (file)
index 0000000..cbe1c14
--- /dev/null
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class TunnelDetailKeyValueCell: CopyableLabelTableViewCell {
+    var key: String {
+        get { return keyLabel.text ?? "" }
+        set(value) { keyLabel.text = value }
+    }
+    var value: String {
+        get { return valueLabel.text }
+        set(value) { valueLabel.text = value }
+    }
+    
+    override var textToCopy: String? {
+        return self.valueLabel.text
+    }
+    
+    let keyLabel: UILabel = {
+        let keyLabel = UILabel()
+        keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
+        keyLabel.adjustsFontForContentSizeCategory = true
+        keyLabel.textColor = .black
+        return keyLabel
+    }()
+    
+    let valueLabel: ScrollableLabel = {
+        let valueLabel = ScrollableLabel()
+        valueLabel.label.font = UIFont.preferredFont(forTextStyle: .body)
+        valueLabel.label.adjustsFontForContentSizeCategory = true
+        valueLabel.textColor = .gray
+        return valueLabel
+    }()
+    
+    var isStackedHorizontally = false
+    var isStackedVertically = false
+    var contentSizeBasedConstraints = [NSLayoutConstraint]()
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        
+        contentView.addSubview(keyLabel)
+        keyLabel.translatesAutoresizingMaskIntoConstraints = false
+        keyLabel.textAlignment = .left
+        NSLayoutConstraint.activate([
+            keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
+            keyLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
+            ])
+        
+        contentView.addSubview(valueLabel)
+        valueLabel.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            valueLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor),
+            contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueLabel.bottomAnchor, multiplier: 0.5)
+            ])
+        
+        keyLabel.setContentCompressionResistancePriority(.defaultHigh + 1, for: .horizontal)
+        keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+        valueLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
+        
+        configureForContentSize()
+    }
+    
+    func configureForContentSize() {
+        var constraints = [NSLayoutConstraint]()
+        if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
+            // Stack vertically
+            if !isStackedVertically {
+                constraints = [
+                    valueLabel.topAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
+                    valueLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
+                    keyLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor)
+                ]
+                isStackedVertically = true
+                isStackedHorizontally = false
+            }
+        } else {
+            // Stack horizontally
+            if !isStackedHorizontally {
+                constraints = [
+                    contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
+                    valueLabel.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
+                    valueLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
+                ]
+                isStackedHorizontally = true
+                isStackedVertically = false
+            }
+        }
+        if !constraints.isEmpty {
+            NSLayoutConstraint.deactivate(self.contentSizeBasedConstraints)
+            NSLayoutConstraint.activate(constraints)
+            self.contentSizeBasedConstraints = constraints
+        }
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        key = ""
+        value = ""
+        configureForContentSize()
+    }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailStatusCell.swift b/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailStatusCell.swift
new file mode 100644 (file)
index 0000000..855e3ed
--- /dev/null
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class TunnelDetailStatusCell: UITableViewCell {
+    var tunnel: TunnelContainer? {
+        didSet(value) {
+            update(from: tunnel?.status)
+            statusObservervationToken = tunnel?.observe(\.status) { [weak self] tunnel, _ in
+                self?.update(from: tunnel.status)
+            }
+        }
+    }
+    var isSwitchInteractionEnabled: Bool {
+        get { return statusSwitch.isUserInteractionEnabled }
+        set(value) { statusSwitch.isUserInteractionEnabled = value }
+    }
+    var onSwitchToggled: ((Bool) -> Void)?
+    private var isOnSwitchToggledHandlerEnabled = true
+    
+    let statusSwitch: UISwitch
+    private var statusObservervationToken: AnyObject?
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        statusSwitch = UISwitch()
+        super.init(style: .default, reuseIdentifier: TunnelDetailKeyValueCell.reuseIdentifier)
+        accessoryView = statusSwitch
+        
+        statusSwitch.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
+    }
+    
+    @objc func switchToggled() {
+        if isOnSwitchToggledHandlerEnabled {
+            onSwitchToggled?(statusSwitch.isOn)
+        }
+    }
+    
+    private func update(from status: TunnelStatus?) {
+        guard let status = status else {
+            reset()
+            return
+        }
+        let text: String
+        switch status {
+        case .inactive:
+            text = "Inactive"
+        case .activating:
+            text = "Activating"
+        case .active:
+            text = "Active"
+        case .deactivating:
+            text = "Deactivating"
+        case .reasserting:
+            text = "Reactivating"
+        case .restarting:
+            text = "Restarting"
+        case .waiting:
+            text = "Waiting"
+        }
+        textLabel?.text = text
+        DispatchQueue.main.async { [weak statusSwitch] in
+            guard let statusSwitch = statusSwitch else { return }
+            statusSwitch.isOn = !(status == .deactivating || status == .inactive)
+            statusSwitch.isUserInteractionEnabled = (status == .inactive || status == .active)
+        }
+        textLabel?.textColor = (status == .active || status == .inactive) ? UIColor.black : UIColor.gray
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    private func reset() {
+        textLabel?.text = "Invalid"
+        statusSwitch.isOn = false
+        textLabel?.textColor = UIColor.gray
+        statusSwitch.isUserInteractionEnabled = false
+    }
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        reset()
+    }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailTableViewController.swift
new file mode 100644 (file)
index 0000000..af4cf83
--- /dev/null
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+// MARK: TunnelDetailTableViewController
+
+class TunnelDetailTableViewController: UITableViewController {
+
+    private enum Section {
+        case status
+        case interface
+        case peer(_ peer: TunnelViewModel.PeerData)
+        case onDemand
+        case delete
+    }
+
+    let interfaceFields: [TunnelViewModel.InterfaceField] = [
+        .name, .publicKey, .addresses,
+        .listenPort, .mtu, .dns
+    ]
+
+    let peerFields: [TunnelViewModel.PeerField] = [
+        .publicKey, .preSharedKey, .endpoint,
+        .allowedIPs, .persistentKeepAlive
+    ]
+
+    let tunnelsManager: TunnelsManager
+    let tunnel: TunnelContainer
+    var tunnelViewModel: TunnelViewModel
+    private var sections = [Section]()
+
+    init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) {
+        self.tunnelsManager = tunnelsManager
+        self.tunnel = tunnel
+        tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration())
+        super.init(style: .grouped)
+        loadSections()
+    }
+
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        self.title = tunnelViewModel.interfaceData[.name]
+        self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(editTapped))
+
+        self.tableView.estimatedRowHeight = 44
+        self.tableView.rowHeight = UITableView.automaticDimension
+        self.tableView.allowsSelection = false
+        self.tableView.register(TunnelDetailStatusCell.self)
+        self.tableView.register(TunnelDetailKeyValueCell.self)
+        self.tableView.register(TunnelDetailButtonCell.self)
+        self.tableView.register(TunnelDetailActivateOnDemandCell.self)
+
+        // State restoration
+        self.restorationIdentifier = "TunnelDetailVC:\(tunnel.name)"
+    }
+
+    private func loadSections() {
+        sections.removeAll()
+        sections.append(.status)
+        sections.append(.interface)
+        tunnelViewModel.peersData.forEach { sections.append(.peer($0)) }
+        sections.append(.onDemand)
+        sections.append(.delete)
+    }
+
+    @objc func editTapped() {
+        let editVC = TunnelEditTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
+        editVC.delegate = self
+        let editNC = UINavigationController(rootViewController: editVC)
+        editNC.modalPresentationStyle = .formSheet
+        present(editNC, animated: true)
+    }
+
+    func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView,
+                               onConfirmed: @escaping (() -> Void)) {
+        let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in
+            onConfirmed()
+        }
+        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
+        let alert = UIAlertController(title: "", message: message, preferredStyle: .actionSheet)
+        alert.addAction(destroyAction)
+        alert.addAction(cancelAction)
+
+        // popoverPresentationController will be nil on iPhone and non-nil on iPad
+        alert.popoverPresentationController?.sourceView = sourceView
+        alert.popoverPresentationController?.sourceRect = sourceView.bounds
+
+        self.present(alert, animated: true, completion: nil)
+    }
+}
+
+// MARK: TunnelEditTableViewControllerDelegate
+
+extension TunnelDetailTableViewController: TunnelEditTableViewControllerDelegate {
+    func tunnelSaved(tunnel: TunnelContainer) {
+        tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration())
+        loadSections()
+        self.title = tunnel.name
+        self.tableView.reloadData()
+    }
+    func tunnelEditingCancelled() {
+        // Nothing to do
+    }
+}
+
+// MARK: UITableViewDataSource
+
+extension TunnelDetailTableViewController {
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        return sections.count
+    }
+
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        switch sections[section] {
+        case .status:
+            return 1
+        case .interface:
+             return tunnelViewModel.interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFields).count
+        case .peer(let peerData):
+            return peerData.filterFieldsWithValueOrControl(peerFields: peerFields).count
+        case .onDemand:
+            return 1
+        case .delete:
+            return 1
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+        switch sections[section] {
+        case .status:
+            return "Status"
+        case .interface:
+            return "Interface"
+        case .peer:
+            return "Peer"
+        case .onDemand:
+            return "On-Demand Activation"
+        case .delete:
+            return nil
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        switch sections[indexPath.section] {
+        case .status:
+            return statusCell(for: tableView, at: indexPath)
+        case .interface:
+            return interfaceCell(for: tableView, at: indexPath)
+        case .peer(let peer):
+            return peerCell(for: tableView, at: indexPath, with: peer)
+        case .onDemand:
+            return onDemandCell(for: tableView, at: indexPath)
+        case .delete:
+            return deleteConfigurationCell(for: tableView, at: indexPath)
+        }
+    }
+
+    private func statusCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
+        let cell: TunnelDetailStatusCell = tableView.dequeueReusableCell(for: indexPath)
+        cell.tunnel = self.tunnel
+        cell.onSwitchToggled = { [weak self] isOn in
+            guard let self = self else { return }
+            if isOn {
+                self.tunnelsManager.startActivation(of: self.tunnel)
+            } else {
+                self.tunnelsManager.startDeactivation(of: self.tunnel)
+            }
+        }
+        return cell
+    }
+
+    private func interfaceCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
+        let field = tunnelViewModel.interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFields)[indexPath.row]
+        let cell: TunnelDetailKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
+        cell.key = field.rawValue
+        cell.value = tunnelViewModel.interfaceData[field]
+        return cell
+    }
+
+    private func peerCell(for tableView: UITableView, at indexPath: IndexPath, with peerData: TunnelViewModel.PeerData) -> UITableViewCell {
+        let field = peerData.filterFieldsWithValueOrControl(peerFields: peerFields)[indexPath.row]
+        let cell: TunnelDetailKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
+        cell.key = field.rawValue
+        cell.value = peerData[field]
+        return cell
+    }
+
+    private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
+        let cell: TunnelDetailActivateOnDemandCell = tableView.dequeueReusableCell(for: indexPath)
+        cell.tunnel = self.tunnel
+        return cell
+    }
+
+    private func deleteConfigurationCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
+        let cell: TunnelDetailButtonCell = tableView.dequeueReusableCell(for: indexPath)
+        cell.buttonText = "Delete tunnel"
+        cell.hasDestructiveAction = true
+        cell.onTapped = { [weak self] in
+            guard let self = self else { return }
+            self.showConfirmationAlert(message: "Delete this tunnel?", buttonTitle: "Delete", from: cell) { [weak self] in
+                guard let tunnelsManager = self?.tunnelsManager, let tunnel = self?.tunnel else { return }
+                tunnelsManager.remove(tunnel: tunnel) { error in
+                    if error != nil {
+                        print("Error removing tunnel: \(String(describing: error))")
+                        return
+                    }
+                }
+                self?.navigationController?.navigationController?.popToRootViewController(animated: true)
+            }
+        }
+        return cell
+    }
+
+}
diff --git a/WireGuard/WireGuard/UI/iOS/TunnelList/BorderedTextButton.swift b/WireGuard/WireGuard/UI/iOS/TunnelList/BorderedTextButton.swift
new file mode 100644 (file)
index 0000000..5114c09
--- /dev/null
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class BorderedTextButton: UIView {
+    let button: UIButton = {
+        let button = UIButton(type: .system)
+        button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
+        button.titleLabel?.adjustsFontForContentSizeCategory = true
+        return button
+    }()
+    
+    override var intrinsicContentSize: CGSize {
+        let buttonSize = button.intrinsicContentSize
+        return CGSize(width: buttonSize.width + 32, height: buttonSize.height + 16)
+    }
+    
+    var title: String {
+        get { return button.title(for: .normal) ?? "" }
+        set(value) { button.setTitle(value, for: .normal) }
+    }
+    
+    var onTapped: (() -> Void)?
+    
+    init() {
+        super.init(frame: CGRect.zero)
+        
+        layer.borderWidth = 1
+        layer.cornerRadius = 5
+        layer.borderColor = button.tintColor.cgColor
+        
+        addSubview(button)
+        button.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            button.centerXAnchor.constraint(equalTo: self.centerXAnchor),
+            button.centerYAnchor.constraint(equalTo: self.centerYAnchor)
+            ])
+        
+        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
+    }
+    
+    @objc func buttonTapped() {
+        onTapped?()
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/TunnelList/TunnelListCell.swift b/WireGuard/WireGuard/UI/iOS/TunnelList/TunnelListCell.swift
new file mode 100644 (file)
index 0000000..95055c5
--- /dev/null
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: MIT
+// Copyright Â© 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class TunnelListCell: UITableViewCell {
+    var tunnel: TunnelContainer? {
+        didSet(value) {
+            // Bind to the tunnel's name
+            nameLabel.text = tunnel?.name ?? ""
+            nameObservervationToken = tunnel?.observe(\.name) { [weak self] tunnel, _ in
+                self?.nameLabel.text = tunnel.name
+            }
+            // Bind to the tunnel's status
+            update(from: tunnel?.status)
+            statusObservervationToken = tunnel?.observe(\.status) { [weak self] tunnel, _ in
+                self?.update(from: tunnel.status)
+            }
+        }
+    }
+    var onSwitchToggled: ((Bool) -> Void)?
+    
+    let nameLabel: UILabel = {
+        let nameLabel = UILabel()
+        nameLabel.font = UIFont.preferredFont(forTextStyle: .body)
+        nameLabel.adjustsFontForContentSizeCategory = true
+        nameLabel.numberOfLines = 0
+        return nameLabel
+    }()
+    
+    let busyIndicator: UIActivityIndicatorView = {
+        let busyIndicator = UIActivityIndicatorView(style: .gray)
+        busyIndicator.hidesWhenStopped = true
+        return busyIndicator
+    }()
+    
+    let statusSwitch = UISwitch()
+    
+    private var statusObservervationToken: AnyObject?
+    private var nameObservervationToken: AnyObject?
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        
+        contentView.addSubview(statusSwitch)
+        statusSwitch.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            statusSwitch.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+            contentView.rightAnchor.constraint(equalTo: statusSwitch.rightAnchor)
+            ])
+        
+        contentView.addSubview(busyIndicator)
+        busyIndicator.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            busyIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+            statusSwitch.leftAnchor.constraint(equalToSystemSpacingAfter: busyIndicator.rightAnchor, multiplier: 1)
+            ])
+        
+        contentView.addSubview(nameLabel)
+        nameLabel.translatesAutoresizingMaskIntoConstraints = false
+        nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
+        let bottomAnchorConstraint = contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: nameLabel.bottomAnchor, multiplier: 1)
+        bottomAnchorConstraint.priority = .defaultLow
+        NSLayoutConstraint.activate([
+            nameLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 1),
+            nameLabel.leftAnchor.constraint(equalToSystemSpacingAfter: contentView.layoutMarginsGuide.leftAnchor, multiplier: 1),
+            busyIndicator.leftAnchor.constraint(equalToSystemSpacingAfter: nameLabel.rightAnchor, multiplier: 1),
+            bottomAnchorConstraint
+            ])
+        
+        accessoryType = .disclosureIndicator
+        
+        statusSwitch.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
+    }
+    
+    @objc func switchToggled() {
+        onSwitchToggled?(statusSwitch.isOn)
+    }
+    
+    private func update(from status: TunnelStatus?) {
+        guard let status = status else {
+            reset()
+            return
+        }
+        DispatchQueue.main.async { [weak statusSwitch, weak busyIndicator] in
+            guard let statusSwitch = statusSwitch, let busyIndicator = busyIndicator else { return }
+            statusSwitch.isOn = !(status == .deactivating || status == .inactive)
+            statusSwitch.isUserInteractionEnabled = (status == .inactive || status == .active)
+            if status == .inactive || status == .active {
+                busyIndicator.stopAnimating()
+            } else {
+                busyIndicator.startAnimating()
+            }
+        }
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    private func reset() {
+        statusSwitch.isOn = false
+        statusSwitch.isUserInteractionEnabled = false
+        busyIndicator.stopAnimating()
+    }
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        reset()
+    }
+}
similarity index 57%
rename from WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift
rename to WireGuard/WireGuard/UI/iOS/TunnelList/TunnelsListTableViewController.swift
index efa85e61f013e6dc5c43d794e534d86b8c3cde73..eda09af3d035697a0b32a4dfc363e220a2eac668 100644 (file)
@@ -9,95 +9,88 @@ class TunnelsListTableViewController: UIViewController {
 
     var tunnelsManager: TunnelsManager?
 
-    var busyIndicator: UIActivityIndicatorView?
-    var centeredAddButton: BorderedTextButton?
-    var tableView: UITableView?
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        view.backgroundColor = UIColor.white
-
-        // Set up the navigation bar
-        self.title = "WireGuard"
-        let addButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonTapped(sender:)))
-        self.navigationItem.rightBarButtonItem = addButtonItem
-        let settingsButtonItem = UIBarButtonItem(title: "Settings", style: .plain, target: self, action: #selector(settingsButtonTapped(sender:)))
-        self.navigationItem.leftBarButtonItem = settingsButtonItem
-
-        // Set up the busy indicator
+    let tableView: UITableView = {
+        let tableView = UITableView(frame: CGRect.zero, style: .plain)
+        tableView.estimatedRowHeight = 60
+        tableView.rowHeight = UITableView.automaticDimension
+        tableView.separatorStyle = .none
+        tableView.register(TunnelListCell.self)
+        return tableView
+    }()
+    
+    let centeredAddButton: BorderedTextButton = {
+        let button = BorderedTextButton()
+        button.title = "Add a tunnel"
+        button.isHidden = true
+        return button
+    }()
+    
+    let busyIndicator: UIActivityIndicatorView = {
         let busyIndicator = UIActivityIndicatorView(style: .gray)
         busyIndicator.hidesWhenStopped = true
+        return busyIndicator
+    }()
+    
+    override func loadView() {
+        view = UIView()
+        view.backgroundColor = .white
+        
+        tableView.dataSource = self
+        tableView.delegate = self
+        
+        view.addSubview(tableView)
+        tableView.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
+            tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
+            tableView.topAnchor.constraint(equalTo: view.topAnchor),
+            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
+        ])
 
-        // Add the busyIndicator, centered
         view.addSubview(busyIndicator)
         busyIndicator.translatesAutoresizingMaskIntoConstraints = false
         NSLayoutConstraint.activate([
             busyIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
             busyIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
         ])
-        busyIndicator.startAnimating()
-        self.busyIndicator = busyIndicator
-
-        // State restoration
-        self.restorationIdentifier = "TunnelsListVC"
-    }
-
-    func setTunnelsManager(tunnelsManager: TunnelsManager) {
-        if self.tunnelsManager != nil {
-            // If a tunnels manager is already set, do nothing
-            return
-        }
-
-        // Create the table view
-
-        let tableView = UITableView(frame: CGRect.zero, style: .plain)
-        tableView.estimatedRowHeight = 60
-        tableView.rowHeight = UITableView.automaticDimension
-        tableView.separatorStyle = .none
-        tableView.register(TunnelCell.self)
-
-        self.view.addSubview(tableView)
-        tableView.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            tableView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
-            tableView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
-            tableView.topAnchor.constraint(equalTo: self.view.topAnchor),
-            tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
-        ])
-        tableView.dataSource = self
-        tableView.delegate = self
-        self.tableView = tableView
-
-        // Add button at the center
-
-        let centeredAddButton = BorderedTextButton()
-        centeredAddButton.title = "Add a tunnel"
-        centeredAddButton.isHidden = true
-        self.view.addSubview(centeredAddButton)
+        
+        view.addSubview(centeredAddButton)
         centeredAddButton.translatesAutoresizingMaskIntoConstraints = false
         NSLayoutConstraint.activate([
-            centeredAddButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
-            centeredAddButton.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
+            centeredAddButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+            centeredAddButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
         ])
+        
         centeredAddButton.onTapped = { [weak self] in
-            self?.addButtonTapped(sender: centeredAddButton)
+            guard let self = self else { return }
+            self.addButtonTapped(sender: self.centeredAddButton)
         }
-        centeredAddButton.isHidden = (tunnelsManager.numberOfTunnels() > 0)
-        self.centeredAddButton = centeredAddButton
-
-        // Hide the busy indicator
+        
+        busyIndicator.startAnimating()
+    }
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
 
-        self.busyIndicator?.stopAnimating()
+        title = "WireGuard"
+        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonTapped(sender:)))
+        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Settings", style: .plain, target: self, action: #selector(settingsButtonTapped(sender:)))
 
-        // Keep track of the tunnels manager
+        restorationIdentifier = "TunnelsListVC"
+    }
 
+    func setTunnelsManager(tunnelsManager: TunnelsManager) {
         self.tunnelsManager = tunnelsManager
         tunnelsManager.tunnelsListDelegate = self
+        
+        busyIndicator.stopAnimating()
+        tableView.reloadData()
+        centeredAddButton.isHidden = tunnelsManager.numberOfTunnels() > 0
     }
 
     override func viewWillAppear(_: Bool) {
         // Remove selection when getting back to the list view on iPhone
-        if let tableView = self.tableView, let selectedRowIndexPath = tableView.indexPathForSelectedRow {
+        if let selectedRowIndexPath = tableView.indexPathForSelectedRow {
             tableView.deselectRow(at: selectedRowIndexPath, animated: false)
         }
     }
@@ -241,7 +234,7 @@ extension TunnelsListTableViewController: UITableViewDataSource {
     }
 
     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        let cell: TunnelCell = tableView.dequeueReusableCell(for: indexPath)
+        let cell: TunnelListCell = tableView.dequeueReusableCell(for: indexPath)
         if let tunnelsManager = tunnelsManager {
             let tunnel = tunnelsManager.tunnel(at: indexPath.row)
             cell.tunnel = tunnel
@@ -293,161 +286,20 @@ extension TunnelsListTableViewController: UITableViewDelegate {
 
 extension TunnelsListTableViewController: TunnelsManagerListDelegate {
     func tunnelAdded(at index: Int) {
-        tableView?.insertRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
-        centeredAddButton?.isHidden = (tunnelsManager?.numberOfTunnels() ?? 0 > 0)
+        tableView.insertRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
+        centeredAddButton.isHidden = (tunnelsManager?.numberOfTunnels() ?? 0 > 0)
     }
 
     func tunnelModified(at index: Int) {
-        tableView?.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
+        tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
     }
 
     func tunnelMoved(from oldIndex: Int, to newIndex: Int) {
-        tableView?.moveRow(at: IndexPath(row: oldIndex, section: 0), to: IndexPath(row: newIndex, section: 0))
+        tableView.moveRow(at: IndexPath(row: oldIndex, section: 0), to: IndexPath(row: newIndex, section: 0))
     }
 
     func tunnelRemoved(at index: Int) {
-        tableView?.deleteRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
-        centeredAddButton?.isHidden = (tunnelsManager?.numberOfTunnels() ?? 0 > 0)
-    }
-}
-
-private class TunnelCell: UITableViewCell {
-    var tunnel: TunnelContainer? {
-        didSet(value) {
-            // Bind to the tunnel's name
-            nameLabel.text = tunnel?.name ?? ""
-            nameObservervationToken = tunnel?.observe(\.name) { [weak self] tunnel, _ in
-                self?.nameLabel.text = tunnel.name
-            }
-            // Bind to the tunnel's status
-            update(from: tunnel?.status)
-            statusObservervationToken = tunnel?.observe(\.status) { [weak self] tunnel, _ in
-                self?.update(from: tunnel.status)
-            }
-        }
-    }
-    var onSwitchToggled: ((Bool) -> Void)?
-
-    let nameLabel: UILabel
-    let busyIndicator: UIActivityIndicatorView
-    let statusSwitch: UISwitch
-
-    private var statusObservervationToken: AnyObject?
-    private var nameObservervationToken: AnyObject?
-
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        nameLabel = UILabel()
-        nameLabel.font = UIFont.preferredFont(forTextStyle: .body)
-        nameLabel.adjustsFontForContentSizeCategory = true
-        busyIndicator = UIActivityIndicatorView(style: .gray)
-        busyIndicator.hidesWhenStopped = true
-        statusSwitch = UISwitch()
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
-        contentView.addSubview(statusSwitch)
-        statusSwitch.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            statusSwitch.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
-            contentView.rightAnchor.constraint(equalTo: statusSwitch.rightAnchor)
-        ])
-        contentView.addSubview(busyIndicator)
-        busyIndicator.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            busyIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
-            statusSwitch.leftAnchor.constraint(equalToSystemSpacingAfter: busyIndicator.rightAnchor, multiplier: 1)
-        ])
-        contentView.addSubview(nameLabel)
-        nameLabel.translatesAutoresizingMaskIntoConstraints = false
-        nameLabel.numberOfLines = 0
-        nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
-        let bottomAnchorConstraint = contentView.layoutMarginsGuide.bottomAnchor.constraint(
-            equalToSystemSpacingBelow: nameLabel.bottomAnchor, multiplier: 1)
-        bottomAnchorConstraint.priority = .defaultLow // Allow this constraint to be broken when animating a cell away during deletion
-        NSLayoutConstraint.activate([
-            nameLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 1),
-            nameLabel.leftAnchor.constraint(equalToSystemSpacingAfter: contentView.layoutMarginsGuide.leftAnchor, multiplier: 1),
-            busyIndicator.leftAnchor.constraint(equalToSystemSpacingAfter: nameLabel.rightAnchor, multiplier: 1),
-            bottomAnchorConstraint
-        ])
-
-        self.accessoryType = .disclosureIndicator
-
-        statusSwitch.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
-    }
-
-    @objc func switchToggled() {
-        onSwitchToggled?(statusSwitch.isOn)
-    }
-
-    private func update(from status: TunnelStatus?) {
-        guard let status = status else {
-            reset()
-            return
-        }
-        DispatchQueue.main.async { [weak statusSwitch, weak busyIndicator] in
-            guard let statusSwitch = statusSwitch, let busyIndicator = busyIndicator else { return }
-            statusSwitch.isOn = !(status == .deactivating || status == .inactive)
-            statusSwitch.isUserInteractionEnabled = (status == .inactive || status == .active)
-            if status == .inactive || status == .active {
-                busyIndicator.stopAnimating()
-            } else {
-                busyIndicator.startAnimating()
-            }
-        }
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    private func reset() {
-        statusSwitch.isOn = false
-        statusSwitch.isUserInteractionEnabled = false
-        busyIndicator.stopAnimating()
-    }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        reset()
-    }
-}
-
-class BorderedTextButton: UIView {
-    let button: UIButton
-
-    override var intrinsicContentSize: CGSize {
-        let buttonSize = button.intrinsicContentSize
-        return CGSize(width: buttonSize.width + 32, height: buttonSize.height + 16)
-    }
-
-    var title: String {
-        get { return button.title(for: .normal) ?? "" }
-        set(value) { button.setTitle(value, for: .normal) }
-    }
-
-    var onTapped: (() -> Void)?
-
-    init() {
-        button = UIButton(type: .system)
-        button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
-        button.titleLabel?.adjustsFontForContentSizeCategory = true
-        super.init(frame: CGRect.zero)
-        addSubview(button)
-        button.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            button.centerXAnchor.constraint(equalTo: self.centerXAnchor),
-            button.centerYAnchor.constraint(equalTo: self.centerYAnchor)
-        ])
-        layer.borderWidth = 1
-        layer.cornerRadius = 5
-        layer.borderColor = button.tintColor.cgColor
-        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
-    }
-
-    @objc func buttonTapped() {
-        onTapped?()
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
+        tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
+        centeredAddButton.isHidden = tunnelsManager?.numberOfTunnels() ?? 0 > 0
     }
 }
index ba83ac5d9df589f2f46078f3744462e4a8b4fc94..aa2f9098fe3e7b53cc2b836906f160f6187e09a7 100644 (file)
@@ -3,5 +3,5 @@
 
 protocol WireGuardAppError: Error {
     typealias AlertText = (title: String, message: String)
-    func alertText() -> AlertText
+    var alertText: AlertText { get }
 }
index ad74d0e1eb10a182b57cf3612739a819a46214e4..66c99f53e04c2b505b296cd695f71a09b776eb4d 100644 (file)
@@ -8,7 +8,7 @@ enum ZipArchiveError: WireGuardAppError {
     case cantOpenOutputZipFileForWriting
     case badArchive
 
-    func alertText() -> AlertText {
+    var alertText: AlertText {
         switch self {
         case .cantOpenInputZipFile:
             return ("Unable to read zip archive", "The zip archive could not be read.")
index cdc9ac983b351c59265c317913d2bef5234776d9..4c5fde865206de593e362d2402692b8fe9f10777 100644 (file)
@@ -6,11 +6,8 @@ import UIKit
 enum ZipExporterError: WireGuardAppError {
     case noTunnelsToExport
 
-    func alertText() -> AlertText {
-        switch self {
-        case .noTunnelsToExport:
-            return ("Nothing to export", "There are no tunnels to export")
-        }
+    var alertText: AlertText {
+        return ("Nothing to export", "There are no tunnels to export")
     }
 }
 
index 523614b99bb908428cb43586727253baf2985c33..e87633c2624ac62522658a5c6a176e1d766aeabc 100644 (file)
@@ -6,11 +6,8 @@ import UIKit
 enum ZipImporterError: WireGuardAppError {
     case noTunnelsInZipArchive
 
-    func alertText() -> AlertText {
-        switch self {
-        case .noTunnelsInZipArchive:
-            return ("No tunnels in zip archive", "No .conf tunnel files were found inside the zip archive.")
-        }
+    var alertText: AlertText {
+        return ("No tunnels in zip archive", "No .conf tunnel files were found inside the zip archive.")
     }
 }
 
index 9f85743dfcff130a8982ce42a2b2c9fdb17234c3..a30b512daf2f1bfad89473268ad0a4578337652d 100644 (file)
@@ -65,6 +65,8 @@ class DNSResolver {
 extension DNSResolver {
     // Based on DNS resolution code by Jason Donenfeld <jason@zx2c4.com>
     // in parse_endpoint() in src/tools/config.c in the WireGuard codebase
+    
+    //swiftlint:disable:next cyclomatic_complexity
     private static func resolveSync(endpoint: Endpoint) -> Endpoint? {
         switch endpoint.host {
         case .name(let name, _):
index 6d3f3321754ffd2fd7dce7a06fdaa3ffe0e89ee8..38182eeff81c2e0df539870326d6172c25bb73a6 100644 (file)
@@ -41,6 +41,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
         startTunnel(with: tunnelConfiguration, completionHandler: startTunnelCompletionHandler)
     }
 
+    //swiftlint:disable:next function_body_length
     func startTunnel(with tunnelConfiguration: TunnelConfiguration, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
 
         configureLogger()
@@ -158,7 +159,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
     }
 }
 
-// swiftlint:disable large_tuple
+// swiftlint:disable large_tuple identifier_name
 func withStringsAsGoStrings<R>(_ s1: String, _ s2: String? = nil, _ s3: String? = nil, _ s4: String? = nil, call: ((gostring_t, gostring_t, gostring_t, gostring_t)) -> R) -> R {
     func helper(_ p1: UnsafePointer<Int8>?, _ p2: UnsafePointer<Int8>?, _ p3: UnsafePointer<Int8>?, _ p4: UnsafePointer<Int8>?, _ call: ((gostring_t, gostring_t, gostring_t, gostring_t)) -> R) -> R {
         return call((gostring_t(p: p1, n: s1.utf8.count), gostring_t(p: p2, n: s2?.utf8.count ?? 0), gostring_t(p: p3, n: s3?.utf8.count ?? 0), gostring_t(p: p4, n: s4?.utf8.count ?? 0)))