summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2022-07-01 09:14:30 +0200
committerAndrej Mihajlov <and@mullvad.net>2022-07-01 09:14:30 +0200
commitc2074223d06bb29b025d8541d26b23ae431f70a4 (patch)
treeb241541f08a5bf233ae8510b03f7cf2ae79a4e1e
parent786b27643a0bd963bb180cc0af92c0e0ea466966 (diff)
parentd7dcf08bf267839fbdf67f454b617c96f37e0129 (diff)
downloadmullvadvpn-c2074223d06bb29b025d8541d26b23ae431f70a4.tar.xz
mullvadvpn-c2074223d06bb29b025d8541d26b23ae431f70a4.zip
Merge branch 'update-account-view'
-rw-r--r--ios/CHANGELOG.md1
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/AccountContentView.swift311
-rw-r--r--ios/MullvadVPN/AccountInputGroupView.swift6
-rw-r--r--ios/MullvadVPN/AccountViewController.swift36
-rw-r--r--ios/MullvadVPN/Assets.xcassets/IconCopy.imageset/Contents.json16
-rw-r--r--ios/MullvadVPN/Assets.xcassets/IconCopy.imageset/IconCopy.pdfbin0 -> 1174 bytes
-rw-r--r--ios/MullvadVPN/Assets.xcassets/IconObscure.imageset/Contents.json16
-rw-r--r--ios/MullvadVPN/Assets.xcassets/IconObscure.imageset/IconObscure.pdfbin0 -> 1481 bytes
-rw-r--r--ios/MullvadVPN/Assets.xcassets/IconUnobscure.imageset/Contents.json16
-rw-r--r--ios/MullvadVPN/Assets.xcassets/IconUnobscure.imageset/IconUnobscure.pdfbin0 -> 1154 bytes
-rw-r--r--ios/MullvadVPN/ProblemReportReviewViewController.swift12
-rw-r--r--ios/MullvadVPN/UIFont+Monospaced.swift27
-rwxr-xr-xios/convert-assets.rb5
14 files changed, 370 insertions, 80 deletions
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 7efd846d3e..7847bb955f 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -26,6 +26,7 @@ Line wrap the file at 100 chars. Th
### Added
- Add option to block gambling and adult content.
- Add last used account field to login view.
+- Display device name under account view.
### Fixed
- Improve random port distribution. Should be less biased towards port 53.
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 04b69c8b65..857f1c44be 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -58,6 +58,7 @@
5820676426E771DB00655B05 /* TunnelManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5820676326E771DB00655B05 /* TunnelManagerError.swift */; };
5820676826E79E7B00655B05 /* Result+UIBackgroundFetchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5820676726E79E7B00655B05 /* Result+UIBackgroundFetchResult.swift */; };
5823FA5426CE49F700283BF8 /* TunnelObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5823FA5326CE49F600283BF8 /* TunnelObserver.swift */; };
+ 58289082286B590900478596 /* UIFont+Monospaced.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58289081286B590900478596 /* UIFont+Monospaced.swift */; };
58293FAE2510CA58005D0BB5 /* ProblemReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FAC2510CA58005D0BB5 /* ProblemReportViewController.swift */; };
58293FB125124117005D0BB5 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FB025124117005D0BB5 /* CustomTextField.swift */; };
58293FB3251241B4005D0BB5 /* CustomTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FB2251241B3005D0BB5 /* CustomTextView.swift */; };
@@ -392,6 +393,7 @@
5820676726E79E7B00655B05 /* Result+UIBackgroundFetchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+UIBackgroundFetchResult.swift"; sourceTree = "<group>"; };
5823FA4F26CA690600283BF8 /* OSLogHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLogHandler.swift; sourceTree = "<group>"; };
5823FA5326CE49F600283BF8 /* TunnelObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelObserver.swift; sourceTree = "<group>"; };
+ 58289081286B590900478596 /* UIFont+Monospaced.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Monospaced.swift"; sourceTree = "<group>"; };
58293FAC2510CA58005D0BB5 /* ProblemReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportViewController.swift; sourceTree = "<group>"; };
58293FB025124117005D0BB5 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = "<group>"; };
58293FB2251241B3005D0BB5 /* CustomTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextView.swift; sourceTree = "<group>"; };
@@ -980,6 +982,7 @@
58F7CA872692E34000FC59FD /* WireguardKeysContentView.swift */,
5877152F23981F7B001F8237 /* WireguardKeysViewController.swift */,
5872D6E7286304DE00DB5F4E /* TermsOfService.swift */,
+ 58289081286B590900478596 /* UIFont+Monospaced.swift */,
);
path = MullvadVPN;
sourceTree = "<group>";
@@ -1328,6 +1331,7 @@
58059DE228468255002B1049 /* ResultOperation+Fallible.swift in Sources */,
582BB1B3229574F40055B6EF /* SettingsAccountCell.swift in Sources */,
588527B2276B3F0700BAA373 /* LoadTunnelConfigurationOperation.swift in Sources */,
+ 58289082286B590900478596 /* UIFont+Monospaced.swift in Sources */,
58F1311527E0B2AB007AC5BC /* Result+Extensions.swift in Sources */,
585DA88426B0270700B8C587 /* ServerRelaysResponse.swift in Sources */,
5875960726F36B3A00BF6711 /* TunnelIPCError.swift in Sources */,
diff --git a/ios/MullvadVPN/AccountContentView.swift b/ios/MullvadVPN/AccountContentView.swift
index e61dc0688b..b357f3d067 100644
--- a/ios/MullvadVPN/AccountContentView.swift
+++ b/ios/MullvadVPN/AccountContentView.swift
@@ -41,8 +41,14 @@ class AccountContentView: UIView {
return button
}()
- let accountTokenRowView: AccountTokenRow = {
- let view = AccountTokenRow()
+ let accountDeviceRow: AccountDeviceRow = {
+ let view = AccountDeviceRow()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ return view
+ }()
+
+ let accountTokenRowView: AccountNumberRow = {
+ let view = AccountNumberRow()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
@@ -54,7 +60,7 @@ class AccountContentView: UIView {
}()
lazy var contentStackView: UIStackView = {
- let stackView = UIStackView(arrangedSubviews: [accountTokenRowView, accountExpiryRowView])
+ let stackView = UIStackView(arrangedSubviews: [accountDeviceRow, accountTokenRowView, accountExpiryRowView])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = UIMetrics.sectionSpacing
@@ -95,17 +101,79 @@ class AccountContentView: UIView {
}
}
-class AccountTokenRow: UIView {
+class AccountDeviceRow: UIView {
- var value: String? {
+ var deviceName: String? {
didSet {
- valueButton.setTitle(value, for: .normal)
- accessibilityValue = value
+ deviceLabel.text = deviceName?.capitalized ?? ""
+ accessibilityValue = deviceName
}
}
- var actionHandler: (() -> Void)?
- private let textLabel: UILabel = {
+ private let titleLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.text = NSLocalizedString(
+ "DEVICE_NAME",
+ tableName: "Account",
+ value: "Device name",
+ comment: ""
+ )
+ label.font = UIFont.systemFont(ofSize: 14)
+ label.textColor = UIColor(white: 1.0, alpha: 0.6)
+ return label
+ }()
+
+ private let deviceLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.font = UIFont.systemFont(ofSize: 17)
+ label.textColor = .white
+ return label
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ addSubview(titleLabel)
+ addSubview(deviceLabel)
+
+ NSLayoutConstraint.activate([
+ titleLabel.topAnchor.constraint(equalTo: topAnchor),
+ titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
+ titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
+
+ deviceLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8),
+ deviceLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
+ deviceLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
+ deviceLabel.bottomAnchor.constraint(equalTo: bottomAnchor)
+ ])
+
+ isAccessibilityElement = true
+ accessibilityLabel = titleLabel.text
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
+
+class AccountNumberRow: UIView {
+ var accountNumber: String? {
+ didSet {
+ updateView()
+ }
+ }
+
+ var isObscured = true {
+ didSet {
+ updateView()
+ }
+ }
+
+ var copyAccountNumber: (() -> Void)?
+
+ private let titleLabel: UILabel = {
let textLabel = UILabel()
textLabel.translatesAutoresizingMaskIntoConstraints = false
textLabel.text = NSLocalizedString(
@@ -119,63 +187,220 @@ class AccountTokenRow: UIView {
return textLabel
}()
- private let valueButton: UIButton = {
+ private let accountNumberLabel: UILabel = {
+ let textLabel = UILabel()
+ textLabel.translatesAutoresizingMaskIntoConstraints = false
+ textLabel.font = UIFont.backport_monospacedSystemFont(ofSize: 17, weight: .regular)
+ textLabel.textColor = .white
+ return textLabel
+ }()
+
+ private let showHideButton: 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(
- "ACCOUNT_TOKEN_ACCESSIBILITY_HINT",
- tableName: "Account",
- value: "Tap to copy to pasteboard.",
- comment: ""
- )
+ button.tintColor = .white
+ button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return button
}()
+ private let copyButton: UIButton = {
+ let button = UIButton(type: .system)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.tintColor = .white
+ button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+ return button
+ }()
+
+ private var revertCopyImageWorkItem: DispatchWorkItem?
+
override init(frame: CGRect) {
super.init(frame: frame)
- addSubview(textLabel)
- addSubview(valueButton)
+ addSubview(titleLabel)
+ addSubview(accountNumberLabel)
+ addSubview(showHideButton)
+ addSubview(copyButton)
NSLayoutConstraint.activate([
- textLabel.topAnchor.constraint(equalTo: topAnchor),
- textLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
- textLabel.trailingAnchor.constraint(greaterThanOrEqualTo: trailingAnchor),
+ titleLabel.topAnchor.constraint(equalTo: topAnchor),
+ titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
+ titleLabel.trailingAnchor.constraint(greaterThanOrEqualTo: trailingAnchor),
- valueButton.topAnchor.constraint(equalTo: textLabel.bottomAnchor, constant: 8),
- valueButton.leadingAnchor.constraint(equalTo: leadingAnchor),
- valueButton.trailingAnchor.constraint(equalTo: trailingAnchor),
- valueButton.bottomAnchor.constraint(equalTo: bottomAnchor)
+ accountNumberLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8),
+ accountNumberLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
+ accountNumberLabel.trailingAnchor.constraint(equalTo: showHideButton.leadingAnchor),
+ accountNumberLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
+
+ showHideButton.heightAnchor.constraint(equalTo: accountNumberLabel.heightAnchor),
+ showHideButton.centerYAnchor.constraint(equalTo: accountNumberLabel.centerYAnchor),
+ showHideButton.leadingAnchor.constraint(equalTo: accountNumberLabel.trailingAnchor),
+
+ copyButton.heightAnchor.constraint(equalTo: accountNumberLabel.heightAnchor),
+ copyButton.centerYAnchor.constraint(equalTo: accountNumberLabel.centerYAnchor),
+ copyButton.leadingAnchor.constraint(equalTo: showHideButton.trailingAnchor, constant: 24),
+ copyButton.trailingAnchor.constraint(equalTo: trailingAnchor),
])
- isAccessibilityElement = true
- accessibilityLabel = textLabel.text
+ showHideButton.addTarget(
+ self,
+ action: #selector(didTapShowHideAccount),
+ for: .touchUpInside
+ )
- let actionName = NSLocalizedString(
- "ACCOUNT_TOKEN_ACCESSIBILITY_ACTION_TITLE",
- tableName: "Account",
- value: "Copy account token to pasteboard",
- comment: ""
+ copyButton.addTarget(
+ self,
+ action: #selector(didTapCopyAccountNumber),
+ for: .touchUpInside
)
- accessibilityCustomActions = [UIAccessibilityCustomAction(name: actionName, target: self, selector: #selector(performAccessibilityAction))]
- valueButton.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
+ isAccessibilityElement = true
+ accessibilityLabel = titleLabel.text
+
+ showCheckmark(false)
+ updateView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
- @objc private func handleTap() {
- self.actionHandler?()
+ // MARK: - Private
+
+ private func updateView() {
+ accountNumberLabel.text = displayAccountNumber ?? ""
+ showHideButton.setImage(showHideImage, for: .normal)
+
+ accessibilityAttributedValue = _accessibilityAttributedValue
+ accessibilityCustomActions = _accessibilityCustomActions
}
- @objc private func performAccessibilityAction() {
- self.actionHandler?()
+ private var displayAccountNumber: String? {
+ guard let accountNumber = accountNumber else {
+ return nil
+ }
+
+ let formattedString = StringFormatter.formattedAccountNumber(from: accountNumber)
+
+ if isObscured {
+ return String(formattedString.map { ch in
+ return ch == " " ? ch : "•"
+ })
+ } else {
+ return formattedString
+ }
+ }
+
+ private var showHideImage: UIImage? {
+ if isObscured {
+ return UIImage(named: "IconUnobscure")
+ } else {
+ return UIImage(named: "IconObscure")
+ }
+ }
+
+
+ private var _accessibilityAttributedValue: NSAttributedString? {
+ guard let accountNumber = accountNumber else {
+ return nil
+ }
+
+ if isObscured {
+ return NSAttributedString(
+ string: NSLocalizedString(
+ "ACCOUNT_ACCESSIBILITY_OBSCURED",
+ tableName: "Account",
+ value: "Obscured",
+ comment: ""
+ )
+ )
+ } else {
+ var attributes: [NSAttributedString.Key: Any]?
+ if #available(iOS 13.0, *) {
+ attributes = [.accessibilitySpeechSpellOut: true]
+ }
+
+ return NSAttributedString(string: accountNumber, attributes: attributes)
+ }
+ }
+
+ private var _accessibilityCustomActions: [UIAccessibilityCustomAction]? {
+ guard accountNumber != nil else { return nil }
+
+ return [
+ UIAccessibilityCustomAction(
+ name: showHideAccessibilityActionName,
+ target: self,
+ selector: #selector(didTapShowHideAccount)
+ ),
+ UIAccessibilityCustomAction(
+ name: NSLocalizedString(
+ "ACCOUNT_ACCESSIBILITY_COPY_TO_PASTEBOARD",
+ tableName: "Account",
+ value: "Copy to pasteboard",
+ comment: ""
+ ),
+ target: self,
+ selector: #selector(didTapCopyAccountNumber)
+ )
+ ]
+ }
+
+ private var showHideAccessibilityActionName: String {
+ if isObscured {
+ return NSLocalizedString(
+ "ACCOUNT_ACCESSIBILITY_SHOW_ACCOUNT_NUMBER",
+ tableName: "Account",
+ value: "Show account number",
+ comment: ""
+ )
+ } else {
+ return NSLocalizedString(
+ "ACCOUNT_ACCESSIBILITY_HIDE_ACCOUNT_NUMBER",
+ tableName: "Account",
+ value: "Hide account number",
+ comment: ""
+ )
+ }
+ }
+
+ private func showCheckmark(_ showCheckmark: Bool) {
+ if showCheckmark {
+ let tickIcon = UIImage(named: "IconTick")
+
+ copyButton.setImage(tickIcon, for: .normal)
+ copyButton.tintColor = .successColor
+ } else {
+ let copyIcon = UIImage(named: "IconCopy")
+
+ copyButton.setImage(copyIcon, for: .normal)
+ copyButton.tintColor = .white
+ }
+ }
+
+ // MARK: - Actions
+
+ @objc private func didTapShowHideAccount() {
+ isObscured.toggle()
+ updateView()
+
+ UIAccessibility.post(notification: .layoutChanged, argument: nil)
+ }
+
+ @objc private func didTapCopyAccountNumber() {
+ let delayedWorkItem = DispatchWorkItem { [weak self] in
+ self?.showCheckmark(false)
+ }
+
+ revertCopyImageWorkItem?.cancel()
+ revertCopyImageWorkItem = delayedWorkItem
+
+ showCheckmark(true)
+ copyAccountNumber?()
+
+ DispatchQueue.main.asyncAfter(
+ deadline: .now() + .seconds(2),
+ execute: delayedWorkItem
+ )
}
}
@@ -206,7 +431,7 @@ class AccountExpiryRow: UIView {
)
}
- valueLabel.text = formattedDate
+ valueLabel.text = formattedDate ?? ""
accessibilityValue = formattedDate
valueLabel.textColor = .white
diff --git a/ios/MullvadVPN/AccountInputGroupView.swift b/ios/MullvadVPN/AccountInputGroupView.swift
index 7bc54d236b..6dfff6301d 100644
--- a/ios/MullvadVPN/AccountInputGroupView.swift
+++ b/ios/MullvadVPN/AccountInputGroupView.swift
@@ -374,11 +374,7 @@ class AccountInputGroupView: UIView {
// MARK: - Private
private static func accountNumberFont() -> UIFont {
- if #available(iOS 13, *) {
- return UIFont.monospacedSystemFont(ofSize: 20, weight: .regular)
- } else {
- return UIFont.systemFont(ofSize: 20)
- }
+ return UIFont.backport_monospacedSystemFont(ofSize: 20, weight: .regular)
}
private func addTextFieldNotificationObservers() {
diff --git a/ios/MullvadVPN/AccountViewController.swift b/ios/MullvadVPN/AccountViewController.swift
index 9753d8aac9..f594e0a6c1 100644
--- a/ios/MullvadVPN/AccountViewController.swift
+++ b/ios/MullvadVPN/AccountViewController.swift
@@ -84,10 +84,8 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb
comment: ""
)
- contentView.accountTokenRowView.value = TunnelManager.shared.accountNumber.map { string in
- return StringFormatter.formattedAccountNumber(from: string)
- }
- contentView.accountTokenRowView.actionHandler = { [weak self] in
+ contentView.accountTokenRowView.accountNumber = TunnelManager.shared.accountNumber
+ contentView.accountTokenRowView.copyAccountNumber = { [weak self] in
self?.copyAccountToken()
}
@@ -99,6 +97,7 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb
TunnelManager.shared.addObserver(self)
updateAccountExpiry(expiryDate: TunnelManager.shared.accountExpiry)
+ updateDeviceName(TunnelManager.shared.device?.name)
// Make sure to disable IAPs when payments are restricted
if AppStorePaymentManager.canMakePayments {
@@ -110,6 +109,10 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb
// MARK: - Private methods
+ private func updateDeviceName(_ deviceName: String?) {
+ contentView.accountDeviceRow.deviceName = deviceName
+ }
+
private func updateAccountExpiry(expiryDate: Date?) {
contentView.accountExpiryRowView.value = expiryDate
}
@@ -322,7 +325,12 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb
}
func tunnelManager(_ manager: TunnelManager, didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2?) {
- updateAccountExpiry(expiryDate: tunnelSettings?.account.expiry)
+ guard let tunnelSettings = tunnelSettings else {
+ return
+ }
+
+ updateDeviceName(tunnelSettings.device.name)
+ updateAccountExpiry(expiryDate: tunnelSettings.account.expiry)
}
// MARK: - AppStorePaymentObserver
@@ -377,24 +385,6 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb
private func copyAccountToken() {
UIPasteboard.general.string = TunnelManager.shared.accountNumber
-
- contentView.accountTokenRowView.value = NSLocalizedString(
- "COPIED_TO_PASTEBOARD_LABEL",
- tableName: "Account",
- value: "COPIED TO PASTEBOARD!",
- comment: ""
- )
-
- let workItem = DispatchWorkItem { [weak self] in
- guard let accountNumber = TunnelManager.shared.accountNumber else { return }
-
- self?.contentView.accountTokenRowView.value = StringFormatter.formattedAccountNumber(from: accountNumber)
- }
-
- copyToPasteboardWork?.cancel()
- copyToPasteboardWork = workItem
-
- DispatchQueue.main.asyncAfter(wallDeadline: .now() + .seconds(3), execute: workItem)
}
@objc private func doPurchase() {
diff --git a/ios/MullvadVPN/Assets.xcassets/IconCopy.imageset/Contents.json b/ios/MullvadVPN/Assets.xcassets/IconCopy.imageset/Contents.json
new file mode 100644
index 0000000000..761f256346
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/IconCopy.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "IconCopy.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/ios/MullvadVPN/Assets.xcassets/IconCopy.imageset/IconCopy.pdf b/ios/MullvadVPN/Assets.xcassets/IconCopy.imageset/IconCopy.pdf
new file mode 100644
index 0000000000..47ec193d80
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/IconCopy.imageset/IconCopy.pdf
Binary files differ
diff --git a/ios/MullvadVPN/Assets.xcassets/IconObscure.imageset/Contents.json b/ios/MullvadVPN/Assets.xcassets/IconObscure.imageset/Contents.json
new file mode 100644
index 0000000000..e3f7020b58
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/IconObscure.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "IconObscure.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/ios/MullvadVPN/Assets.xcassets/IconObscure.imageset/IconObscure.pdf b/ios/MullvadVPN/Assets.xcassets/IconObscure.imageset/IconObscure.pdf
new file mode 100644
index 0000000000..813fa38764
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/IconObscure.imageset/IconObscure.pdf
Binary files differ
diff --git a/ios/MullvadVPN/Assets.xcassets/IconUnobscure.imageset/Contents.json b/ios/MullvadVPN/Assets.xcassets/IconUnobscure.imageset/Contents.json
new file mode 100644
index 0000000000..c69f047004
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/IconUnobscure.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "IconUnobscure.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/ios/MullvadVPN/Assets.xcassets/IconUnobscure.imageset/IconUnobscure.pdf b/ios/MullvadVPN/Assets.xcassets/IconUnobscure.imageset/IconUnobscure.pdf
new file mode 100644
index 0000000000..77452cf7b2
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/IconUnobscure.imageset/IconUnobscure.pdf
Binary files differ
diff --git a/ios/MullvadVPN/ProblemReportReviewViewController.swift b/ios/MullvadVPN/ProblemReportReviewViewController.swift
index c8b2e8a6a9..19fc05a248 100644
--- a/ios/MullvadVPN/ProblemReportReviewViewController.swift
+++ b/ios/MullvadVPN/ProblemReportReviewViewController.swift
@@ -40,14 +40,10 @@ class ProblemReportReviewViewController: UIViewController {
textView.translatesAutoresizingMaskIntoConstraints = false
textView.text = reportString
textView.isEditable = false
- if #available(iOS 13.0, *) {
- textView.font = UIFont.monospacedSystemFont(
- ofSize: UIFont.systemFontSize,
- weight: .regular
- )
- } else {
- textView.font = UIFont(name: "Courier", size: UIFont.systemFontSize)
- }
+ textView.font = UIFont.backport_monospacedSystemFont(
+ ofSize: UIFont.systemFontSize,
+ weight: .regular
+ )
view.addSubview(textView)
diff --git a/ios/MullvadVPN/UIFont+Monospaced.swift b/ios/MullvadVPN/UIFont+Monospaced.swift
new file mode 100644
index 0000000000..5ce7bb633b
--- /dev/null
+++ b/ios/MullvadVPN/UIFont+Monospaced.swift
@@ -0,0 +1,27 @@
+//
+// UIFont+Monospaced.swift
+// MullvadVPN
+//
+// Created by pronebird on 28/06/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import UIKit
+
+extension UIFont {
+ class func backport_monospacedSystemFont(ofSize size: CGFloat, weight: UIFont.Weight) -> UIFont
+ {
+ if #available(iOS 13, *) {
+ return UIFont.monospacedSystemFont(ofSize: size, weight: weight)
+ } else {
+ let fontDescriptor = UIFontDescriptor(fontAttributes: [
+ .name: "Menlo",
+ .traits: [
+ UIFontDescriptor.TraitKey.weight: weight
+ ]
+ ])
+
+ return UIFont(descriptor: fontDescriptor, size: size)
+ }
+ }
+}
diff --git a/ios/convert-assets.rb b/ios/convert-assets.rb
index 5d8be56277..5a26616e9e 100755
--- a/ios/convert-assets.rb
+++ b/ios/convert-assets.rb
@@ -32,7 +32,10 @@ GRAPHICAL_ASSETS = [
"location-marker-unsecure.svg",
"logo-icon.svg",
"logo-text.svg",
- "icon-close-sml.svg"
+ "icon-close-sml.svg",
+ "icon-copy.svg",
+ "icon-obscure.svg",
+ "icon-unobscure.svg"
]
# App icon sizes