diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2021-06-30 13:25:03 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2021-07-14 11:29:17 +0200 |
| commit | 3cabd77226edf67046eae8ff122fd84660be036e (patch) | |
| tree | ea2ac48858dc5b0f7ff03a53c6bef251049ddda1 /ios | |
| parent | 44cdae1e195b59c6dc71caef34663e4f52fcaca0 (diff) | |
| download | mullvadvpn-3cabd77226edf67046eae8ff122fd84660be036e.tar.xz mullvadvpn-3cabd77226edf67046eae8ff122fd84660be036e.zip | |
WireGuardKeys: drop XIB
Diffstat (limited to 'ios')
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 25 | ||||
| -rw-r--r-- | ios/MullvadVPN/WireguardKeysContentView.swift | 358 | ||||
| -rw-r--r-- | ios/MullvadVPN/WireguardKeysViewController.swift | 167 | ||||
| -rw-r--r-- | ios/MullvadVPN/WireguardKeysViewController.xib | 259 | ||||
| -rw-r--r-- | ios/MullvadVPN/en.lproj/WireguardKeys.strings | 50 |
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"; |
