mirror of
https://github.com/passepartoutvpn/wireguard-apple.git
synced 2025-02-08 00:42:03 +00:00
Most similar views now shared between ViewControllers
This commit is contained in:
parent
9a7571051f
commit
b06a43e2a2
@ -61,7 +61,7 @@ extension Endpoint {
|
|||||||
extension Endpoint: Codable {
|
extension Endpoint: Codable {
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
var container = encoder.singleValueContainer()
|
var container = encoder.singleValueContainer()
|
||||||
try container.encode(self.stringRepresentation())
|
try container.encode(stringRepresentation())
|
||||||
}
|
}
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
|
@ -8,19 +8,15 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */; };
|
5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */; };
|
||||||
5F45418A21C2D45B00994C13 /* TunnelEditKeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418921C2D45B00994C13 /* TunnelEditKeyValueCell.swift */; };
|
5F45418A21C2D45B00994C13 /* EditableKeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418921C2D45B00994C13 /* EditableKeyValueCell.swift */; };
|
||||||
5F45418C21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418B21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift */; };
|
5F45418C21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418B21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift */; };
|
||||||
5F45418E21C2D51100994C13 /* TunnelEditButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418D21C2D51100994C13 /* TunnelEditButtonCell.swift */; };
|
5F45419021C2D53800994C13 /* SwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418F21C2D53800994C13 /* SwitchCell.swift */; };
|
||||||
5F45419021C2D53800994C13 /* TunnelEditSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418F21C2D53800994C13 /* TunnelEditSwitchCell.swift */; };
|
5F45419221C2D55800994C13 /* CheckmarkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419121C2D55800994C13 /* CheckmarkCell.swift */; };
|
||||||
5F45419221C2D55800994C13 /* TunnelEditSectionListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419121C2D55800994C13 /* TunnelEditSectionListCell.swift */; };
|
5F45419821C2D60500994C13 /* KeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419721C2D60500994C13 /* KeyValueCell.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 */; };
|
5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419F21C2D6B700994C13 /* TunnelListCell.swift */; };
|
||||||
5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */; };
|
5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */; };
|
||||||
|
5F4541A621C4449E00994C13 /* ButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A521C4449E00994C13 /* ButtonCell.swift */; };
|
||||||
|
5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A821C451D100994C13 /* TunnelStatus.swift */; };
|
||||||
6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */; };
|
6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */; };
|
||||||
6F0068572191AFD200419BE9 /* ScrollableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0068562191AFD200419BE9 /* ScrollableLabel.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 */; };
|
6F5A2B4621AFDED40081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; };
|
||||||
@ -109,19 +105,15 @@
|
|||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewCell+Reuse.swift"; sourceTree = "<group>"; };
|
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>"; };
|
5F45418921C2D45B00994C13 /* EditableKeyValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableKeyValueCell.swift; sourceTree = "<group>"; };
|
||||||
5F45418B21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditReadOnlyKeyValueCell.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 /* SwitchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchCell.swift; sourceTree = "<group>"; };
|
||||||
5F45418F21C2D53800994C13 /* TunnelEditSwitchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditSwitchCell.swift; sourceTree = "<group>"; };
|
5F45419121C2D55800994C13 /* CheckmarkCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckmarkCell.swift; sourceTree = "<group>"; };
|
||||||
5F45419121C2D55800994C13 /* TunnelEditSectionListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditSectionListCell.swift; sourceTree = "<group>"; };
|
5F45419721C2D60500994C13 /* KeyValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueCell.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>"; };
|
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>"; };
|
5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BorderedTextButton.swift; sourceTree = "<group>"; };
|
||||||
|
5F4541A521C4449E00994C13 /* ButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCell.swift; sourceTree = "<group>"; };
|
||||||
|
5F4541A821C451D100994C13 /* TunnelStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatus.swift; sourceTree = "<group>"; };
|
||||||
6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CopyableLabelTableViewCell.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>"; };
|
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>"; };
|
6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extension.swift"; sourceTree = "<group>"; };
|
||||||
@ -136,7 +128,7 @@
|
|||||||
6F61F1EA21B937EF00483816 /* WireGuardResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardResult.swift; sourceTree = "<group>"; };
|
6F61F1EA21B937EF00483816 /* WireGuardResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardResult.swift; sourceTree = "<group>"; };
|
||||||
6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelViewModel.swift; sourceTree = "<group>"; };
|
6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelViewModel.swift; sourceTree = "<group>"; };
|
||||||
6F628C3E217F3413003482A3 /* DNSServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSServer.swift; sourceTree = "<group>"; };
|
6F628C3E217F3413003482A3 /* DNSServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSServer.swift; sourceTree = "<group>"; };
|
||||||
6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = "<group>"; };
|
6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TunnelDetailTableViewController.swift; path = TunnelDetail/TunnelDetailTableViewController.swift; sourceTree = "<group>"; };
|
||||||
6F689999218043390012E523 /* WireGuard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuard-Bridging-Header.h"; sourceTree = "<group>"; };
|
6F689999218043390012E523 /* WireGuard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuard-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
6F6899A42180447E0012E523 /* x25519.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = x25519.h; sourceTree = "<group>"; };
|
6F6899A42180447E0012E523 /* x25519.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = x25519.h; sourceTree = "<group>"; };
|
||||||
6F6899A52180447E0012E523 /* x25519.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x25519.c; sourceTree = "<group>"; };
|
6F6899A52180447E0012E523 /* x25519.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x25519.c; sourceTree = "<group>"; };
|
||||||
@ -202,51 +194,39 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
5F45418521C2C6AB00994C13 /* Settings */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */,
|
|
||||||
5F45419B21C2D64800994C13 /* SettingsButtonCell.swift */,
|
|
||||||
5F45419D21C2D66400994C13 /* SettingsKeyValueCell.swift */,
|
|
||||||
);
|
|
||||||
path = Settings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
5F45418621C2C6B400994C13 /* EditTunnel */ = {
|
5F45418621C2C6B400994C13 /* EditTunnel */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */,
|
6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */,
|
||||||
5F45418921C2D45B00994C13 /* TunnelEditKeyValueCell.swift */,
|
|
||||||
5F45418B21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift */,
|
5F45418B21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift */,
|
||||||
5F45418D21C2D51100994C13 /* TunnelEditButtonCell.swift */,
|
|
||||||
5F45418F21C2D53800994C13 /* TunnelEditSwitchCell.swift */,
|
|
||||||
5F45419121C2D55800994C13 /* TunnelEditSectionListCell.swift */,
|
|
||||||
);
|
);
|
||||||
path = EditTunnel;
|
path = EditTunnel;
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
5F45418821C2C6CC00994C13 /* TunnelList */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */,
|
6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */,
|
||||||
5F45419F21C2D6B700994C13 /* TunnelListCell.swift */,
|
5F45419F21C2D6B700994C13 /* TunnelListCell.swift */,
|
||||||
5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */,
|
|
||||||
);
|
);
|
||||||
path = TunnelList;
|
path = TunnelList;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
5F4541A721C44F5B00994C13 /* SharedViews */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */,
|
||||||
|
6F0068562191AFD200419BE9 /* ScrollableLabel.swift */,
|
||||||
|
5F45418F21C2D53800994C13 /* SwitchCell.swift */,
|
||||||
|
5F45419721C2D60500994C13 /* KeyValueCell.swift */,
|
||||||
|
5F4541A521C4449E00994C13 /* ButtonCell.swift */,
|
||||||
|
5F45419121C2D55800994C13 /* CheckmarkCell.swift */,
|
||||||
|
5F45418921C2D45B00994C13 /* EditableKeyValueCell.swift */,
|
||||||
|
5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */,
|
||||||
|
);
|
||||||
|
path = SharedViews;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
6F5D0C1B218352EF000F85AD /* WireGuardNetworkExtension */ = {
|
6F5D0C1B218352EF000F85AD /* WireGuardNetworkExtension */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -303,16 +283,15 @@
|
|||||||
6F7774DE217181B1006A79B3 /* iOS */ = {
|
6F7774DE217181B1006A79B3 /* iOS */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5F4541A721C44F5B00994C13 /* SharedViews */,
|
||||||
5F45418821C2C6CC00994C13 /* TunnelList */,
|
5F45418821C2C6CC00994C13 /* TunnelList */,
|
||||||
5F45418721C2C6C100994C13 /* TunnelDetail */,
|
|
||||||
5F45418621C2C6B400994C13 /* EditTunnel */,
|
5F45418621C2C6B400994C13 /* EditTunnel */,
|
||||||
5F45418521C2C6AB00994C13 /* Settings */,
|
|
||||||
6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */,
|
|
||||||
6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */,
|
6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */,
|
||||||
|
6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */,
|
||||||
|
6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */,
|
||||||
6F7774E0217181B1006A79B3 /* AppDelegate.swift */,
|
6F7774E0217181B1006A79B3 /* AppDelegate.swift */,
|
||||||
6F7774DF217181B1006A79B3 /* MainViewController.swift */,
|
6F7774DF217181B1006A79B3 /* MainViewController.swift */,
|
||||||
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */,
|
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */,
|
||||||
6F0068562191AFD200419BE9 /* ScrollableLabel.swift */,
|
|
||||||
5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */,
|
5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */,
|
||||||
);
|
);
|
||||||
path = iOS;
|
path = iOS;
|
||||||
@ -334,6 +313,7 @@
|
|||||||
children = (
|
children = (
|
||||||
6F7774EE21722D97006A79B3 /* TunnelsManager.swift */,
|
6F7774EE21722D97006A79B3 /* TunnelsManager.swift */,
|
||||||
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */,
|
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */,
|
||||||
|
5F4541A821C451D100994C13 /* TunnelStatus.swift */,
|
||||||
);
|
);
|
||||||
path = Tunnel;
|
path = Tunnel;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -692,47 +672,42 @@
|
|||||||
6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */,
|
6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */,
|
||||||
6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */,
|
6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */,
|
||||||
5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */,
|
5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */,
|
||||||
5F45419221C2D55800994C13 /* TunnelEditSectionListCell.swift in Sources */,
|
5F45419221C2D55800994C13 /* CheckmarkCell.swift in Sources */,
|
||||||
6FE254FF219C60290028284D /* ZipExporter.swift in Sources */,
|
6FE254FF219C60290028284D /* ZipExporter.swift in Sources */,
|
||||||
6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
|
6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
|
||||||
5F45418E21C2D51100994C13 /* TunnelEditButtonCell.swift in Sources */,
|
|
||||||
6F0068572191AFD200419BE9 /* ScrollableLabel.swift in Sources */,
|
6F0068572191AFD200419BE9 /* ScrollableLabel.swift in Sources */,
|
||||||
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
|
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
|
||||||
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
||||||
|
5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
|
||||||
6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */,
|
6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */,
|
||||||
5F45418A21C2D45B00994C13 /* TunnelEditKeyValueCell.swift in Sources */,
|
5F45418A21C2D45B00994C13 /* EditableKeyValueCell.swift in Sources */,
|
||||||
6F6899A62180447E0012E523 /* x25519.c in Sources */,
|
6F6899A62180447E0012E523 /* x25519.c in Sources */,
|
||||||
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */,
|
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */,
|
||||||
6FDEF80021863C0100D8FBF6 /* ioapi.c in Sources */,
|
6FDEF80021863C0100D8FBF6 /* ioapi.c in Sources */,
|
||||||
6FDEF7FC21863B6100D8FBF6 /* zip.c in Sources */,
|
6FDEF7FC21863B6100D8FBF6 /* zip.c in Sources */,
|
||||||
6F628C3F217F3413003482A3 /* DNSServer.swift in Sources */,
|
6F628C3F217F3413003482A3 /* DNSServer.swift in Sources */,
|
||||||
5F45419C21C2D64800994C13 /* SettingsButtonCell.swift in Sources */,
|
|
||||||
6F628C3D217F09E9003482A3 /* TunnelViewModel.swift in Sources */,
|
6F628C3D217F09E9003482A3 /* TunnelViewModel.swift in Sources */,
|
||||||
5F45419821C2D60500994C13 /* TunnelDetailKeyValueCell.swift in Sources */,
|
5F4541A621C4449E00994C13 /* ButtonCell.swift in Sources */,
|
||||||
|
5F45419821C2D60500994C13 /* KeyValueCell.swift in Sources */,
|
||||||
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */,
|
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */,
|
||||||
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */,
|
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */,
|
||||||
5F45418C21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift in Sources */,
|
5F45418C21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift in Sources */,
|
||||||
6FDEF8082187442100D8FBF6 /* WgQuickConfigFileWriter.swift in Sources */,
|
6FDEF8082187442100D8FBF6 /* WgQuickConfigFileWriter.swift in Sources */,
|
||||||
6FE254FB219C10800028284D /* ZipImporter.swift in Sources */,
|
6FE254FB219C10800028284D /* ZipImporter.swift in Sources */,
|
||||||
5F45419A21C2D61D00994C13 /* TunnelDetailStatusCell.swift in Sources */,
|
|
||||||
6F7774EA217229DB006A79B3 /* IPAddressRange.swift in Sources */,
|
6F7774EA217229DB006A79B3 /* IPAddressRange.swift in Sources */,
|
||||||
5F45419621C2D5DB00994C13 /* TunnelDetailButtonCell.swift in Sources */,
|
|
||||||
6F7774E82172020C006A79B3 /* Configuration.swift in Sources */,
|
6F7774E82172020C006A79B3 /* Configuration.swift in Sources */,
|
||||||
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */,
|
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */,
|
||||||
5F45419E21C2D66400994C13 /* SettingsKeyValueCell.swift in Sources */,
|
|
||||||
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */,
|
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */,
|
||||||
5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */,
|
5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */,
|
||||||
6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */,
|
6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */,
|
||||||
6F61F1EB21B937EF00483816 /* WireGuardResult.swift in Sources */,
|
6F61F1EB21B937EF00483816 /* WireGuardResult.swift in Sources */,
|
||||||
6F7774F321774263006A79B3 /* TunnelEditTableViewController.swift in Sources */,
|
6F7774F321774263006A79B3 /* TunnelEditTableViewController.swift in Sources */,
|
||||||
6FDEF802218646BA00D8FBF6 /* ZipArchive.swift in Sources */,
|
6FDEF802218646BA00D8FBF6 /* ZipArchive.swift in Sources */,
|
||||||
5F45419021C2D53800994C13 /* TunnelEditSwitchCell.swift in Sources */,
|
5F45419021C2D53800994C13 /* SwitchCell.swift in Sources */,
|
||||||
6FDEF806218725D200D8FBF6 /* SettingsTableViewController.swift in Sources */,
|
6FDEF806218725D200D8FBF6 /* SettingsTableViewController.swift in Sources */,
|
||||||
5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */,
|
5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */,
|
||||||
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
|
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
|
||||||
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */,
|
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */,
|
||||||
5F45419421C2D5C500994C13 /* TunnelDetailActivateOnDemandCell.swift in Sources */,
|
|
||||||
6FF717E521B2CB1E0045A474 /* InternetReachability.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -63,9 +63,9 @@ extension ActivateOnDemandSetting {
|
|||||||
}
|
}
|
||||||
self.activateOnDemandOption = activateOnDemandOption
|
self.activateOnDemandOption = activateOnDemandOption
|
||||||
if activateOnDemandOption == .none {
|
if activateOnDemandOption == .none {
|
||||||
self.isActivateOnDemandEnabled = false
|
isActivateOnDemandEnabled = false
|
||||||
} else {
|
} else {
|
||||||
self.isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled
|
isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
46
WireGuard/WireGuard/Tunnel/TunnelStatus.swift
Normal file
46
WireGuard/WireGuard/Tunnel/TunnelStatus.swift
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import NetworkExtension
|
||||||
|
|
||||||
|
@objc enum TunnelStatus: Int {
|
||||||
|
case inactive
|
||||||
|
case activating
|
||||||
|
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
|
||||||
|
|
||||||
|
init(from systemStatus: NEVPNStatus) {
|
||||||
|
switch systemStatus {
|
||||||
|
case .connected:
|
||||||
|
self = .active
|
||||||
|
case .connecting:
|
||||||
|
self = .activating
|
||||||
|
case .disconnected:
|
||||||
|
self = .inactive
|
||||||
|
case .disconnecting:
|
||||||
|
self = .deactivating
|
||||||
|
case .reasserting:
|
||||||
|
self = .reasserting
|
||||||
|
case .invalid:
|
||||||
|
self = .inactive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TunnelStatus: CustomDebugStringConvertible {
|
||||||
|
public var debugDescription: String {
|
||||||
|
switch self {
|
||||||
|
case .inactive: return "inactive"
|
||||||
|
case .activating: return "activating"
|
||||||
|
case .active: return "active"
|
||||||
|
case .deactivating: return "deactivating"
|
||||||
|
case .reasserting: return "reasserting"
|
||||||
|
case .restarting: return "restarting"
|
||||||
|
case .waiting: return "waiting"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -86,8 +86,8 @@ class TunnelsManager {
|
|||||||
private var statusObservationToken: AnyObject?
|
private var statusObservationToken: AnyObject?
|
||||||
|
|
||||||
init(tunnelProviders: [NETunnelProviderManager]) {
|
init(tunnelProviders: [NETunnelProviderManager]) {
|
||||||
self.tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { $0.name < $1.name }
|
tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { $0.name < $1.name }
|
||||||
self.startObservingTunnelStatuses()
|
startObservingTunnelStatuses()
|
||||||
}
|
}
|
||||||
|
|
||||||
static func create(completionHandler: @escaping (WireGuardResult<TunnelsManager>) -> Void) {
|
static func create(completionHandler: @escaping (WireGuardResult<TunnelsManager>) -> Void) {
|
||||||
@ -112,7 +112,7 @@ class TunnelsManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.tunnels.contains(where: { $0.name == tunnelName }) {
|
if tunnels.contains(where: { $0.name == tunnelName }) {
|
||||||
completionHandler(.failure(TunnelsManagerError.tunnelAlreadyExistsWithThatName))
|
completionHandler(.failure(TunnelsManagerError.tunnelAlreadyExistsWithThatName))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ class TunnelsManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let tail = tunnelConfigurations.dropFirst()
|
let tail = tunnelConfigurations.dropFirst()
|
||||||
self.add(tunnelConfiguration: head) { [weak self, tail] result in
|
add(tunnelConfiguration: head) { [weak self, tail] result in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful + (result.isSuccess ? 1 : 0), completionHandler: completionHandler)
|
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful + (result.isSuccess ? 1 : 0), completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
@ -168,7 +168,7 @@ class TunnelsManager {
|
|||||||
let tunnelProviderManager = tunnel.tunnelProvider
|
let tunnelProviderManager = tunnel.tunnelProvider
|
||||||
let isNameChanged = (tunnelName != tunnelProviderManager.localizedDescription)
|
let isNameChanged = (tunnelName != tunnelProviderManager.localizedDescription)
|
||||||
if isNameChanged {
|
if isNameChanged {
|
||||||
if self.tunnels.contains(where: { $0.name == tunnelName }) {
|
if tunnels.contains(where: { $0.name == tunnelName }) {
|
||||||
completionHandler(TunnelsManagerError.tunnelAlreadyExistsWithThatName)
|
completionHandler(TunnelsManagerError.tunnelAlreadyExistsWithThatName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -249,13 +249,13 @@ class TunnelsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func tunnel(named tunnelName: String) -> TunnelContainer? {
|
func tunnel(named tunnelName: String) -> TunnelContainer? {
|
||||||
return self.tunnels.first { $0.name == tunnelName }
|
return tunnels.first { $0.name == tunnelName }
|
||||||
}
|
}
|
||||||
|
|
||||||
func startActivation(of tunnel: TunnelContainer) {
|
func startActivation(of tunnel: TunnelContainer) {
|
||||||
guard tunnels.contains(tunnel) else { return } // Ensure it's not deleted
|
guard tunnels.contains(tunnel) else { return } // Ensure it's not deleted
|
||||||
guard tunnel.status == .inactive else {
|
guard tunnel.status == .inactive else {
|
||||||
self.activationDelegate?.tunnelActivationAttemptFailed(tunnel: tunnel, error: .tunnelIsNotInactive)
|
activationDelegate?.tunnelActivationAttemptFailed(tunnel: tunnel, error: .tunnelIsNotInactive)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +272,7 @@ class TunnelsManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnel.startActivation(activationDelegate: self.activationDelegate)
|
tunnel.startActivation(activationDelegate: activationDelegate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startDeactivation(of tunnel: TunnelContainer) {
|
func startDeactivation(of tunnel: TunnelContainer) {
|
||||||
@ -346,7 +346,7 @@ class TunnelsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
if let statusObservationToken = self.statusObservationToken {
|
if let statusObservationToken = statusObservationToken {
|
||||||
NotificationCenter.default.removeObserver(statusObservationToken)
|
NotificationCenter.default.removeObserver(statusObservationToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -371,8 +371,8 @@ class TunnelContainer: NSObject {
|
|||||||
self.activationTimer = activationTimer
|
self.activationTimer = activationTimer
|
||||||
RunLoop.main.add(activationTimer, forMode: .default)
|
RunLoop.main.add(activationTimer, forMode: .default)
|
||||||
} else {
|
} else {
|
||||||
self.activationTimer?.invalidate()
|
activationTimer?.invalidate()
|
||||||
self.activationTimer = nil
|
activationTimer = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -383,11 +383,11 @@ class TunnelContainer: NSObject {
|
|||||||
private var lastTunnelConnectionStatus: NEVPNStatus?
|
private var lastTunnelConnectionStatus: NEVPNStatus?
|
||||||
|
|
||||||
init(tunnel: NETunnelProviderManager) {
|
init(tunnel: NETunnelProviderManager) {
|
||||||
self.name = tunnel.localizedDescription ?? "Unnamed"
|
name = tunnel.localizedDescription ?? "Unnamed"
|
||||||
let status = TunnelStatus(from: tunnel.connection.status)
|
let status = TunnelStatus(from: tunnel.connection.status)
|
||||||
self.status = status
|
self.status = status
|
||||||
self.isActivateOnDemandEnabled = tunnel.isOnDemandEnabled
|
isActivateOnDemandEnabled = tunnel.isOnDemandEnabled
|
||||||
self.tunnelProvider = tunnel
|
tunnelProvider = tunnel
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,9 +400,9 @@ class TunnelContainer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func refreshStatus() {
|
func refreshStatus() {
|
||||||
let status = TunnelStatus(from: self.tunnelProvider.connection.status)
|
let status = TunnelStatus(from: tunnelProvider.connection.status)
|
||||||
self.status = status
|
self.status = status
|
||||||
self.isActivateOnDemandEnabled = self.tunnelProvider.isOnDemandEnabled
|
isActivateOnDemandEnabled = tunnelProvider.isOnDemandEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
//swiftlint:disable:next function_body_length
|
//swiftlint:disable:next function_body_length
|
||||||
@ -413,9 +413,9 @@ class TunnelContainer: NSObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wg_log(.debug, message: "startActivation: Entering (tunnel: \(self.name))")
|
wg_log(.debug, message: "startActivation: Entering (tunnel: \(name))")
|
||||||
|
|
||||||
self.status = .activating // Ensure that no other tunnel can attempt activation until this tunnel is done trying
|
status = .activating // Ensure that no other tunnel can attempt activation until this tunnel is done trying
|
||||||
|
|
||||||
guard tunnelProvider.isEnabled else {
|
guard tunnelProvider.isEnabled else {
|
||||||
// In case the tunnel had gotten disabled, re-enable and save it,
|
// In case the tunnel had gotten disabled, re-enable and save it,
|
||||||
@ -440,14 +440,14 @@ class TunnelContainer: NSObject {
|
|||||||
// Start the tunnel
|
// Start the tunnel
|
||||||
do {
|
do {
|
||||||
wg_log(.debug, staticMessage: "startActivation: Starting tunnel")
|
wg_log(.debug, staticMessage: "startActivation: Starting tunnel")
|
||||||
self.isAttemptingActivation = true
|
isAttemptingActivation = true
|
||||||
let activationAttemptId = UUID().uuidString
|
let activationAttemptId = UUID().uuidString
|
||||||
self.activationAttemptId = activationAttemptId
|
self.activationAttemptId = activationAttemptId
|
||||||
try (tunnelProvider.connection as? NETunnelProviderSession)?.startTunnel(options: ["activationAttemptId": activationAttemptId])
|
try (tunnelProvider.connection as? NETunnelProviderSession)?.startTunnel(options: ["activationAttemptId": activationAttemptId])
|
||||||
wg_log(.debug, staticMessage: "startActivation: Success")
|
wg_log(.debug, staticMessage: "startActivation: Success")
|
||||||
activationDelegate?.tunnelActivationAttemptSucceeded(tunnel: self)
|
activationDelegate?.tunnelActivationAttemptSucceeded(tunnel: self)
|
||||||
} catch let error {
|
} catch let error {
|
||||||
self.isAttemptingActivation = false
|
isAttemptingActivation = false
|
||||||
guard let systemError = error as? NEVPNError else {
|
guard let systemError = error as? NEVPNError else {
|
||||||
wg_log(.error, message: "Failed to activate tunnel: Error: \(error)")
|
wg_log(.error, message: "Failed to activate tunnel: Error: \(error)")
|
||||||
status = .inactive
|
status = .inactive
|
||||||
@ -481,47 +481,6 @@ class TunnelContainer: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc enum TunnelStatus: Int {
|
|
||||||
case inactive
|
|
||||||
case activating
|
|
||||||
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
|
|
||||||
|
|
||||||
init(from systemStatus: NEVPNStatus) {
|
|
||||||
switch systemStatus {
|
|
||||||
case .connected:
|
|
||||||
self = .active
|
|
||||||
case .connecting:
|
|
||||||
self = .activating
|
|
||||||
case .disconnected:
|
|
||||||
self = .inactive
|
|
||||||
case .disconnecting:
|
|
||||||
self = .deactivating
|
|
||||||
case .reasserting:
|
|
||||||
self = .reasserting
|
|
||||||
case .invalid:
|
|
||||||
self = .inactive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TunnelStatus: CustomDebugStringConvertible {
|
|
||||||
public var debugDescription: String {
|
|
||||||
switch self {
|
|
||||||
case .inactive: return "inactive"
|
|
||||||
case .activating: return "activating"
|
|
||||||
case .active: return "active"
|
|
||||||
case .deactivating: return "deactivating"
|
|
||||||
case .reasserting: return "reasserting"
|
|
||||||
case .restarting: return "restarting"
|
|
||||||
case .waiting: return "waiting"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension NEVPNStatus: CustomDebugStringConvertible {
|
extension NEVPNStatus: CustomDebugStringConvertible {
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -15,7 +15,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
Logger.configureGlobal(withFilePath: FileManager.appLogFileURL?.path)
|
Logger.configureGlobal(withFilePath: FileManager.appLogFileURL?.path)
|
||||||
|
|
||||||
let window = UIWindow(frame: UIScreen.main.bounds)
|
let window = UIWindow(frame: UIScreen.main.bounds)
|
||||||
window.backgroundColor = UIColor.white
|
window.backgroundColor = .white
|
||||||
self.window = window
|
self.window = window
|
||||||
|
|
||||||
let mainVC = MainViewController()
|
let mainVC = MainViewController()
|
||||||
|
@ -6,29 +6,36 @@ import UIKit
|
|||||||
class TunnelEditReadOnlyKeyValueCell: CopyableLabelTableViewCell {
|
class TunnelEditReadOnlyKeyValueCell: CopyableLabelTableViewCell {
|
||||||
var key: String {
|
var key: String {
|
||||||
get { return keyLabel.text ?? "" }
|
get { return keyLabel.text ?? "" }
|
||||||
set(value) {keyLabel.text = value }
|
set(value) { keyLabel.text = value }
|
||||||
}
|
}
|
||||||
var value: String {
|
var value: String {
|
||||||
get { return valueLabel.text }
|
get { return valueLabel.text }
|
||||||
set(value) { valueLabel.text = value }
|
set(value) { valueLabel.text = value }
|
||||||
}
|
}
|
||||||
|
|
||||||
let keyLabel: UILabel
|
override var textToCopy: String? {
|
||||||
let valueLabel: ScrollableLabel
|
return valueLabel.text
|
||||||
|
}
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
let keyLabel: UILabel = {
|
||||||
keyLabel = UILabel()
|
let keyLabel = UILabel()
|
||||||
keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
|
keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
|
||||||
keyLabel.adjustsFontForContentSizeCategory = true
|
keyLabel.adjustsFontForContentSizeCategory = true
|
||||||
valueLabel = ScrollableLabel()
|
keyLabel.textColor = .gray
|
||||||
|
return keyLabel
|
||||||
|
}()
|
||||||
|
|
||||||
|
let valueLabel: ScrollableLabel = {
|
||||||
|
let valueLabel = ScrollableLabel()
|
||||||
valueLabel.label.font = UIFont.preferredFont(forTextStyle: .body)
|
valueLabel.label.font = UIFont.preferredFont(forTextStyle: .body)
|
||||||
valueLabel.label.adjustsFontForContentSizeCategory = true
|
valueLabel.label.adjustsFontForContentSizeCategory = true
|
||||||
|
valueLabel.textColor = .gray
|
||||||
|
return valueLabel
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
|
||||||
keyLabel.textColor = UIColor.gray
|
|
||||||
valueLabel.textColor = UIColor.gray
|
|
||||||
|
|
||||||
contentView.addSubview(keyLabel)
|
contentView.addSubview(keyLabel)
|
||||||
keyLabel.translatesAutoresizingMaskIntoConstraints = false
|
keyLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
keyLabel.textAlignment = .right
|
keyLabel.textAlignment = .right
|
||||||
@ -55,10 +62,6 @@ class TunnelEditReadOnlyKeyValueCell: CopyableLabelTableViewCell {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
override var textToCopy: String? {
|
|
||||||
return self.valueLabel.text
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
@ -72,18 +72,18 @@ class TunnelEditTableViewController: UITableViewController {
|
|||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
self.title = tunnel == nil ? "New configuration" : "Edit configuration"
|
title = tunnel == nil ? "New configuration" : "Edit configuration"
|
||||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveTapped))
|
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveTapped))
|
||||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelTapped))
|
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelTapped))
|
||||||
|
|
||||||
self.tableView.estimatedRowHeight = 44
|
tableView.estimatedRowHeight = 44
|
||||||
self.tableView.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
|
|
||||||
self.tableView.register(TunnelEditKeyValueCell.self)
|
tableView.register(EditableKeyValueCell.self)
|
||||||
self.tableView.register(TunnelEditReadOnlyKeyValueCell.self)
|
tableView.register(TunnelEditReadOnlyKeyValueCell.self)
|
||||||
self.tableView.register(TunnelEditButtonCell.self)
|
tableView.register(ButtonCell.self)
|
||||||
self.tableView.register(TunnelEditSwitchCell.self)
|
tableView.register(SwitchCell.self)
|
||||||
self.tableView.register(TunnelEditSelectionListCell.self)
|
tableView.register(CheckmarkCell.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadSections() {
|
private func loadSections() {
|
||||||
@ -95,13 +95,13 @@ class TunnelEditTableViewController: UITableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func saveTapped() {
|
@objc func saveTapped() {
|
||||||
self.tableView.endEditing(false)
|
tableView.endEditing(false)
|
||||||
let tunnelSaveResult = tunnelViewModel.save()
|
let tunnelSaveResult = tunnelViewModel.save()
|
||||||
switch tunnelSaveResult {
|
switch tunnelSaveResult {
|
||||||
case .error(let errorMessage):
|
case .error(let errorMessage):
|
||||||
let erroringConfiguration = (tunnelViewModel.interfaceData.validatedConfiguration == nil) ? "Interface" : "Peer"
|
let erroringConfiguration = (tunnelViewModel.interfaceData.validatedConfiguration == nil) ? "Interface" : "Peer"
|
||||||
ErrorPresenter.showErrorAlert(title: "Invalid \(erroringConfiguration)", message: errorMessage, from: self)
|
ErrorPresenter.showErrorAlert(title: "Invalid \(erroringConfiguration)", message: errorMessage, from: self)
|
||||||
self.tableView.reloadData() // Highlight erroring fields
|
tableView.reloadData() // Highlight erroring fields
|
||||||
case .saved(let tunnelConfiguration):
|
case .saved(let tunnelConfiguration):
|
||||||
if let tunnel = tunnel {
|
if let tunnel = tunnel {
|
||||||
// We're modifying an existing tunnel
|
// We're modifying an existing tunnel
|
||||||
@ -133,7 +133,7 @@ class TunnelEditTableViewController: UITableViewController {
|
|||||||
|
|
||||||
@objc func cancelTapped() {
|
@objc func cancelTapped() {
|
||||||
dismiss(animated: true, completion: nil)
|
dismiss(animated: true, completion: nil)
|
||||||
self.delegate?.tunnelEditingCancelled()
|
delegate?.tunnelEditingCancelled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +201,7 @@ extension TunnelEditTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func generateKeyPairCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell {
|
private func generateKeyPairCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell {
|
||||||
let cell: TunnelEditButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.buttonText = field.rawValue
|
cell.buttonText = field.rawValue
|
||||||
cell.onTapped = { [weak self] in
|
cell.onTapped = { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
@ -225,7 +225,7 @@ extension TunnelEditTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func interfaceFieldKeyValueCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell {
|
private func interfaceFieldKeyValueCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell {
|
||||||
let cell: TunnelEditKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: EditableKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.key = field.rawValue
|
cell.key = field.rawValue
|
||||||
|
|
||||||
switch field {
|
switch field {
|
||||||
@ -287,7 +287,7 @@ extension TunnelEditTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func deletePeerCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell {
|
private func deletePeerCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell {
|
||||||
let cell: TunnelEditButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.buttonText = field.rawValue
|
cell.buttonText = field.rawValue
|
||||||
cell.hasDestructiveAction = true
|
cell.hasDestructiveAction = true
|
||||||
cell.onTapped = { [weak self, weak peerData] in
|
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 {
|
private func excludePrivateIPsCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell {
|
||||||
let cell: TunnelEditSwitchCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.message = field.rawValue
|
cell.message = field.rawValue
|
||||||
cell.isEnabled = peerData.shouldAllowExcludePrivateIPsControl
|
cell.isEnabled = peerData.shouldAllowExcludePrivateIPsControl
|
||||||
cell.isOn = peerData.excludePrivateIPsValue
|
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 {
|
private func peerFieldKeyValueCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell {
|
||||||
let cell: TunnelEditKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: EditableKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.key = field.rawValue
|
cell.key = field.rawValue
|
||||||
|
|
||||||
switch field {
|
switch field {
|
||||||
@ -377,7 +377,7 @@ extension TunnelEditTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func addPeerCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
private func addPeerCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell: TunnelEditButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.buttonText = "Add peer"
|
cell.buttonText = "Add peer"
|
||||||
cell.onTapped = { [weak self] in
|
cell.onTapped = { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
@ -398,7 +398,7 @@ extension TunnelEditTableViewController {
|
|||||||
|
|
||||||
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
if indexPath.row == 0 {
|
if indexPath.row == 0 {
|
||||||
let cell: TunnelEditSwitchCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.message = "Activate on demand"
|
cell.message = "Activate on demand"
|
||||||
cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled
|
cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled
|
||||||
cell.onSwitchToggled = { [weak self] isOn in
|
cell.onSwitchToggled = { [weak self] isOn in
|
||||||
@ -419,7 +419,7 @@ extension TunnelEditTableViewController {
|
|||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
} else {
|
} else {
|
||||||
let cell: TunnelEditSelectionListCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: CheckmarkCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
let rowOption = activateOnDemandOptions[indexPath.row - 1]
|
let rowOption = activateOnDemandOptions[indexPath.row - 1]
|
||||||
let selectedOption = activateOnDemandSetting.activateOnDemandOption
|
let selectedOption = activateOnDemandSetting.activateOnDemandOption
|
||||||
assert(selectedOption != .none)
|
assert(selectedOption != .none)
|
||||||
@ -455,7 +455,7 @@ extension TunnelEditTableViewController {
|
|||||||
alert.popoverPresentationController?.sourceView = sourceView
|
alert.popoverPresentationController?.sourceView = sourceView
|
||||||
alert.popoverPresentationController?.sourceRect = sourceView.bounds
|
alert.popoverPresentationController?.sourceRect = sourceView.bounds
|
||||||
|
|
||||||
self.present(alert, animated: true, completion: nil)
|
present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,20 +12,20 @@ class MainViewController: UISplitViewController {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
let detailVC = UIViewController()
|
let detailVC = UIViewController()
|
||||||
detailVC.view.backgroundColor = UIColor.white
|
detailVC.view.backgroundColor = .white
|
||||||
let detailNC = UINavigationController(rootViewController: detailVC)
|
let detailNC = UINavigationController(rootViewController: detailVC)
|
||||||
|
|
||||||
let masterVC = TunnelsListTableViewController()
|
let masterVC = TunnelsListTableViewController()
|
||||||
let masterNC = UINavigationController(rootViewController: masterVC)
|
let masterNC = UINavigationController(rootViewController: masterVC)
|
||||||
|
|
||||||
self.tunnelsListVC = masterVC
|
tunnelsListVC = masterVC
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
self.viewControllers = [ masterNC, detailNC ]
|
viewControllers = [ masterNC, detailNC ]
|
||||||
|
|
||||||
// State restoration
|
// State restoration
|
||||||
self.restorationIdentifier = "MainVC"
|
restorationIdentifier = "MainVC"
|
||||||
masterNC.restorationIdentifier = "MasterNC"
|
masterNC.restorationIdentifier = "MasterNC"
|
||||||
detailNC.restorationIdentifier = "DetailNC"
|
detailNC.restorationIdentifier = "DetailNC"
|
||||||
}
|
}
|
||||||
@ -35,10 +35,10 @@ class MainViewController: UISplitViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
self.delegate = self
|
delegate = self
|
||||||
|
|
||||||
// On iPad, always show both masterVC and detailVC, even in portrait mode, like the Settings app
|
// On iPad, always show both masterVC and detailVC, even in portrait mode, like the Settings app
|
||||||
self.preferredDisplayMode = .allVisible
|
preferredDisplayMode = .allVisible
|
||||||
|
|
||||||
// Create the tunnels manager, and when it's ready, inform tunnelsListVC
|
// Create the tunnels manager, and when it's ready, inform tunnelsListVC
|
||||||
TunnelsManager.create { [weak self] result in
|
TunnelsManager.create { [weak self] result in
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
import CoreData
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
protocol QRScanViewControllerDelegate: class {
|
protocol QRScanViewControllerDelegate: class {
|
||||||
@ -18,8 +17,8 @@ class QRScanViewController: UIViewController {
|
|||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
self.title = "Scan QR code"
|
title = "Scan QR code"
|
||||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelTapped))
|
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelTapped))
|
||||||
|
|
||||||
let tipLabel = UILabel()
|
let tipLabel = UILabel()
|
||||||
tipLabel.text = "Tip: Generate with `qrencode -t ansiutf8 < tunnel.conf`"
|
tipLabel.text = "Tip: Generate with `qrencode -t ansiutf8 < tunnel.conf`"
|
||||||
@ -102,7 +101,7 @@ class QRScanViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
previewLayer?.frame = self.view.bounds
|
previewLayer?.frame = view.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanDidComplete(withCode code: String) {
|
func scanDidComplete(withCode code: String) {
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
// 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 = ""
|
|
||||||
}
|
|
||||||
}
|
|
@ -40,15 +40,15 @@ class SettingsTableViewController: UITableViewController {
|
|||||||
tableView.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
tableView.allowsSelection = false
|
tableView.allowsSelection = false
|
||||||
|
|
||||||
tableView.register(SettingsKeyValueCell.self)
|
tableView.register(KeyValueCell.self)
|
||||||
tableView.register(SettingsButtonCell.self)
|
tableView.register(ButtonCell.self)
|
||||||
|
|
||||||
tableView.tableFooterView = UIImageView(image: UIImage(named: "wireguard.pdf"))
|
tableView.tableFooterView = UIImageView(image: UIImage(named: "wireguard.pdf"))
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
super.viewDidLayoutSubviews()
|
super.viewDidLayoutSubviews()
|
||||||
guard let logo = self.tableView.tableFooterView else { return }
|
guard let logo = tableView.tableFooterView else { return }
|
||||||
|
|
||||||
let bottomPadding = max(tableView.layoutMargins.bottom, 10)
|
let bottomPadding = max(tableView.layoutMargins.bottom, 10)
|
||||||
let fullHeight = max(tableView.contentSize.height, tableView.bounds.size.height - tableView.layoutMargins.top - bottomPadding)
|
let fullHeight = max(tableView.contentSize.height, tableView.bounds.size.height - tableView.layoutMargins.top - bottomPadding)
|
||||||
@ -167,7 +167,8 @@ extension SettingsTableViewController {
|
|||||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let field = settingsFieldsBySection[indexPath.section][indexPath.row]
|
let field = settingsFieldsBySection[indexPath.section][indexPath.row]
|
||||||
if field == .iosAppVersion || field == .goBackendVersion {
|
if field == .iosAppVersion || field == .goBackendVersion {
|
||||||
let cell: SettingsKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
|
cell.copyableGesture = false
|
||||||
cell.key = field.rawValue
|
cell.key = field.rawValue
|
||||||
if field == .iosAppVersion {
|
if field == .iosAppVersion {
|
||||||
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version"
|
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version"
|
||||||
@ -180,7 +181,7 @@ extension SettingsTableViewController {
|
|||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
} else if field == .exportZipArchive {
|
} else if field == .exportZipArchive {
|
||||||
let cell: SettingsButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.buttonText = field.rawValue
|
cell.buttonText = field.rawValue
|
||||||
cell.onTapped = { [weak self] in
|
cell.onTapped = { [weak self] in
|
||||||
self?.exportConfigurationsAsZipFile(sourceView: cell.button)
|
self?.exportConfigurationsAsZipFile(sourceView: cell.button)
|
||||||
@ -188,7 +189,7 @@ extension SettingsTableViewController {
|
|||||||
return cell
|
return cell
|
||||||
} else {
|
} else {
|
||||||
assert(field == .exportLogFile)
|
assert(field == .exportLogFile)
|
||||||
let cell: SettingsButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.buttonText = field.rawValue
|
cell.buttonText = field.rawValue
|
||||||
cell.onTapped = { [weak self] in
|
cell.onTapped = { [weak self] in
|
||||||
self?.exportLogForLastActivatedTunnel(sourceView: cell.button)
|
self?.exportLogForLastActivatedTunnel(sourceView: cell.button)
|
@ -33,8 +33,8 @@ class BorderedTextButton: UIView {
|
|||||||
addSubview(button)
|
addSubview(button)
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
button.centerXAnchor.constraint(equalTo: self.centerXAnchor),
|
button.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||||
button.centerYAnchor.constraint(equalTo: self.centerYAnchor)
|
button.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
|
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
|
@ -3,14 +3,14 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class TunnelEditButtonCell: UITableViewCell {
|
class ButtonCell: UITableViewCell {
|
||||||
var buttonText: String {
|
var buttonText: String {
|
||||||
get { return button.title(for: .normal) ?? "" }
|
get { return button.title(for: .normal) ?? "" }
|
||||||
set(value) { button.setTitle(value, for: .normal) }
|
set(value) { button.setTitle(value, for: .normal) }
|
||||||
}
|
}
|
||||||
var hasDestructiveAction: Bool {
|
var hasDestructiveAction: Bool {
|
||||||
get { return button.tintColor == UIColor.red }
|
get { return button.tintColor == .red }
|
||||||
set(value) { button.tintColor = value ? UIColor.red : buttonStandardTintColor }
|
set(value) { button.tintColor = value ? .red : buttonStandardTintColor }
|
||||||
}
|
}
|
||||||
var onTapped: (() -> Void)?
|
var onTapped: (() -> Void)?
|
||||||
|
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class TunnelEditSelectionListCell: UITableViewCell {
|
class CheckmarkCell: UITableViewCell {
|
||||||
var message: String {
|
var message: String {
|
||||||
get { return textLabel?.text ?? "" }
|
get { return textLabel?.text ?? "" }
|
||||||
set(value) { textLabel!.text = value }
|
set(value) { textLabel!.text = value }
|
||||||
@ -13,6 +13,7 @@ class TunnelEditSelectionListCell: UITableViewCell {
|
|||||||
accessoryType = isChecked ? .checkmark : .none
|
accessoryType = isChecked ? .checkmark : .none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
isChecked = false
|
isChecked = false
|
||||||
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
@ -17,13 +17,13 @@ class CopyableLabelTableViewCell: UITableViewCell {
|
|||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
|
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
|
||||||
self.addGestureRecognizer(gestureRecognizer)
|
addGestureRecognizer(gestureRecognizer)
|
||||||
self.isUserInteractionEnabled = true
|
isUserInteractionEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UIGestureRecognizer
|
// MARK: - UIGestureRecognizer
|
||||||
@objc func handleTapGesture(_ recognizer: UIGestureRecognizer) {
|
@objc func handleTapGesture(_ recognizer: UIGestureRecognizer) {
|
||||||
if !self.copyableGesture {
|
if !copyableGesture {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard recognizer.state == .recognized else { return }
|
guard recognizer.state == .recognized else { return }
|
||||||
@ -31,7 +31,7 @@ class CopyableLabelTableViewCell: UITableViewCell {
|
|||||||
if let recognizerView = recognizer.view,
|
if let recognizerView = recognizer.view,
|
||||||
let recognizerSuperView = recognizerView.superview, recognizerView.becomeFirstResponder() {
|
let recognizerSuperView = recognizerView.superview, recognizerView.becomeFirstResponder() {
|
||||||
let menuController = UIMenuController.shared
|
let menuController = UIMenuController.shared
|
||||||
menuController.setTargetRect(self.detailTextLabel?.frame ?? recognizerView.frame, in: self.detailTextLabel?.superview ?? recognizerSuperView)
|
menuController.setTargetRect(detailTextLabel?.frame ?? recognizerView.frame, in: detailTextLabel?.superview ?? recognizerSuperView)
|
||||||
menuController.setMenuVisible(true, animated: true)
|
menuController.setMenuVisible(true, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,6 +50,6 @@ class CopyableLabelTableViewCell: UITableViewCell {
|
|||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
self.copyableGesture = true
|
copyableGesture = true
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,10 +3,10 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class TunnelEditKeyValueCell: UITableViewCell {
|
class EditableKeyValueCell: UITableViewCell {
|
||||||
var key: String {
|
var key: String {
|
||||||
get { return keyLabel.text ?? "" }
|
get { return keyLabel.text ?? "" }
|
||||||
set(value) {keyLabel.text = value }
|
set(value) { keyLabel.text = value }
|
||||||
}
|
}
|
||||||
var value: String {
|
var value: String {
|
||||||
get { return valueTextField.text ?? "" }
|
get { return valueTextField.text ?? "" }
|
||||||
@ -89,7 +89,7 @@ class TunnelEditKeyValueCell: UITableViewCell {
|
|||||||
|
|
||||||
func configureForContentSize() {
|
func configureForContentSize() {
|
||||||
var constraints = [NSLayoutConstraint]()
|
var constraints = [NSLayoutConstraint]()
|
||||||
if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
|
if traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
|
||||||
// Stack vertically
|
// Stack vertically
|
||||||
if !isStackedVertically {
|
if !isStackedVertically {
|
||||||
constraints = [
|
constraints = [
|
||||||
@ -113,9 +113,9 @@ class TunnelEditKeyValueCell: UITableViewCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !constraints.isEmpty {
|
if !constraints.isEmpty {
|
||||||
NSLayoutConstraint.deactivate(self.contentSizeBasedConstraints)
|
NSLayoutConstraint.deactivate(contentSizeBasedConstraints)
|
||||||
NSLayoutConstraint.activate(constraints)
|
NSLayoutConstraint.activate(constraints)
|
||||||
self.contentSizeBasedConstraints = constraints
|
contentSizeBasedConstraints = constraints
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ class TunnelEditKeyValueCell: UITableViewCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TunnelEditKeyValueCell: UITextFieldDelegate {
|
extension EditableKeyValueCell: UITextFieldDelegate {
|
||||||
func textFieldDidBeginEditing(_ textField: UITextField) {
|
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||||
textFieldValueOnBeginEditing = textField.text ?? ""
|
textFieldValueOnBeginEditing = textField.text ?? ""
|
||||||
isValueValid = true
|
isValueValid = true
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class TunnelDetailKeyValueCell: CopyableLabelTableViewCell {
|
class KeyValueCell: CopyableLabelTableViewCell {
|
||||||
var key: String {
|
var key: String {
|
||||||
get { return keyLabel.text ?? "" }
|
get { return keyLabel.text ?? "" }
|
||||||
set(value) { keyLabel.text = value }
|
set(value) { keyLabel.text = value }
|
||||||
@ -14,7 +14,7 @@ class TunnelDetailKeyValueCell: CopyableLabelTableViewCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override var textToCopy: String? {
|
override var textToCopy: String? {
|
||||||
return self.valueLabel.text
|
return valueLabel.text
|
||||||
}
|
}
|
||||||
|
|
||||||
let keyLabel: UILabel = {
|
let keyLabel: UILabel = {
|
||||||
@ -64,7 +64,7 @@ class TunnelDetailKeyValueCell: CopyableLabelTableViewCell {
|
|||||||
|
|
||||||
func configureForContentSize() {
|
func configureForContentSize() {
|
||||||
var constraints = [NSLayoutConstraint]()
|
var constraints = [NSLayoutConstraint]()
|
||||||
if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
|
if traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
|
||||||
// Stack vertically
|
// Stack vertically
|
||||||
if !isStackedVertically {
|
if !isStackedVertically {
|
||||||
constraints = [
|
constraints = [
|
||||||
@ -88,9 +88,9 @@ class TunnelDetailKeyValueCell: CopyableLabelTableViewCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !constraints.isEmpty {
|
if !constraints.isEmpty {
|
||||||
NSLayoutConstraint.deactivate(self.contentSizeBasedConstraints)
|
NSLayoutConstraint.deactivate(contentSizeBasedConstraints)
|
||||||
NSLayoutConstraint.activate(constraints)
|
NSLayoutConstraint.activate(constraints)
|
||||||
self.contentSizeBasedConstraints = constraints
|
contentSizeBasedConstraints = constraints
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,10 +3,10 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class TunnelEditSwitchCell: UITableViewCell {
|
class SwitchCell: UITableViewCell {
|
||||||
var message: String {
|
var message: String {
|
||||||
get { return textLabel?.text ?? "" }
|
get { return textLabel?.text ?? "" }
|
||||||
set(value) { textLabel!.text = value }
|
set(value) { textLabel?.text = value }
|
||||||
}
|
}
|
||||||
var isOn: Bool {
|
var isOn: Bool {
|
||||||
get { return switchView.isOn }
|
get { return switchView.isOn }
|
||||||
@ -16,31 +16,32 @@ class TunnelEditSwitchCell: UITableViewCell {
|
|||||||
get { return switchView.isEnabled }
|
get { return switchView.isEnabled }
|
||||||
set(value) {
|
set(value) {
|
||||||
switchView.isEnabled = value
|
switchView.isEnabled = value
|
||||||
textLabel?.textColor = value ? UIColor.black : UIColor.gray
|
textLabel?.textColor = value ? .black : .gray
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var onSwitchToggled: ((Bool) -> Void)?
|
var onSwitchToggled: ((Bool) -> Void)?
|
||||||
|
|
||||||
let switchView: UISwitch
|
let switchView = UISwitch()
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
switchView = UISwitch()
|
|
||||||
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
||||||
|
|
||||||
accessoryView = switchView
|
accessoryView = switchView
|
||||||
switchView.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
|
switchView.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func switchToggled() {
|
|
||||||
onSwitchToggled?(switchView.isOn)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func switchToggled() {
|
||||||
|
onSwitchToggled?(switchView.isOn)
|
||||||
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
|
isEnabled = true
|
||||||
message = ""
|
message = ""
|
||||||
isOn = false
|
isOn = false
|
||||||
}
|
}
|
@ -1,40 +0,0 @@
|
|||||||
// 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 = ""
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
// 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.asyncAfter(deadline: .now() + .milliseconds(200)) { [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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,6 +29,8 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
let tunnel: TunnelContainer
|
let tunnel: TunnelContainer
|
||||||
var tunnelViewModel: TunnelViewModel
|
var tunnelViewModel: TunnelViewModel
|
||||||
private var sections = [Section]()
|
private var sections = [Section]()
|
||||||
|
private var onDemandStatusObservervationToken: AnyObject?
|
||||||
|
private var statusObservervationToken: AnyObject?
|
||||||
|
|
||||||
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) {
|
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) {
|
||||||
self.tunnelsManager = tunnelsManager
|
self.tunnelsManager = tunnelsManager
|
||||||
@ -42,21 +44,25 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
onDemandStatusObservervationToken = nil
|
||||||
|
statusObservervationToken = nil
|
||||||
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
self.title = tunnelViewModel.interfaceData[.name]
|
title = tunnelViewModel.interfaceData[.name]
|
||||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(editTapped))
|
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(editTapped))
|
||||||
|
|
||||||
self.tableView.estimatedRowHeight = 44
|
tableView.estimatedRowHeight = 44
|
||||||
self.tableView.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
self.tableView.allowsSelection = false
|
tableView.allowsSelection = false
|
||||||
self.tableView.register(TunnelDetailStatusCell.self)
|
tableView.register(SwitchCell.self)
|
||||||
self.tableView.register(TunnelDetailKeyValueCell.self)
|
tableView.register(KeyValueCell.self)
|
||||||
self.tableView.register(TunnelDetailButtonCell.self)
|
tableView.register(ButtonCell.self)
|
||||||
self.tableView.register(TunnelDetailActivateOnDemandCell.self)
|
|
||||||
|
|
||||||
// State restoration
|
// State restoration
|
||||||
self.restorationIdentifier = "TunnelDetailVC:\(tunnel.name)"
|
restorationIdentifier = "TunnelDetailVC:\(tunnel.name)"
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadSections() {
|
private func loadSections() {
|
||||||
@ -76,8 +82,7 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
present(editNC, animated: true)
|
present(editNC, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView,
|
func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView, onConfirmed: @escaping (() -> Void)) {
|
||||||
onConfirmed: @escaping (() -> Void)) {
|
|
||||||
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in
|
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in
|
||||||
onConfirmed()
|
onConfirmed()
|
||||||
}
|
}
|
||||||
@ -90,7 +95,7 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
alert.popoverPresentationController?.sourceView = sourceView
|
alert.popoverPresentationController?.sourceView = sourceView
|
||||||
alert.popoverPresentationController?.sourceRect = sourceView.bounds
|
alert.popoverPresentationController?.sourceRect = sourceView.bounds
|
||||||
|
|
||||||
self.present(alert, animated: true, completion: nil)
|
present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,8 +105,8 @@ extension TunnelDetailTableViewController: TunnelEditTableViewControllerDelegate
|
|||||||
func tunnelSaved(tunnel: TunnelContainer) {
|
func tunnelSaved(tunnel: TunnelContainer) {
|
||||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration())
|
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration())
|
||||||
loadSections()
|
loadSections()
|
||||||
self.title = tunnel.name
|
title = tunnel.name
|
||||||
self.tableView.reloadData()
|
tableView.reloadData()
|
||||||
}
|
}
|
||||||
func tunnelEditingCancelled() {
|
func tunnelEditingCancelled() {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
@ -161,8 +166,40 @@ extension TunnelDetailTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func statusCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
private func statusCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell: TunnelDetailStatusCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.tunnel = self.tunnel
|
|
||||||
|
let statusUpdate: (SwitchCell, TunnelStatus) -> Void = { cell, status in
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
cell.textLabel?.text = text
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { [weak cell] in
|
||||||
|
cell?.switchView.isOn = !(status == .deactivating || status == .inactive)
|
||||||
|
cell?.switchView.isUserInteractionEnabled = (status == .inactive || status == .active)
|
||||||
|
}
|
||||||
|
cell.isEnabled = status == .active || status == .inactive
|
||||||
|
}
|
||||||
|
|
||||||
|
statusUpdate(cell, tunnel.status)
|
||||||
|
statusObservervationToken = tunnel.observe(\.status) { [weak cell] tunnel, _ in
|
||||||
|
guard let cell = cell else { return }
|
||||||
|
statusUpdate(cell, tunnel.status)
|
||||||
|
}
|
||||||
|
|
||||||
cell.onSwitchToggled = { [weak self] isOn in
|
cell.onSwitchToggled = { [weak self] isOn in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if isOn {
|
if isOn {
|
||||||
@ -176,7 +213,7 @@ extension TunnelDetailTableViewController {
|
|||||||
|
|
||||||
private func interfaceCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
private func interfaceCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
let field = tunnelViewModel.interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFields)[indexPath.row]
|
let field = tunnelViewModel.interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFields)[indexPath.row]
|
||||||
let cell: TunnelDetailKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.key = field.rawValue
|
cell.key = field.rawValue
|
||||||
cell.value = tunnelViewModel.interfaceData[field]
|
cell.value = tunnelViewModel.interfaceData[field]
|
||||||
return cell
|
return cell
|
||||||
@ -184,20 +221,24 @@ extension TunnelDetailTableViewController {
|
|||||||
|
|
||||||
private func peerCell(for tableView: UITableView, at indexPath: IndexPath, with peerData: TunnelViewModel.PeerData) -> UITableViewCell {
|
private func peerCell(for tableView: UITableView, at indexPath: IndexPath, with peerData: TunnelViewModel.PeerData) -> UITableViewCell {
|
||||||
let field = peerData.filterFieldsWithValueOrControl(peerFields: peerFields)[indexPath.row]
|
let field = peerData.filterFieldsWithValueOrControl(peerFields: peerFields)[indexPath.row]
|
||||||
let cell: TunnelDetailKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.key = field.rawValue
|
cell.key = field.rawValue
|
||||||
cell.value = peerData[field]
|
cell.value = peerData[field]
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell: TunnelDetailActivateOnDemandCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.tunnel = self.tunnel
|
cell.key = "Activate on demand"
|
||||||
|
cell.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting())
|
||||||
|
onDemandStatusObservervationToken = tunnel.observe(\.isActivateOnDemandEnabled) { [weak cell] tunnel, _ in
|
||||||
|
cell?.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting())
|
||||||
|
}
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
private func deleteConfigurationCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
private func deleteConfigurationCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell: TunnelDetailButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.buttonText = "Delete tunnel"
|
cell.buttonText = "Delete tunnel"
|
||||||
cell.hasDestructiveAction = true
|
cell.hasDestructiveAction = true
|
||||||
cell.onTapped = { [weak self] in
|
cell.onTapped = { [weak self] in
|
||||||
|
@ -96,7 +96,8 @@ class TunnelsListTableViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func addButtonTapped(sender: AnyObject) {
|
@objc func addButtonTapped(sender: AnyObject) {
|
||||||
if self.tunnelsManager == nil { return } // Do nothing until we've loaded the tunnels
|
guard tunnelsManager != nil else { return }
|
||||||
|
|
||||||
let alert = UIAlertController(title: "", message: "Add a new WireGuard tunnel", preferredStyle: .actionSheet)
|
let alert = UIAlertController(title: "", message: "Add a new WireGuard tunnel", preferredStyle: .actionSheet)
|
||||||
let importFileAction = UIAlertAction(title: "Create from file or archive", style: .default) { [weak self] _ in
|
let importFileAction = UIAlertAction(title: "Create from file or archive", style: .default) { [weak self] _ in
|
||||||
self?.presentViewControllerForFileImport()
|
self?.presentViewControllerForFileImport()
|
||||||
@ -125,29 +126,30 @@ class TunnelsListTableViewController: UIViewController {
|
|||||||
alert.popoverPresentationController?.sourceView = sender
|
alert.popoverPresentationController?.sourceView = sender
|
||||||
alert.popoverPresentationController?.sourceRect = sender.bounds
|
alert.popoverPresentationController?.sourceRect = sender.bounds
|
||||||
}
|
}
|
||||||
self.present(alert, animated: true, completion: nil)
|
present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func settingsButtonTapped(sender: UIBarButtonItem!) {
|
@objc func settingsButtonTapped(sender: UIBarButtonItem!) {
|
||||||
if self.tunnelsManager == nil { return } // Do nothing until we've loaded the tunnels
|
guard tunnelsManager != nil else { return }
|
||||||
|
|
||||||
let settingsVC = SettingsTableViewController(tunnelsManager: tunnelsManager)
|
let settingsVC = SettingsTableViewController(tunnelsManager: tunnelsManager)
|
||||||
let settingsNC = UINavigationController(rootViewController: settingsVC)
|
let settingsNC = UINavigationController(rootViewController: settingsVC)
|
||||||
settingsNC.modalPresentationStyle = .formSheet
|
settingsNC.modalPresentationStyle = .formSheet
|
||||||
self.present(settingsNC, animated: true)
|
present(settingsNC, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentViewControllerForTunnelCreation(tunnelsManager: TunnelsManager, tunnelConfiguration: TunnelConfiguration?) {
|
func presentViewControllerForTunnelCreation(tunnelsManager: TunnelsManager, tunnelConfiguration: TunnelConfiguration?) {
|
||||||
let editVC = TunnelEditTableViewController(tunnelsManager: tunnelsManager, tunnelConfiguration: tunnelConfiguration)
|
let editVC = TunnelEditTableViewController(tunnelsManager: tunnelsManager, tunnelConfiguration: tunnelConfiguration)
|
||||||
let editNC = UINavigationController(rootViewController: editVC)
|
let editNC = UINavigationController(rootViewController: editVC)
|
||||||
editNC.modalPresentationStyle = .formSheet
|
editNC.modalPresentationStyle = .formSheet
|
||||||
self.present(editNC, animated: true)
|
present(editNC, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentViewControllerForFileImport() {
|
func presentViewControllerForFileImport() {
|
||||||
let documentTypes = ["com.wireguard.config.quick", String(kUTTypeText), String(kUTTypeZipArchive)]
|
let documentTypes = ["com.wireguard.config.quick", String(kUTTypeText), String(kUTTypeZipArchive)]
|
||||||
let filePicker = UIDocumentPickerViewController(documentTypes: documentTypes, in: .import)
|
let filePicker = UIDocumentPickerViewController(documentTypes: documentTypes, in: .import)
|
||||||
filePicker.delegate = self
|
filePicker.delegate = self
|
||||||
self.present(filePicker, animated: true)
|
present(filePicker, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentViewControllerForScanningQRCode() {
|
func presentViewControllerForScanningQRCode() {
|
||||||
@ -155,7 +157,7 @@ class TunnelsListTableViewController: UIViewController {
|
|||||||
scanQRCodeVC.delegate = self
|
scanQRCodeVC.delegate = self
|
||||||
let scanQRCodeNC = UINavigationController(rootViewController: scanQRCodeVC)
|
let scanQRCodeNC = UINavigationController(rootViewController: scanQRCodeVC)
|
||||||
scanQRCodeNC.modalPresentationStyle = .fullScreen
|
scanQRCodeNC.modalPresentationStyle = .fullScreen
|
||||||
self.present(scanQRCodeNC, animated: true)
|
present(scanQRCodeNC, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func importFromFile(url: URL, completionHandler: (() -> Void)?) {
|
func importFromFile(url: URL, completionHandler: (() -> Void)?) {
|
||||||
|
@ -33,7 +33,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
let activationAttemptId = options?["activationAttemptId"] as? String
|
let activationAttemptId = options?["activationAttemptId"] as? String
|
||||||
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId, tunnelProvider: self)
|
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId, tunnelProvider: self)
|
||||||
|
|
||||||
guard let tunnelProviderProtocol = self.protocolConfiguration as? NETunnelProviderProtocol,
|
guard let tunnelProviderProtocol = protocolConfiguration as? NETunnelProviderProtocol,
|
||||||
let tunnelConfiguration = tunnelProviderProtocol.tunnelConfiguration() else {
|
let tunnelConfiguration = tunnelProviderProtocol.tunnelConfiguration() else {
|
||||||
errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
||||||
startTunnelCompletionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
startTunnelCompletionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
||||||
|
Loading…
Reference in New Issue
Block a user