summaryrefslogtreecommitdiffhomepage
path: root/ios
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2021-06-30 13:25:03 +0200
committerAndrej Mihajlov <and@mullvad.net>2021-07-14 11:29:17 +0200
commit3cabd77226edf67046eae8ff122fd84660be036e (patch)
treeea2ac48858dc5b0f7ff03a53c6bef251049ddda1 /ios
parent44cdae1e195b59c6dc71caef34663e4f52fcaca0 (diff)
downloadmullvadvpn-3cabd77226edf67046eae8ff122fd84660be036e.tar.xz
mullvadvpn-3cabd77226edf67046eae8ff122fd84660be036e.zip
WireGuardKeys: drop XIB
Diffstat (limited to 'ios')
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj25
-rw-r--r--ios/MullvadVPN/WireguardKeysContentView.swift358
-rw-r--r--ios/MullvadVPN/WireguardKeysViewController.swift167
-rw-r--r--ios/MullvadVPN/WireguardKeysViewController.xib259
-rw-r--r--ios/MullvadVPN/en.lproj/WireguardKeys.strings50
5 files changed, 523 insertions, 336 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index e69c082ba1..ce7f089fc0 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -175,7 +175,6 @@
58B43C1925F77DB60002C8C3 /* ConnectMainContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B43C1825F77DB60002C8C3 /* ConnectMainContentView.swift */; };
58B67B482602079E008EF58E /* RelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CD422AFBA39009B9D8E /* RelaySelector.swift */; };
58B8743222B25A7600015324 /* WireguardAssociatedAddresses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B8743122B25A7600015324 /* WireguardAssociatedAddresses.swift */; };
- 58B9814E24FEA70D00C0D59E /* WireguardKeysViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 58B9814D24FEA70D00C0D59E /* WireguardKeysViewController.xib */; };
58B993B12608A34500BA7811 /* LoginContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B993B02608A34500BA7811 /* LoginContentView.swift */; };
58B9EB132488ED2100095626 /* AlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B9EB122488ED2100095626 /* AlertPresenter.swift */; };
58B9EB152489139B00095626 /* DisplayChainedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B9EB142489139B00095626 /* DisplayChainedError.swift */; };
@@ -221,6 +220,8 @@
58F3C0A4249CB069003E76BE /* HeaderBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F3C0A3249CB069003E76BE /* HeaderBarView.swift */; };
58F3C0A624A50157003E76BE /* relays.json in Resources */ = {isa = PBXBuildFile; fileRef = 58F3C0A524A50155003E76BE /* relays.json */; };
58F3C0A724A50C02003E76BE /* relays.json in Resources */ = {isa = PBXBuildFile; fileRef = 58F3C0A524A50155003E76BE /* relays.json */; };
+ 58F61F4F2692F21C00DCFC2B /* WireguardKeys.strings in Resources */ = {isa = PBXBuildFile; fileRef = 58F61F4D2692F21C00DCFC2B /* WireguardKeys.strings */; };
+ 58F7CA882692E34000FC59FD /* WireguardKeysContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F7CA872692E34000FC59FD /* WireguardKeysContentView.swift */; };
58F840AF2464382C0044E708 /* KeychainItemRevision.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F840AE2464382C0044E708 /* KeychainItemRevision.swift */; };
58F840B02464382C0044E708 /* KeychainItemRevision.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F840AE2464382C0044E708 /* KeychainItemRevision.swift */; };
58F840B22464491D0044E708 /* ChainedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F840B12464491D0044E708 /* ChainedError.swift */; };
@@ -365,7 +366,7 @@
587B753C2666468F00DEF7E9 /* NotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationController.swift; sourceTree = "<group>"; };
587B753E2668E5A700DEF7E9 /* NotificationContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContainerView.swift; sourceTree = "<group>"; };
587B75402668FD7700DEF7E9 /* AccountExpiryNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiryNotificationProvider.swift; sourceTree = "<group>"; };
- 587B7544266922BF00DEF7E9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
+ 587B7544266922BF00DEF7E9 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
587CBFE222807F530028DED3 /* UIColor+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Helpers.swift"; sourceTree = "<group>"; };
588534BD246193C00018B744 /* AutomaticKeyRotationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomaticKeyRotationManager.swift; sourceTree = "<group>"; };
5888AD82227B11080051EB06 /* SelectLocationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationCell.swift; sourceTree = "<group>"; };
@@ -391,7 +392,6 @@
58B0A2A4238EE67E00BC001D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
58B43C1825F77DB60002C8C3 /* ConnectMainContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectMainContentView.swift; sourceTree = "<group>"; };
58B8743122B25A7600015324 /* WireguardAssociatedAddresses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireguardAssociatedAddresses.swift; sourceTree = "<group>"; };
- 58B9814D24FEA70D00C0D59E /* WireguardKeysViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WireguardKeysViewController.xib; sourceTree = "<group>"; };
58B993B02608A34500BA7811 /* LoginContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginContentView.swift; sourceTree = "<group>"; };
58B9EB122488ED2100095626 /* AlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresenter.swift; sourceTree = "<group>"; };
58B9EB142489139B00095626 /* DisplayChainedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayChainedError.swift; sourceTree = "<group>"; };
@@ -434,6 +434,8 @@
58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerActivityIndicatorView.swift; sourceTree = "<group>"; };
58F3C0A3249CB069003E76BE /* HeaderBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderBarView.swift; sourceTree = "<group>"; };
58F3C0A524A50155003E76BE /* relays.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = relays.json; sourceTree = "<group>"; };
+ 58F61F4E2692F21C00DCFC2B /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/WireguardKeys.strings; sourceTree = "<group>"; };
+ 58F7CA872692E34000FC59FD /* WireguardKeysContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireguardKeysContentView.swift; sourceTree = "<group>"; };
58F840AE2464382C0044E708 /* KeychainItemRevision.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainItemRevision.swift; sourceTree = "<group>"; };
58F840B12464491D0044E708 /* ChainedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainedError.swift; sourceTree = "<group>"; };
58F8AC0D25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportReviewViewController.swift; sourceTree = "<group>"; };
@@ -537,6 +539,8 @@
isa = PBXGroup;
children = (
582CFEE526945FC30072883A /* AppStoreSubscriptions.strings */,
+ 587B7543266922BF00DEF7E9 /* Localizable.strings */,
+ 58F61F4D2692F21C00DCFC2B /* WireguardKeys.strings */,
);
name = Localizations;
sourceTree = "<group>";
@@ -650,7 +654,7 @@
58FAEDFC24533A5500CB0F5B /* KeychainMatchLimit.swift */,
58FAEDFE24533A7000CB0F5B /* KeychainReturn.swift */,
58727282265D173C00F315B2 /* LaunchScreen.storyboard */,
- 587B7543266922BF00DEF7E9 /* Localizable.strings */,
+ 582CFEE1269448160072883A /* Localizations */,
58A1AA8623F43901009F7EA6 /* Location.swift */,
583DA21325FA4B5C00318683 /* LocationDataSource.swift */,
58BA692D23E99EFF009DC256 /* Locking.swift */,
@@ -710,8 +714,8 @@
585CA70E25F8C44600B47C62 /* UIMetrics.swift */,
58FD5BF12424F7D700112C88 /* UserInterfaceInteractionRestriction.swift */,
58B8743122B25A7600015324 /* WireguardAssociatedAddresses.swift */,
+ 58F7CA872692E34000FC59FD /* WireguardKeysContentView.swift */,
5877152F23981F7B001F8237 /* WireguardKeysViewController.swift */,
- 58B9814D24FEA70D00C0D59E /* WireguardKeysViewController.xib */,
);
path = MullvadVPN;
sourceTree = "<group>";
@@ -948,8 +952,8 @@
582CFEE726945FC30072883A /* AppStoreSubscriptions.strings in Resources */,
584789B8264D4A2A000E45FB /* old_le_root_cert.cer in Resources */,
584789BE264D4A2A000E45FB /* new_le_root_cert.cer in Resources */,
+ 58F61F4F2692F21C00DCFC2B /* WireguardKeys.strings in Resources */,
58E5BC2624FEB6DB00A53A76 /* AccountViewController.xib in Resources */,
- 58B9814E24FEA70D00C0D59E /* WireguardKeysViewController.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1073,6 +1077,7 @@
58CC40EF24A601900019D96E /* ObserverList.swift in Sources */,
58CCA01822426713004F3011 /* AccountViewController.swift in Sources */,
5871FBA0254C26C00051A0A4 /* NSRegularExpression+IPAddress.swift in Sources */,
+ 58F7CA882692E34000FC59FD /* WireguardKeysContentView.swift in Sources */,
5868585524054096000B8131 /* AppButton.swift in Sources */,
5845F842236CBACD00B2D93C /* PacketTunnelIpc.swift in Sources */,
58781CC922AE7CA8009B9D8E /* RelayConstraints.swift in Sources */,
@@ -1259,6 +1264,14 @@
name = Localizable.strings;
sourceTree = "<group>";
};
+ 58F61F4D2692F21C00DCFC2B /* WireguardKeys.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 58F61F4E2692F21C00DCFC2B /* en */,
+ );
+ name = WireguardKeys.strings;
+ sourceTree = "<group>";
+ };
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
diff --git a/ios/MullvadVPN/WireguardKeysContentView.swift b/ios/MullvadVPN/WireguardKeysContentView.swift
new file mode 100644
index 0000000000..298192147e
--- /dev/null
+++ b/ios/MullvadVPN/WireguardKeysContentView.swift
@@ -0,0 +1,358 @@
+//
+// WireguardKeysContentView.swift
+// MullvadVPN
+//
+// Created by pronebird on 05/07/2021.
+// Copyright © 2021 Mullvad VPN AB. All rights reserved.
+//
+
+import UIKit
+
+class WireguardKeysContentView: UIView {
+
+ let regenerateKeyButton: AppButton = {
+ let button = AppButton(style: .success)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.setTitle(
+ NSLocalizedString(
+ "REGENERATE_KEY_BUTTON_TITLE",
+ tableName: "WireguardKeys",
+ comment: ""
+ ),
+ for: .normal
+ )
+ return button
+ }()
+
+ let verifyKeyButton: AppButton = {
+ let button = AppButton(style: .default)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.setTitle(
+ NSLocalizedString(
+ "VERIFY_KEY_BUTTON_TITLE",
+ tableName: "WireguardKeys",
+ comment: ""
+ ),
+ for: .normal
+ )
+ return button
+ }()
+
+ let publicKeyRowView: WireguardKeysPublicKeyRow = {
+ let view = WireguardKeysPublicKeyRow()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ return view
+ }()
+
+ let creationRowView: WireguardKeysCreationRow = {
+ let view = WireguardKeysCreationRow()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ return view
+ }()
+
+ lazy var contentStackView: UIStackView = {
+ let stackView = UIStackView(arrangedSubviews: [publicKeyRowView, creationRowView])
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.axis = .vertical
+ stackView.spacing = UIMetrics.contentLayoutMargins.top
+ return stackView
+ }()
+
+ lazy var buttonStackView: UIStackView = {
+ let stackView = UIStackView(arrangedSubviews: [regenerateKeyButton, verifyKeyButton])
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.axis = .vertical
+ stackView.spacing = UIMetrics.contentLayoutMargins.top
+ return stackView
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ layoutMargins = UIMetrics.contentLayoutMargins
+
+ addSubview(contentStackView)
+ addSubview(buttonStackView)
+
+ NSLayoutConstraint.activate([
+ contentStackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
+ contentStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
+ contentStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
+
+ buttonStackView.topAnchor.constraint(greaterThanOrEqualTo: contentStackView.bottomAnchor, constant: UIMetrics.contentLayoutMargins.top),
+ buttonStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
+ buttonStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
+ buttonStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
+ ])
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
+
+class WireguardKeysPublicKeyRow: UIView {
+
+ var value: String? {
+ didSet {
+ valueButton.setTitle(value, for: .normal)
+ accessibilityValue = value
+ }
+ }
+
+ var status: WireguardKeyStatusView.Status = .default {
+ didSet {
+ statusView.status = status
+ updateAccessibilityLabel()
+ }
+ }
+
+ var actionHandler: (() -> Void)?
+
+ private let textLabel: UILabel = {
+ let textLabel = UILabel()
+ textLabel.translatesAutoresizingMaskIntoConstraints = false
+ textLabel.text = NSLocalizedString("PUBLIC_KEY_LABEL", tableName: "WireguardKeys", comment: "")
+ textLabel.font = UIFont.systemFont(ofSize: 14)
+ textLabel.textColor = UIColor(white: 1.0, alpha: 0.6)
+ return textLabel
+ }()
+
+ private let valueButton: UIButton = {
+ let button = UIButton(type: .system)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.titleLabel?.font = UIFont.systemFont(ofSize: 17)
+ button.setTitleColor(.white, for: .normal)
+ button.contentHorizontalAlignment = .leading
+ button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 1)
+ button.accessibilityHint = NSLocalizedString("PUBLIC_KEY_ACCESSIBILITY_HINT", tableName: "WireguardKeys", comment: "")
+ return button
+ }()
+
+ private let statusView: WireguardKeyStatusView = {
+ let view = WireguardKeyStatusView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+ view.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
+ return view
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ [textLabel, valueButton, statusView].forEach { subview in
+ addSubview(subview)
+ }
+
+ NSLayoutConstraint.activate([
+ textLabel.topAnchor.constraint(equalTo: topAnchor),
+ textLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
+ textLabel.trailingAnchor.constraint(greaterThanOrEqualTo: statusView.leadingAnchor, constant: -8),
+
+ statusView.topAnchor.constraint(equalTo: textLabel.topAnchor),
+ statusView.bottomAnchor.constraint(equalTo: textLabel.bottomAnchor),
+ statusView.trailingAnchor.constraint(equalTo: trailingAnchor),
+
+ valueButton.topAnchor.constraint(equalTo: textLabel.bottomAnchor, constant: 8),
+ valueButton.leadingAnchor.constraint(equalTo: leadingAnchor),
+ valueButton.trailingAnchor.constraint(equalTo: trailingAnchor),
+ valueButton.bottomAnchor.constraint(equalTo: bottomAnchor)
+ ])
+
+ isAccessibilityElement = true
+ updateAccessibilityLabel()
+
+ let actionName = NSLocalizedString(
+ "ACCOUNT_TOKEN_ACCESSIBILITY_ACTION_TITLE",
+ tableName: "Account",
+ comment: ""
+ )
+ accessibilityCustomActions = [UIAccessibilityCustomAction(name: actionName, target: self, selector: #selector(performAccessibilityAction))]
+
+ valueButton.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func updateAccessibilityLabel() {
+ var accessibilityLabelString = self.textLabel.text ?? ""
+
+ if case .verified(let isValid) = status {
+ accessibilityLabelString += ", "
+
+ if isValid {
+ accessibilityLabelString.append(
+ NSLocalizedString(
+ "KEY_STATUS_VALID",
+ tableName: "WireguardKeys",
+ comment: ""
+ )
+ )
+ } else {
+ accessibilityLabelString.append(
+ NSLocalizedString(
+ "KEY_STATUS_INVALID",
+ tableName: "WireguardKeys",
+ comment: ""
+ )
+ )
+ }
+ }
+
+ accessibilityLabel = accessibilityLabelString
+ }
+
+ @objc private func handleTap() {
+ actionHandler?()
+ }
+
+ @objc private func performAccessibilityAction() {
+ self.actionHandler?()
+ }
+}
+
+class WireguardKeysCreationRow: UIView {
+
+ var value: String? {
+ didSet {
+ accessibilityValue = value
+ valueLabel.text = value
+ }
+ }
+
+ private let textLabel: UILabel = {
+ let textLabel = UILabel()
+ textLabel.translatesAutoresizingMaskIntoConstraints = false
+ textLabel.text = NSLocalizedString("KEY_GENERATED_LABEL", tableName: "WireguardKeys", comment: "")
+ textLabel.font = UIFont.systemFont(ofSize: 14)
+ textLabel.textColor = UIColor(white: 1.0, alpha: 0.6)
+ return textLabel
+ }()
+
+ private let valueLabel: UILabel = {
+ let valueLabel = UILabel()
+ valueLabel.translatesAutoresizingMaskIntoConstraints = false
+ valueLabel.font = UIFont.systemFont(ofSize: 17)
+ valueLabel.textColor = .white
+ return valueLabel
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ addSubview(textLabel)
+ addSubview(valueLabel)
+
+ NSLayoutConstraint.activate([
+ textLabel.topAnchor.constraint(equalTo: topAnchor),
+ textLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
+ textLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
+
+ valueLabel.topAnchor.constraint(equalTo: textLabel.bottomAnchor, constant: 8),
+ valueLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
+ valueLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
+ valueLabel.bottomAnchor.constraint(equalTo: bottomAnchor)
+ ])
+
+ isAccessibilityElement = true
+ accessibilityLabel = textLabel.text
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
+
+class WireguardKeyStatusView: UIView {
+ enum Status {
+ case `default`, verifying, verified(Bool), regenerating
+ }
+
+ let textLabel: UILabel = {
+ let textLabel = UILabel()
+ textLabel.translatesAutoresizingMaskIntoConstraints = false
+ textLabel.font = UIFont.systemFont(ofSize: 14)
+ textLabel.textColor = .successColor
+ return textLabel
+ }()
+
+ let activityIndicator: SpinnerActivityIndicatorView = {
+ let activityIndicator = SpinnerActivityIndicatorView(style: .small)
+ activityIndicator.translatesAutoresizingMaskIntoConstraints = false
+ activityIndicator.tintColor = .white
+ return activityIndicator
+ }()
+
+ lazy var stackView: UIStackView = {
+ let stackView = UIStackView(arrangedSubviews: [textLabel, activityIndicator])
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.spacing = 4
+ stackView.axis = .horizontal
+ stackView.distribution = .equalCentering
+ return stackView
+ }()
+
+ var status: Status = .default {
+ didSet {
+ updateView()
+ }
+ }
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ addSubview(stackView)
+
+ NSLayoutConstraint.activate([
+ stackView.topAnchor.constraint(equalTo: topAnchor),
+ stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
+ stackView.trailingAnchor.constraint(equalTo: trailingAnchor)
+ ])
+
+ updateView()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func updateView() {
+ switch status {
+ case .default:
+ textLabel.isHidden = true
+ activityIndicator.stopAnimating()
+
+ case .regenerating, .verifying:
+ startSpinner()
+
+ case .verified(let isValid):
+ textLabel.isHidden = false
+ activityIndicator.stopAnimating()
+
+ if isValid {
+ textLabel.text = NSLocalizedString(
+ "KEY_STATUS_VALID",
+ tableName: "WireguardKeys",
+ comment: ""
+ )
+ textLabel.textColor = .successColor
+ } else {
+ textLabel.text = NSLocalizedString(
+ "KEY_STATUS_INVALID",
+ tableName: "WireguardKeys",
+ comment: ""
+ )
+ textLabel.textColor = .dangerColor
+ }
+ }
+ }
+
+ private func startSpinner() {
+ textLabel.isHidden = true
+ activityIndicator.startAnimating()
+ }
+
+}
diff --git a/ios/MullvadVPN/WireguardKeysViewController.swift b/ios/MullvadVPN/WireguardKeysViewController.swift
index 9c78e489ea..d5705582ec 100644
--- a/ios/MullvadVPN/WireguardKeysViewController.swift
+++ b/ios/MullvadVPN/WireguardKeysViewController.swift
@@ -21,21 +21,22 @@ private enum WireguardKeysViewState {
case verifyingKey
case verifiedKey(Bool)
case regeneratingKey
+ case regeneratedKey(Bool)
}
class WireguardKeysViewController: UIViewController, TunnelObserver {
- @IBOutlet var publicKeyButton: UIButton!
- @IBOutlet var creationDateLabel: UILabel!
- @IBOutlet var regenerateKeyButton: UIButton!
- @IBOutlet var verifyKeyButton: UIButton!
- @IBOutlet var wireguardKeyStatusView: WireguardKeyStatusView!
+ private let contentView: WireguardKeysContentView = {
+ let contentView = WireguardKeysContentView()
+ contentView.translatesAutoresizingMaskIntoConstraints = false
+ return contentView
+ }()
private var publicKeyPeriodicUpdateTimer: DispatchSourceTimer?
private var copyToPasteboardWork: DispatchWorkItem?
private let alertPresenter = AlertPresenter()
- private let logger = Logger(label: "WireguardKeysViewController")
+ private let logger = Logger(label: "WireguardKeys")
private var state: WireguardKeysViewState = .default {
didSet {
@@ -46,7 +47,34 @@ class WireguardKeysViewController: UIViewController, TunnelObserver {
override func viewDidLoad() {
super.viewDidLoad()
- navigationItem.title = NSLocalizedString("WireGuard key", comment: "Navigation title")
+ view.backgroundColor = .secondaryColor
+
+ let scrollView = UIScrollView()
+ scrollView.translatesAutoresizingMaskIntoConstraints = false
+ scrollView.addSubview(contentView)
+ view.addSubview(scrollView)
+
+ NSLayoutConstraint.activate([
+ scrollView.topAnchor.constraint(equalTo: view.topAnchor),
+ scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+
+ contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
+ contentView.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.safeAreaLayoutGuide.bottomAnchor),
+ contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
+ contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
+ contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
+ ])
+
+ navigationItem.title = NSLocalizedString("NAVIGATION_TITLE", tableName: "WireguardKeys", comment: "")
+
+ contentView.publicKeyRowView.actionHandler = { [weak self] in
+ self?.copyPublicKey()
+ }
+
+ contentView.regenerateKeyButton.addTarget(self, action: #selector(handleRegenerateKey(_:)), for: .touchUpInside)
+ contentView.verifyKeyButton.addTarget(self, action: #selector(handleVerifyKey(_:)), for: .touchUpInside)
TunnelManager.shared.addObserver(self)
updatePublicKey(tunnelSettings: TunnelManager.shared.tunnelSettings, animated: false)
@@ -78,15 +106,15 @@ class WireguardKeysViewController: UIViewController, TunnelObserver {
}
}
- // MARK: - IBActions
+ // MARK: - Actions
- @IBAction func copyPublicKey(_ sender: Any) {
+ private func copyPublicKey() {
guard let metadata = TunnelManager.shared.tunnelSettings?.interface.privateKey.publicKeyWithMetadata else { return }
UIPasteboard.general.string = metadata.stringRepresentation()
setPublicKeyTitle(
- string: NSLocalizedString("COPIED TO PASTEBOARD!", comment: ""),
+ string: NSLocalizedString("COPIED_TO_PASTEBOARD_LABEL", tableName: "WireguardKeys", comment: ""),
animated: true)
let dispatchWork = DispatchWorkItem { [weak self] in
@@ -99,11 +127,11 @@ class WireguardKeysViewController: UIViewController, TunnelObserver {
self.copyToPasteboardWork = dispatchWork
}
- @IBAction func handleRegenerateKey(_ sender: Any) {
+ @objc private func handleRegenerateKey(_ sender: Any) {
regeneratePrivateKey()
}
- @IBAction func handleVerifyKey(_ sender: Any) {
+ @objc private func handleVerifyKey(_ sender: Any) {
verifyKey()
}
@@ -115,12 +143,12 @@ class WireguardKeysViewController: UIViewController, TunnelObserver {
to: Date(),
unitsStyle: .full
).map { (formattedInterval) -> String in
- return String(format: NSLocalizedString("%@ ago", comment: ""), formattedInterval)
+ return String(format: NSLocalizedString("KEY_GENERATED_SINCE_FORMAT", tableName: "WireguardKeys", comment: ""), formattedInterval)
}
}
private func updateCreationDateLabel(with creationDate: Date) {
- creationDateLabel.text = formatKeyGenerationElapsedTime(with: creationDate) ?? "-"
+ contentView.creationRowView.value = formatKeyGenerationElapsedTime(with: creationDate) ?? "-"
}
private func updatePublicKey(tunnelSettings: TunnelSettings?, animated: Bool) {
@@ -132,7 +160,7 @@ class WireguardKeysViewController: UIViewController, TunnelObserver {
updateCreationDateLabel(with: publicKey.creationDate)
} else {
setPublicKeyTitle(string: "-", animated: animated)
- creationDateLabel.text = "-"
+ contentView.creationRowView.value = "-"
}
}
@@ -140,25 +168,34 @@ class WireguardKeysViewController: UIViewController, TunnelObserver {
switch state {
case .default:
setKeyActionButtonsEnabled(true)
- wireguardKeyStatusView.status = .default
+ contentView.publicKeyRowView.status = .default
case .verifyingKey:
setKeyActionButtonsEnabled(false)
- wireguardKeyStatusView.status = .verifying
+ contentView.publicKeyRowView.status = .verifying
case .verifiedKey(let isValid):
setKeyActionButtonsEnabled(true)
- wireguardKeyStatusView.status = .verified(isValid)
+ contentView.publicKeyRowView.status = .verified(isValid)
+ announceKeyVerificationResult(isValid: isValid)
case .regeneratingKey:
setKeyActionButtonsEnabled(false)
- wireguardKeyStatusView.status = .verifying
+ contentView.publicKeyRowView.status = .regenerating
+
+ case .regeneratedKey(let success):
+ setKeyActionButtonsEnabled(true)
+ contentView.publicKeyRowView.status = .default
+ if success {
+ announceKeyRegenerated()
+ }
+
}
}
private func setKeyActionButtonsEnabled(_ enabled: Bool) {
- regenerateKeyButton.isEnabled = enabled
- verifyKeyButton.isEnabled = enabled
+ contentView.regenerateKeyButton.isEnabled = enabled
+ contentView.verifyKeyButton.isEnabled = enabled
}
private func verifyKey() {
@@ -172,12 +209,12 @@ class WireguardKeysViewController: UIViewController, TunnelObserver {
case .failure(let error):
let alertController = UIAlertController(
- title: NSLocalizedString("Cannot verify the key", comment: ""),
+ title: NSLocalizedString("VERIFY_KEY_FAILURE_ALERT_TITLE", tableName: "WireguardKeys", comment: ""),
message: error.errorChainDescription,
preferredStyle: .alert
)
alertController.addAction(
- UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .cancel)
+ UIAlertAction(title: NSLocalizedString("VERIFY_KEY_FAILURE_ALERT_OK_ACTION", tableName: "WireguardKeys", comment: ""), style: .cancel)
)
self.alertPresenter.enqueue(alertController, presentingController: self)
@@ -190,35 +227,38 @@ class WireguardKeysViewController: UIViewController, TunnelObserver {
private func regeneratePrivateKey() {
self.updateViewState(.regeneratingKey)
- TunnelManager.shared.regeneratePrivateKey { (result) in
+ TunnelManager.shared.regeneratePrivateKey { [weak self] (result) in
DispatchQueue.main.async {
+ guard let self = self else { return }
+
switch result {
case .success:
- break
+ self.updateViewState(.regeneratedKey(true))
case .failure(let error):
let alertController = UIAlertController(
- title: NSLocalizedString("Cannot regenerate the key", comment: ""),
+ title: NSLocalizedString("REGENERATE_KEY_FAILURE_ALERT_TITLE", tableName: "WireguardKeys", comment: ""),
message: error.errorChainDescription,
preferredStyle: .alert
)
alertController.addAction(
- UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .cancel)
+ UIAlertAction(title: NSLocalizedString("REGENERATE_KEY_FAILURE_ALERT_OK_ACTION", tableName: "WireguardKeys", comment: ""), style: .cancel)
)
self.logger.error(chainedError: error, message: "Failed to regenerate the private key")
self.alertPresenter.enqueue(alertController, presentingController: self)
- }
- self.updateViewState(.default)
+ self.updateViewState(.regeneratedKey(false))
+ }
}
}
}
private func setPublicKeyTitle(string: String, animated: Bool) {
let updateTitle = {
- self.publicKeyButton.setTitle(string, for: .normal)
+ self.contentView.publicKeyRowView.value = string
+
}
if animated {
@@ -226,56 +266,41 @@ class WireguardKeysViewController: UIViewController, TunnelObserver {
} else {
UIView.performWithoutAnimation {
updateTitle()
- publicKeyButton.layoutIfNeeded()
+ self.contentView.publicKeyRowView.layoutIfNeeded()
}
}
}
-}
-
-class WireguardKeyStatusView: UIView {
-
- enum Status {
- case `default`, verifying, verified(Bool)
- }
+ private func announceKeyVerificationResult(isValid: Bool) {
+ let announcementString: String
- @IBOutlet var textLabel: UILabel!
- @IBOutlet var activityIndicator: SpinnerActivityIndicatorView!
-
- var status: Status = .default {
- didSet {
- updateView()
+ if isValid {
+ announcementString = NSLocalizedString(
+ "ACCESSIBILITY_ANNOUNCEMENT_VALID_KEY",
+ tableName: "WireguardKeys",
+ value: "Key is valid.",
+ comment: ""
+ )
+ } else {
+ announcementString = NSLocalizedString(
+ "ACCESSIBILITY_ANNOUNCEMENT_INVALID_KEY",
+ tableName: "WireguardKeys",
+ value: "Key is invalid.",
+ comment: ""
+ )
}
- }
- override func awakeFromNib() {
- super.awakeFromNib()
-
- updateView()
+ UIAccessibility.post(notification: .announcement, argument: announcementString)
}
- private func updateView() {
- switch status {
- case .default:
- textLabel.isHidden = true
- activityIndicator.stopAnimating()
-
- case .verifying:
- textLabel.isHidden = true
- activityIndicator.startAnimating()
-
- case .verified(let isValid):
- textLabel.isHidden = false
- activityIndicator.stopAnimating()
-
- if isValid {
- textLabel.textColor = .successColor
- textLabel.text = NSLocalizedString("Key is valid", comment: "")
- } else {
- textLabel.textColor = .dangerColor
- textLabel.text = NSLocalizedString("Key is invalid", comment: "")
- }
- }
+ private func announceKeyRegenerated() {
+ let announcementString = NSLocalizedString(
+ "ACCESSIBILITY_ANNOUNCEMENT_REGENERATED_KEY",
+ tableName: "WireguardKeys",
+ value: "Key is regenerated.",
+ comment: ""
+ )
+ UIAccessibility.post(notification: .announcement, argument: announcementString)
}
}
diff --git a/ios/MullvadVPN/WireguardKeysViewController.xib b/ios/MullvadVPN/WireguardKeysViewController.xib
deleted file mode 100644
index 46e0084a49..0000000000
--- a/ios/MullvadVPN/WireguardKeysViewController.xib
+++ /dev/null
@@ -1,259 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
- <device id="retina6_1" orientation="portrait" appearance="light"/>
- <dependencies>
- <deployment identifier="iOS"/>
- <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
- <capability name="Named colors" minToolsVersion="9.0"/>
- <capability name="Safe area layout guides" minToolsVersion="9.0"/>
- <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
- </dependencies>
- <objects>
- <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="WireguardKeysViewController" customModule="MullvadVPN" customModuleProvider="target">
- <connections>
- <outlet property="creationDateLabel" destination="lzi-4c-l9v" id="0Z2-ew-RhM"/>
- <outlet property="publicKeyButton" destination="bD5-xv-y6Z" id="C0z-zj-s1r"/>
- <outlet property="regenerateKeyButton" destination="moM-8X-Qyw" id="Lk5-iu-49e"/>
- <outlet property="verifyKeyButton" destination="aB5-uU-WIR" id="KXV-vp-x6x"/>
- <outlet property="view" destination="0KT-g1-t9r" id="QAy-jD-drf"/>
- <outlet property="wireguardKeyStatusView" destination="UWn-xd-nij" id="det-Vn-9ID"/>
- </connections>
- </placeholder>
- <view contentMode="scaleToFill" id="0KT-g1-t9r">
- <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
- <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
- <subviews>
- <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ef2-EJ-GbD">
- <rect key="frame" x="0.0" y="0.0" width="414" height="862"/>
- <subviews>
- <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8RA-l5-l5o" userLabel="Container">
- <rect key="frame" x="0.0" y="0.0" width="414" height="295.5"/>
- <subviews>
- <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="nm3-Kj-ONm" userLabel="Content">
- <rect key="frame" x="24" y="24" width="366" height="247.5"/>
- <subviews>
- <view contentMode="scaleToFill" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="TQT-yd-nbI" userLabel="Account number">
- <rect key="frame" x="0.0" y="0.0" width="366" height="46"/>
- <subviews>
- <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="vMc-wW-ggf">
- <rect key="frame" x="0.0" y="0.0" width="366" height="46"/>
- <subviews>
- <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="OrE-dl-8bW">
- <rect key="frame" x="0.0" y="0.0" width="366" height="17"/>
- <subviews>
- <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="751" text="Public key" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="g23-nO-ste">
- <rect key="frame" x="0.0" y="0.0" width="66" height="17"/>
- <fontDescription key="fontDescription" type="system" pointSize="14"/>
- <color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <nil key="highlightedColor"/>
- </label>
- <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="UgW-XB-q8E" customClass="EmbeddedViewContainerView" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="224" y="0.0" width="142" height="17"/>
- <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <constraints>
- <constraint firstAttribute="width" constant="142" placeholder="YES" id="tdi-3g-eCH"/>
- </constraints>
- <connections>
- <outlet property="embeddedView" destination="UWn-xd-nij" id="DCV-o7-ddF"/>
- </connections>
- </view>
- </subviews>
- <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <constraints>
- <constraint firstItem="UgW-XB-q8E" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="g23-nO-ste" secondAttribute="trailing" constant="8" id="L13-rB-Uy0"/>
- <constraint firstAttribute="trailing" secondItem="UgW-XB-q8E" secondAttribute="trailing" id="YCs-71-hye"/>
- <constraint firstItem="g23-nO-ste" firstAttribute="leading" secondItem="OrE-dl-8bW" secondAttribute="leading" id="YI1-PR-QX4"/>
- <constraint firstItem="UgW-XB-q8E" firstAttribute="top" secondItem="OrE-dl-8bW" secondAttribute="top" id="aUr-hg-Omr"/>
- <constraint firstAttribute="bottom" secondItem="g23-nO-ste" secondAttribute="bottom" id="cPa-1N-3xG"/>
- <constraint firstAttribute="bottom" secondItem="UgW-XB-q8E" secondAttribute="bottom" id="dEW-6v-kNP"/>
- <constraint firstItem="g23-nO-ste" firstAttribute="top" secondItem="OrE-dl-8bW" secondAttribute="top" id="r9d-Wb-cDT"/>
- </constraints>
- </view>
- <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bD5-xv-y6Z">
- <rect key="frame" x="0.0" y="25" width="366" height="21"/>
- <fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
- <inset key="contentEdgeInsets" minX="0.01" minY="0.0" maxX="1" maxY="0.0"/>
- <state key="normal" title="123456789">
- <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- </state>
- <connections>
- <action selector="copyPublicKey:" destination="-1" eventType="touchUpInside" id="lcS-i9-vnc"/>
- </connections>
- </button>
- </subviews>
- </stackView>
- </subviews>
- <constraints>
- <constraint firstItem="vMc-wW-ggf" firstAttribute="top" secondItem="TQT-yd-nbI" secondAttribute="top" id="0h8-eE-6HJ"/>
- <constraint firstAttribute="bottom" secondItem="vMc-wW-ggf" secondAttribute="bottom" id="Biu-i4-kLE"/>
- <constraint firstItem="vMc-wW-ggf" firstAttribute="leading" secondItem="TQT-yd-nbI" secondAttribute="leading" id="aLi-aW-dRS"/>
- <constraint firstAttribute="trailing" secondItem="vMc-wW-ggf" secondAttribute="trailing" id="j8I-aY-laA"/>
- </constraints>
- </view>
- <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WE4-cD-dXV" userLabel="Expiry">
- <rect key="frame" x="0.0" y="70" width="366" height="45.5"/>
- <subviews>
- <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="qQz-cx-MJT">
- <rect key="frame" x="0.0" y="0.0" width="366" height="45.5"/>
- <subviews>
- <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Key generated" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="017-qu-nUG">
- <rect key="frame" x="0.0" y="0.0" width="366" height="17"/>
- <fontDescription key="fontDescription" type="system" pointSize="14"/>
- <color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <nil key="highlightedColor"/>
- </label>
- <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="6 days ago" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsLetterSpacingToFitWidth="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lzi-4c-l9v">
- <rect key="frame" x="0.0" y="25" width="366" height="20.5"/>
- <fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
- <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <nil key="highlightedColor"/>
- </label>
- </subviews>
- </stackView>
- </subviews>
- <constraints>
- <constraint firstAttribute="bottom" secondItem="qQz-cx-MJT" secondAttribute="bottom" id="7CP-kA-aNk"/>
- <constraint firstItem="qQz-cx-MJT" firstAttribute="leading" secondItem="WE4-cD-dXV" secondAttribute="leading" id="Cdg-Vz-UPX"/>
- <constraint firstAttribute="trailing" secondItem="qQz-cx-MJT" secondAttribute="trailing" id="aYR-XF-Fm4"/>
- <constraint firstItem="qQz-cx-MJT" firstAttribute="top" secondItem="WE4-cD-dXV" secondAttribute="top" id="vC6-tk-rlS"/>
- </constraints>
- </view>
- <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Rnk-UH-rRP" userLabel="Buttons">
- <rect key="frame" x="0.0" y="139.5" width="366" height="108"/>
- <subviews>
- <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="hTO-tb-TtE">
- <rect key="frame" x="0.0" y="0.0" width="366" height="108"/>
- <subviews>
- <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="moM-8X-Qyw" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="0.0" width="366" height="42"/>
- <constraints>
- <constraint firstAttribute="height" constant="42" placeholder="YES" id="hcH-5Y-7hH"/>
- </constraints>
- <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
- <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <state key="normal" title="Regenerate key" backgroundImage="SuccessButton"/>
- <connections>
- <action selector="handleRegenerateKey:" destination="-1" eventType="touchUpInside" id="MPC-g8-z3f"/>
- </connections>
- </button>
- <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aB5-uU-WIR" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="66" width="366" height="42"/>
- <constraints>
- <constraint firstAttribute="height" constant="42" placeholder="YES" id="lzN-hG-dmz"/>
- </constraints>
- <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
- <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <state key="normal" title="Verify key" backgroundImage="DefaultButton"/>
- <connections>
- <action selector="handleVerifyKey:" destination="-1" eventType="touchUpInside" id="tkx-8o-gGK"/>
- </connections>
- </button>
- </subviews>
- </stackView>
- </subviews>
- <constraints>
- <constraint firstAttribute="bottom" secondItem="hTO-tb-TtE" secondAttribute="bottom" id="XYT-Mh-nOc"/>
- <constraint firstItem="hTO-tb-TtE" firstAttribute="top" secondItem="Rnk-UH-rRP" secondAttribute="top" id="eLy-gb-ayb"/>
- <constraint firstItem="hTO-tb-TtE" firstAttribute="leading" secondItem="Rnk-UH-rRP" secondAttribute="leading" id="lBy-5I-ndg"/>
- <constraint firstAttribute="trailing" secondItem="hTO-tb-TtE" secondAttribute="trailing" id="m07-nv-nWj"/>
- </constraints>
- </view>
- </subviews>
- <constraints>
- <constraint firstItem="Rnk-UH-rRP" firstAttribute="top" secondItem="WE4-cD-dXV" secondAttribute="bottom" constant="24" id="2sI-jI-10o"/>
- <constraint firstAttribute="trailing" secondItem="Rnk-UH-rRP" secondAttribute="trailing" id="7kY-ED-xKR"/>
- <constraint firstItem="TQT-yd-nbI" firstAttribute="leading" secondItem="nm3-Kj-ONm" secondAttribute="leading" id="9ok-6a-WGz"/>
- <constraint firstItem="Rnk-UH-rRP" firstAttribute="leading" secondItem="nm3-Kj-ONm" secondAttribute="leading" id="DIF-4p-U9a"/>
- <constraint firstAttribute="trailing" secondItem="TQT-yd-nbI" secondAttribute="trailing" id="Msk-pE-y04"/>
- <constraint firstItem="WE4-cD-dXV" firstAttribute="top" secondItem="TQT-yd-nbI" secondAttribute="bottom" constant="24" id="XYW-Zd-IB6"/>
- <constraint firstAttribute="bottom" secondItem="Rnk-UH-rRP" secondAttribute="bottom" id="amK-et-wSg"/>
- <constraint firstAttribute="trailing" secondItem="WE4-cD-dXV" secondAttribute="trailing" id="c5R-u5-ncH"/>
- <constraint firstItem="TQT-yd-nbI" firstAttribute="top" secondItem="nm3-Kj-ONm" secondAttribute="top" id="yWj-DN-anI"/>
- <constraint firstItem="WE4-cD-dXV" firstAttribute="leading" secondItem="nm3-Kj-ONm" secondAttribute="leading" id="zcQ-MS-Mkz"/>
- </constraints>
- </view>
- </subviews>
- <constraints>
- <constraint firstItem="nm3-Kj-ONm" firstAttribute="leading" secondItem="8RA-l5-l5o" secondAttribute="leading" constant="24" id="6xO-Yz-LyP"/>
- <constraint firstAttribute="trailing" secondItem="nm3-Kj-ONm" secondAttribute="trailing" constant="24" id="8ps-I8-JZz"/>
- <constraint firstAttribute="bottom" secondItem="nm3-Kj-ONm" secondAttribute="bottom" constant="24" id="SNO-Jm-cFh"/>
- <constraint firstItem="nm3-Kj-ONm" firstAttribute="top" secondItem="8RA-l5-l5o" secondAttribute="top" constant="24" id="xhA-R2-TRH"/>
- </constraints>
- </view>
- </subviews>
- <constraints>
- <constraint firstItem="8RA-l5-l5o" firstAttribute="width" secondItem="Ef2-EJ-GbD" secondAttribute="width" id="2wa-Gf-kAz"/>
- <constraint firstItem="8RA-l5-l5o" firstAttribute="top" secondItem="Ef2-EJ-GbD" secondAttribute="top" id="5US-02-mPv"/>
- <constraint firstAttribute="trailing" secondItem="8RA-l5-l5o" secondAttribute="trailing" id="Woy-WL-LaP"/>
- <constraint firstAttribute="bottom" secondItem="8RA-l5-l5o" secondAttribute="bottom" id="weK-v1-ewb"/>
- <constraint firstItem="8RA-l5-l5o" firstAttribute="leading" secondItem="Ef2-EJ-GbD" secondAttribute="leading" id="xbl-T4-0ym"/>
- </constraints>
- </scrollView>
- </subviews>
- <color key="backgroundColor" name="Secondary"/>
- <constraints>
- <constraint firstItem="Ef2-EJ-GbD" firstAttribute="trailing" secondItem="BeY-Zt-zoO" secondAttribute="trailing" id="AVa-p6-gnL"/>
- <constraint firstItem="Ef2-EJ-GbD" firstAttribute="top" secondItem="0KT-g1-t9r" secondAttribute="top" id="DUA-7u-Tpr"/>
- <constraint firstItem="Ef2-EJ-GbD" firstAttribute="bottom" secondItem="BeY-Zt-zoO" secondAttribute="bottom" id="Xqi-8g-wIh"/>
- <constraint firstItem="Ef2-EJ-GbD" firstAttribute="leading" secondItem="BeY-Zt-zoO" secondAttribute="leading" id="sST-q5-QMi"/>
- </constraints>
- <viewLayoutGuide key="safeArea" id="BeY-Zt-zoO"/>
- <point key="canvasLocation" x="3274" y="-451"/>
- </view>
- <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
- <view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="UWn-xd-nij" customClass="WireguardKeyStatusView" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="0.0" width="332" height="30"/>
- <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
- <subviews>
- <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="751" text="Key is valid" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Hfe-qt-Bfg">
- <rect key="frame" x="0.0" y="0.0" width="332" height="30"/>
- <fontDescription key="fontDescription" type="system" pointSize="14"/>
- <color key="textColor" name="Success"/>
- <nil key="highlightedColor"/>
- </label>
- <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="izE-NT-b2X" customClass="SpinnerActivityIndicatorView" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="302" y="0.0" width="30" height="30"/>
- <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <constraints>
- <constraint firstAttribute="width" secondItem="izE-NT-b2X" secondAttribute="height" multiplier="1:1" id="Hb3-oO-ph9"/>
- </constraints>
- <userDefinedRuntimeAttributes>
- <userDefinedRuntimeAttribute type="number" keyPath="thickness">
- <real key="value" value="2"/>
- </userDefinedRuntimeAttribute>
- </userDefinedRuntimeAttributes>
- </view>
- </subviews>
- <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <constraints>
- <constraint firstItem="izE-NT-b2X" firstAttribute="height" secondItem="Hfe-qt-Bfg" secondAttribute="height" id="5CX-7Y-dTK"/>
- <constraint firstItem="Hfe-qt-Bfg" firstAttribute="top" secondItem="UWn-xd-nij" secondAttribute="top" id="8c7-fU-nCj"/>
- <constraint firstAttribute="bottom" secondItem="Hfe-qt-Bfg" secondAttribute="bottom" id="FdG-Wt-XTb"/>
- <constraint firstItem="Hfe-qt-Bfg" firstAttribute="leading" secondItem="UWn-xd-nij" secondAttribute="leading" id="Rwl-nz-b8C"/>
- <constraint firstAttribute="trailing" secondItem="Hfe-qt-Bfg" secondAttribute="trailing" id="dhs-MG-8Va"/>
- <constraint firstAttribute="trailing" secondItem="izE-NT-b2X" secondAttribute="trailing" id="iIb-6W-Xx6"/>
- <constraint firstItem="izE-NT-b2X" firstAttribute="centerY" secondItem="Hfe-qt-Bfg" secondAttribute="centerY" id="iiN-wu-4SO"/>
- </constraints>
- <nil key="simulatedTopBarMetrics"/>
- <nil key="simulatedBottomBarMetrics"/>
- <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
- <viewLayoutGuide key="safeArea" id="4Jg-lh-k5T"/>
- <connections>
- <outlet property="activityIndicator" destination="izE-NT-b2X" id="BrH-U3-uZU"/>
- <outlet property="textLabel" destination="Hfe-qt-Bfg" id="An4-Wb-wtv"/>
- </connections>
- <point key="canvasLocation" x="2561" y="-159"/>
- </view>
- </objects>
- <resources>
- <image name="DefaultButton" width="9" height="9"/>
- <image name="SuccessButton" width="9" height="9"/>
- <namedColor name="Secondary">
- <color red="0.098039215686274508" green="0.1803921568627451" blue="0.27058823529411763" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
- </namedColor>
- <namedColor name="Success">
- <color red="0.26666666666666666" green="0.67843137254901964" blue="0.30196078431372547" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
- </namedColor>
- </resources>
-</document>
diff --git a/ios/MullvadVPN/en.lproj/WireguardKeys.strings b/ios/MullvadVPN/en.lproj/WireguardKeys.strings
new file mode 100644
index 0000000000..5da834961c
--- /dev/null
+++ b/ios/MullvadVPN/en.lproj/WireguardKeys.strings
@@ -0,0 +1,50 @@
+/* No comment provided by engineer. */
+"ACCESSIBILITY_ANNOUNCEMENT_INVALID_KEY" = "Key is invalid.";
+
+/* No comment provided by engineer. */
+"ACCESSIBILITY_ANNOUNCEMENT_REGENERATED_KEY" = "Key is regenerated.";
+
+/* No comment provided by engineer. */
+"ACCESSIBILITY_ANNOUNCEMENT_VALID_KEY" = "Key is valid.";
+
+/* No comment provided by engineer. */
+"COPIED_TO_PASTEBOARD_LABEL" = "COPIED TO PASTEBOARD!";
+
+/* No comment provided by engineer. */
+"KEY_GENERATED_LABEL" = "Key generated";
+
+/* No comment provided by engineer. */
+"KEY_GENERATED_SINCE_FORMAT" = "%@ ago";
+
+/* No comment provided by engineer. */
+"KEY_STATUS_INVALID" = "Key is invalid";
+
+/* No comment provided by engineer. */
+"KEY_STATUS_VALID" = "Key is valid";
+
+/* No comment provided by engineer. */
+"NAVIGATION_TITLE" = "WireGuard key";
+
+/* No comment provided by engineer. */
+"PUBLIC_KEY_ACCESSIBILITY_HINT" = "Tap to copy to pasteboard.";
+
+/* No comment provided by engineer. */
+"PUBLIC_KEY_LABEL" = "Public key";
+
+/* No comment provided by engineer. */
+"REGENERATE_KEY_BUTTON_TITLE" = "Regenerate key";
+
+/* No comment provided by engineer. */
+"REGENERATE_KEY_FAILURE_ALERT_OK_ACTION" = "OK";
+
+/* No comment provided by engineer. */
+"REGENERATE_KEY_FAILURE_ALERT_TITLE" = "Cannot regenerate the key";
+
+/* No comment provided by engineer. */
+"VERIFY_KEY_BUTTON_TITLE" = "Verify key";
+
+/* No comment provided by engineer. */
+"VERIFY_KEY_FAILURE_ALERT_OK_ACTION" = "OK";
+
+/* No comment provided by engineer. */
+"VERIFY_KEY_FAILURE_ALERT_TITLE" = "Cannot verify the key";