aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Kuck <eric@bluelinelabs.com>2018-12-13 12:58:50 -0600
committerEric Kuck <eric@bluelinelabs.com>2018-12-13 12:58:50 -0600
commit05d750539b91eff582ff6a789fcdcab73bb5f7bb (patch)
tree0a59939a0805567ea1c4b310d78e4d4c9394cb96
parentAvoid escaping heap allocation (diff)
downloadwireguard-apple-05d750539b91eff582ff6a789fcdcab73bb5f7bb.tar.xz
wireguard-apple-05d750539b91eff582ff6a789fcdcab73bb5f7bb.zip
Reorganized ViewControllers (split out UIViews and UITableViewCells into their own classes)
All swiftlint warnings except one fixed up Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
-rw-r--r--WireGuard/.swiftlint.yml2
-rw-r--r--WireGuard/WireGuard.xcodeproj/project.pbxproj93
-rw-r--r--WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift3
-rw-r--r--WireGuard/WireGuard/Tunnel/TunnelsManager.swift106
-rw-r--r--WireGuard/WireGuard/UI/TunnelViewModel.swift2
-rw-r--r--WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditButtonCell.swift55
-rw-r--r--WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditKeyValueCell.swift158
-rw-r--r--WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditReadOnlyKeyValueCell.swift71
-rw-r--r--WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditSectionListCell.swift30
-rw-r--r--WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditSwitchCell.swift47
-rw-r--r--WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditTableViewController.swift (renamed from WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift)358
-rw-r--r--WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift2
-rw-r--r--WireGuard/WireGuard/UI/iOS/QRScanViewController.swift2
-rw-r--r--WireGuard/WireGuard/UI/iOS/ScrollableLabel.swift30
-rw-r--r--WireGuard/WireGuard/UI/iOS/Settings/SettingsButtonCell.swift47
-rw-r--r--WireGuard/WireGuard/UI/iOS/Settings/SettingsKeyValueCell.swift29
-rw-r--r--WireGuard/WireGuard/UI/iOS/Settings/SettingsTableViewController.swift (renamed from WireGuard/WireGuard/UI/iOS/SettingsTableViewController.swift)74
-rw-r--r--WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailActivateOnDemandCell.swift40
-rw-r--r--WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailButtonCell.swift55
-rw-r--r--WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailKeyValueCell.swift107
-rw-r--r--WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailStatusCell.swift85
-rw-r--r--WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailTableViewController.swift219
-rw-r--r--WireGuard/WireGuard/UI/iOS/TunnelList/BorderedTextButton.swift50
-rw-r--r--WireGuard/WireGuard/UI/iOS/TunnelList/TunnelListCell.swift111
-rw-r--r--WireGuard/WireGuard/UI/iOS/TunnelList/TunnelsListTableViewController.swift (renamed from WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift)280
-rw-r--r--WireGuard/WireGuard/WireGuardAppError.swift2
-rw-r--r--WireGuard/WireGuard/ZipArchive/ZipArchive.swift2
-rw-r--r--WireGuard/WireGuard/ZipArchive/ZipExporter.swift7
-rw-r--r--WireGuard/WireGuard/ZipArchive/ZipImporter.swift7
-rw-r--r--WireGuard/WireGuardNetworkExtension/DNSResolver.swift2
-rw-r--r--WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift3
31 files changed, 1360 insertions, 719 deletions
diff --git a/WireGuard/.swiftlint.yml b/WireGuard/.swiftlint.yml
index 2c414b60..f0a450f1 100644
--- a/WireGuard/.swiftlint.yml
+++ b/WireGuard/.swiftlint.yml
@@ -10,3 +10,5 @@ file_length:
cyclomatic_complexity:
warning: 10
error: 25
+function_body_length:
+ warning: 45
diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj
index 4e6f6d87..53f25241 100644
--- a/WireGuard/WireGuard.xcodeproj/project.pbxproj
+++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj
@@ -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 */; };
@@ -96,6 +109,19 @@
/* 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>"; };
@@ -176,6 +202,51 @@
/* 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 = (
@@ -232,14 +303,14 @@
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 */,
@@ -621,33 +692,47 @@
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;
};
diff --git a/WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift b/WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift
index a8747206..b7239e5c 100644
--- a/WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift
+++ b/WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift
@@ -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 }
diff --git a/WireGuard/WireGuard/Tunnel/TunnelsManager.swift b/WireGuard/WireGuard/Tunnel/TunnelsManager.swift
index a968419e..64f694ba 100644
--- a/WireGuard/WireGuard/Tunnel/TunnelsManager.swift
+++ b/WireGuard/WireGuard/Tunnel/TunnelsManager.swift
@@ -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
diff --git a/WireGuard/WireGuard/UI/TunnelViewModel.swift b/WireGuard/WireGuard/UI/TunnelViewModel.swift
index 80a6092b..c9b1cf14 100644
--- a/WireGuard/WireGuard/UI/TunnelViewModel.swift
+++ b/WireGuard/WireGuard/UI/TunnelViewModel.swift
@@ -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
index 00000000..af70183f
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditButtonCell.swift
@@ -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
index 00000000..432d75b6
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditKeyValueCell.swift
@@ -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
index 00000000..48c87982
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditReadOnlyKeyValueCell.swift
@@ -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
index 00000000..ca0352eb
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditSectionListCell.swift
@@ -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
index 00000000..658fb951
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditSwitchCell.swift
@@ -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
+ }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift b/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditTableViewController.swift
index 0386b0aa..8d055d23 100644
--- a/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/EditTunnel/TunnelEditTableViewController.swift
@@ -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
- }
-}
diff --git a/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift b/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift
index ccbd00d2..6d57006b 100644
--- a/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift
+++ b/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift
@@ -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?()
}
diff --git a/WireGuard/WireGuard/UI/iOS/QRScanViewController.swift b/WireGuard/WireGuard/UI/iOS/QRScanViewController.swift
index ad0fe795..a03b7094 100644
--- a/WireGuard/WireGuard/UI/iOS/QRScanViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/QRScanViewController.swift
@@ -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)
diff --git a/WireGuard/WireGuard/UI/iOS/ScrollableLabel.swift b/WireGuard/WireGuard/UI/iOS/ScrollableLabel.swift
index f2d0f585..bd6f5471 100644
--- a/WireGuard/WireGuard/UI/iOS/ScrollableLabel.swift
+++ b/WireGuard/WireGuard/UI/iOS/ScrollableLabel.swift
@@ -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
index 00000000..d795ab48
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/Settings/SettingsButtonCell.swift
@@ -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
index 00000000..532f1d1a
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/Settings/SettingsKeyValueCell.swift
@@ -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 = ""
+ }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/SettingsTableViewController.swift b/WireGuard/WireGuard/UI/iOS/Settings/SettingsTableViewController.swift
index af9893dd..c87d452d 100644
--- a/WireGuard/WireGuard/UI/iOS/SettingsTableViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/Settings/SettingsTableViewController.swift
@@ -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
index 00000000..9507c457
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailActivateOnDemandCell.swift
@@ -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
index 00000000..8710616a
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailButtonCell.swift
@@ -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
index 00000000..cbe1c147
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailKeyValueCell.swift
@@ -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
index 00000000..855e3ed2
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailStatusCell.swift
@@ -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
index 00000000..af4cf83a
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/TunnelDetail/TunnelDetailTableViewController.swift
@@ -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
index 00000000..5114c09b
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/TunnelList/BorderedTextButton.swift
@@ -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
index 00000000..95055c51
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/TunnelList/TunnelListCell.swift
@@ -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()
+ }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelList/TunnelsListTableViewController.swift
index efa85e61..eda09af3 100644
--- a/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/TunnelList/TunnelsListTableViewController.swift
@@ -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
}
}
diff --git a/WireGuard/WireGuard/WireGuardAppError.swift b/WireGuard/WireGuard/WireGuardAppError.swift
index ba83ac5d..aa2f9098 100644
--- a/WireGuard/WireGuard/WireGuardAppError.swift
+++ b/WireGuard/WireGuard/WireGuardAppError.swift
@@ -3,5 +3,5 @@
protocol WireGuardAppError: Error {
typealias AlertText = (title: String, message: String)
- func alertText() -> AlertText
+ var alertText: AlertText { get }
}
diff --git a/WireGuard/WireGuard/ZipArchive/ZipArchive.swift b/WireGuard/WireGuard/ZipArchive/ZipArchive.swift
index ad74d0e1..66c99f53 100644
--- a/WireGuard/WireGuard/ZipArchive/ZipArchive.swift
+++ b/WireGuard/WireGuard/ZipArchive/ZipArchive.swift
@@ -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.")
diff --git a/WireGuard/WireGuard/ZipArchive/ZipExporter.swift b/WireGuard/WireGuard/ZipArchive/ZipExporter.swift
index cdc9ac98..4c5fde86 100644
--- a/WireGuard/WireGuard/ZipArchive/ZipExporter.swift
+++ b/WireGuard/WireGuard/ZipArchive/ZipExporter.swift
@@ -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")
}
}
diff --git a/WireGuard/WireGuard/ZipArchive/ZipImporter.swift b/WireGuard/WireGuard/ZipArchive/ZipImporter.swift
index 523614b9..e87633c2 100644
--- a/WireGuard/WireGuard/ZipArchive/ZipImporter.swift
+++ b/WireGuard/WireGuard/ZipArchive/ZipImporter.swift
@@ -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.")
}
}
diff --git a/WireGuard/WireGuardNetworkExtension/DNSResolver.swift b/WireGuard/WireGuardNetworkExtension/DNSResolver.swift
index 9f85743d..a30b512d 100644
--- a/WireGuard/WireGuardNetworkExtension/DNSResolver.swift
+++ b/WireGuard/WireGuardNetworkExtension/DNSResolver.swift
@@ -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, _):
diff --git a/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift b/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift
index 6d3f3321..38182eef 100644
--- a/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift
+++ b/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift
@@ -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)))